diff options
-rw-r--r-- | src/Network/Torrent/PeerID.hs | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/Network/Torrent/PeerID.hs b/src/Network/Torrent/PeerID.hs new file mode 100644 index 00000000..35c70c9d --- /dev/null +++ b/src/Network/Torrent/PeerID.hs | |||
@@ -0,0 +1,113 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | -- | Recommended method for generation of the peer ID's is 'newPeerID', | ||
3 | -- though this module exports some other goodies for custom generation. | ||
4 | -- | ||
5 | module Network.Torrent.PeerID | ||
6 | ( PeerID(PeerID, getPeerID) | ||
7 | -- * Encoding styles | ||
8 | , azureusStyle, shadowStyle | ||
9 | -- * Defaults | ||
10 | , defaultClientID, defaultVersionNumber | ||
11 | -- * Generation | ||
12 | , newPeerID, timestampByteString | ||
13 | -- * Extra | ||
14 | , byteStringPadded | ||
15 | ) where | ||
16 | |||
17 | import Control.Applicative | ||
18 | import Data.ByteString (ByteString) | ||
19 | import qualified Data.ByteString as B | ||
20 | import qualified Data.ByteString.Char8 as BC | ||
21 | import qualified Data.ByteString.Lazy as BL | ||
22 | import qualified Data.ByteString.Builder as B | ||
23 | import Data.Foldable (foldMap) | ||
24 | import Data.Monoid ((<>)) | ||
25 | import Data.Version (versionBranch) | ||
26 | import Data.Time.Clock (getCurrentTime) | ||
27 | import Data.Time.Format (formatTime) | ||
28 | import System.Locale (defaultTimeLocale) | ||
29 | |||
30 | import Paths_network_torrent (version) | ||
31 | |||
32 | |||
33 | -- | Peer identifier is exactly 20 bytes long bytestring. | ||
34 | newtype PeerID = PeerID { getPeerID :: ByteString } | ||
35 | deriving (Show, Eq, Ord) | ||
36 | |||
37 | -- | Azureus-style encoding: | ||
38 | -- * 1 byte : '-' | ||
39 | -- * 2 bytes: client id | ||
40 | -- * 4 bytes: version number | ||
41 | -- * 1 byte : '-' | ||
42 | -- * 12 bytes: random number | ||
43 | -- | ||
44 | azureusStyle :: ByteString -- ^ 2 character client ID, padded with 'H'. | ||
45 | -> ByteString -- ^ Version number, padded with 'X'. | ||
46 | -> ByteString -- ^ Random number, padded with '0'. | ||
47 | -> PeerID -- ^ Azureus-style encoded peer ID. | ||
48 | azureusStyle cid ver rnd = PeerID $ BL.toStrict $ B.toLazyByteString $ | ||
49 | B.char8 '-' <> | ||
50 | byteStringPadded cid 2 'H' <> | ||
51 | byteStringPadded ver 4 'X' <> | ||
52 | B.char8 '-' <> | ||
53 | byteStringPadded rnd 12 '0' | ||
54 | |||
55 | -- | Shadow-style encoding: | ||
56 | -- * 1 byte : client id. | ||
57 | -- * 0-4 bytes: version number. If less than 4 then padded with '-' char. | ||
58 | -- * 15 bytes : random number. If length is less than 15 then padded with '0' char. | ||
59 | -- | ||
60 | shadowStyle :: Char -- ^ Client ID. | ||
61 | -> ByteString -- ^ Version number. | ||
62 | -> ByteString -- ^ Random number. | ||
63 | -> PeerID -- ^ Shadow style encoded peer ID. | ||
64 | shadowStyle cid ver rnd = PeerID $ BL.toStrict $ B.toLazyByteString $ | ||
65 | B.char8 cid <> | ||
66 | byteStringPadded ver 4 '-' <> | ||
67 | byteStringPadded rnd 15 '0' | ||
68 | |||
69 | |||
70 | -- | "HS" - 2 bytes long client identifier. | ||
71 | defaultClientID :: ByteString | ||
72 | defaultClientID = "HS" | ||
73 | |||
74 | -- | Gives exactly 4 bytes long version number for any version of the package. | ||
75 | -- Version is taken from .cabal. | ||
76 | defaultVersionNumber :: ByteString | ||
77 | defaultVersionNumber = B.take 4 (BC.pack (foldMap show (versionBranch version))) | ||
78 | |||
79 | -- | Gives 15 characters long decimal timestamp such that: | ||
80 | -- * 6 bytes : first 6 characters from picoseconds obtained with %q. | ||
81 | -- * 1 bytes : character '.' for readability. | ||
82 | -- * 9..* bytes: number of whole seconds since the Unix epoch (!)REVERSED. | ||
83 | -- Can be used both with shadow and azureus style encoding. This format is | ||
84 | -- used to make the ID's readable(for debugging) and more or less random. | ||
85 | -- | ||
86 | timestampByteString :: IO ByteString | ||
87 | timestampByteString = (BC.pack . format) <$> getCurrentTime | ||
88 | where | ||
89 | format t = take 6 (formatTime defaultTimeLocale "%q" t) ++ "." ++ | ||
90 | take 9 (reverse (formatTime defaultTimeLocale "%s" t)) | ||
91 | |||
92 | -- | Here we use Azureus-style encoding with the following args: | ||
93 | -- * 'HS' for the client id. | ||
94 | -- * Version of the package for the version number | ||
95 | -- * UTC time day ++ day time for the random number. | ||
96 | -- | ||
97 | newPeerID :: IO PeerID | ||
98 | newPeerID = azureusStyle defaultClientID defaultVersionNumber | ||
99 | <$> timestampByteString | ||
100 | |||
101 | -- | length < size: Complete bytestring by given charaters. | ||
102 | -- length = size: Output bytestring as is. | ||
103 | -- length > size: Drop last (length - size) charaters from a given bytestring. | ||
104 | -- | ||
105 | byteStringPadded :: ByteString -- ^ bytestring to be padded. | ||
106 | -> Int -- ^ size of result builder. | ||
107 | -> Char -- ^ character used for padding. | ||
108 | -> B.Builder | ||
109 | byteStringPadded bs s c = | ||
110 | B.byteString (B.take s bs) <> | ||
111 | B.byteString (BC.replicate padLen c) | ||
112 | where | ||
113 | padLen = s - max (B.length bs) s \ No newline at end of file | ||