summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2013-11-28 06:28:06 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2013-11-28 06:28:06 +0400
commitfc4ed85a313f93b4fb0c46b500ee12c38e94df68 (patch)
tree3eed36a608878fe5d232ab5d84df1e4ffefed24a
parent533068e7ebbf3ae5f15bd7b65312a69ab50052e5 (diff)
Implement AnnounceQuery parsing
-rw-r--r--src/Data/Torrent/InfoHash.hs20
-rw-r--r--src/Network/BitTorrent/Core/PeerId.hs18
-rw-r--r--src/Network/BitTorrent/Tracker/Message.hs108
-rw-r--r--tests/Network/BitTorrent/Core/PeerIdSpec.hs9
-rw-r--r--tests/Network/BitTorrent/Tracker/MessageSpec.hs12
5 files changed, 108 insertions, 59 deletions
diff --git a/src/Data/Torrent/InfoHash.hs b/src/Data/Torrent/InfoHash.hs
index 3d2bbe25..a4d6a02e 100644
--- a/src/Data/Torrent/InfoHash.hs
+++ b/src/Data/Torrent/InfoHash.hs
@@ -14,6 +14,7 @@ module Data.Torrent.InfoHash
14 InfoHash(..) 14 InfoHash(..)
15 15
16 -- * Parsing 16 -- * Parsing
17 , byteStringToInfoHash
17 , textToInfoHash 18 , textToInfoHash
18 19
19 -- * Rendering 20 -- * Rendering
@@ -54,6 +55,16 @@ import Text.PrettyPrint
54import Text.PrettyPrint.Class 55import Text.PrettyPrint.Class
55 56
56 57
58-- TODO
59--
60-- data Word160 = Word160 {-# UNPACK #-} !Word64
61-- {-# UNPACK #-} !Word64
62-- {-# UNPACK #-} !Word32
63--
64-- newtype InfoHash = InfoHash Word160
65--
66-- reason: bytestring have overhead = 8 words, while infohash have length 20 bytes
67
57-- | Exactly 20 bytes long SHA1 hash of the info part of torrent file. 68-- | Exactly 20 bytes long SHA1 hash of the info part of torrent file.
58newtype InfoHash = InfoHash { getInfoHash :: BS.ByteString } 69newtype InfoHash = InfoHash { getInfoHash :: BS.ByteString }
59 deriving (Eq, Ord) 70 deriving (Eq, Ord)
@@ -110,6 +121,15 @@ instance URLShow InfoHash where
110instance Pretty InfoHash where 121instance Pretty InfoHash where
111 pretty = text . BC.unpack . ppHex . getInfoHash 122 pretty = text . BC.unpack . ppHex . getInfoHash
112 123
124infoHashLen :: Int
125infoHashLen = 20
126
127-- | Convert raw bytes to info hash.
128byteStringToInfoHash :: BS.ByteString -> Maybe InfoHash
129byteStringToInfoHash bs
130 | BS.length bs == infoHashLen = Just (InfoHash bs)
131 | otherwise = Nothing
132
113-- | Tries both base16 and base32 while decoding info hash. 133-- | Tries both base16 and base32 while decoding info hash.
114textToInfoHash :: Text -> Maybe InfoHash 134textToInfoHash :: Text -> Maybe InfoHash
115textToInfoHash t 135textToInfoHash t
diff --git a/src/Network/BitTorrent/Core/PeerId.hs b/src/Network/BitTorrent/Core/PeerId.hs
index 2c79739a..a2b03e92 100644
--- a/src/Network/BitTorrent/Core/PeerId.hs
+++ b/src/Network/BitTorrent/Core/PeerId.hs
@@ -15,6 +15,7 @@
15module Network.BitTorrent.Core.PeerId 15module Network.BitTorrent.Core.PeerId
16 ( -- * PeerId 16 ( -- * PeerId
17 PeerId (getPeerId) 17 PeerId (getPeerId)
18 , byteStringToPeerId
18 19
19 -- * Generation 20 -- * Generation
20 , genPeerId 21 , genPeerId
@@ -60,30 +61,37 @@ import Paths_bittorrent (version)
60 61
61import Data.Torrent.Client 62import Data.Torrent.Client
62 63
63-- TODO use unpacked form (length is known statically) 64-- TODO use unpacked Word160 form (length is known statically)
64 65
65-- | Peer identifier is exactly 20 bytes long bytestring. 66-- | Peer identifier is exactly 20 bytes long bytestring.
66newtype PeerId = PeerId { getPeerId :: ByteString } 67newtype PeerId = PeerId { getPeerId :: ByteString }
67 deriving (Show, Eq, Ord, BEncode, ToJSON, FromJSON) 68 deriving (Show, Eq, Ord, BEncode, ToJSON, FromJSON)
68 69
70peerIdLen :: Int
71peerIdLen = 20
72
69instance Serialize PeerId where 73instance Serialize PeerId where
70 put = putByteString . getPeerId 74 put = putByteString . getPeerId
71 get = PeerId <$> getBytes 20 75 get = PeerId <$> getBytes peerIdLen
72 76
73instance URLShow PeerId where 77instance URLShow PeerId where
74 urlShow = BC.unpack . getPeerId 78 urlShow = BC.unpack . getPeerId
75 79
76instance IsString PeerId where 80instance IsString PeerId where
77 fromString str 81 fromString str
78 | BS.length bs == 20 = PeerId bs 82 | BS.length bs == peerIdLen = PeerId bs
79 | otherwise = error $ "Peer id should be 20 bytes long: " 83 | otherwise = error $ "Peer id should be 20 bytes long: " ++ show str
80 ++ show str
81 where 84 where
82 bs = fromString str 85 bs = fromString str
83 86
84instance Pretty PeerId where 87instance Pretty PeerId where
85 pretty = text . BC.unpack . getPeerId 88 pretty = text . BC.unpack . getPeerId
86 89
90byteStringToPeerId :: BS.ByteString -> Maybe PeerId
91byteStringToPeerId bs
92 | BS.length bs == peerIdLen = Just (PeerId bs)
93 | otherwise = Nothing
94
87{----------------------------------------------------------------------- 95{-----------------------------------------------------------------------
88-- Encoding 96-- Encoding
89-----------------------------------------------------------------------} 97-----------------------------------------------------------------------}
diff --git a/src/Network/BitTorrent/Tracker/Message.hs b/src/Network/BitTorrent/Tracker/Message.hs
index 3447a367..59ef2027 100644
--- a/src/Network/BitTorrent/Tracker/Message.hs
+++ b/src/Network/BitTorrent/Tracker/Message.hs
@@ -52,6 +52,8 @@ import Data.Aeson (ToJSON(..), FromJSON(..))
52import Data.Aeson.TH 52import Data.Aeson.TH
53import Data.BEncode as BE 53import Data.BEncode as BE
54import Data.BEncode.BDict as BE 54import Data.BEncode.BDict as BE
55import Data.ByteString as BS
56import Data.ByteString.Char8 as BC
55import Data.Char as Char 57import Data.Char as Char
56import Data.List as L 58import Data.List as L
57import Data.Map as M 59import Data.Map as M
@@ -64,8 +66,10 @@ import Data.Typeable
64import Data.URLEncoded as URL 66import Data.URLEncoded as URL
65import Data.Word 67import Data.Word
66import Network 68import Network
67import Network.URI 69import Network.HTTP.Types.URI hiding (urlEncode)
68import Network.Socket 70import Network.Socket
71import Network.URI
72import Text.Read (readMaybe)
69 73
70import Data.Torrent.InfoHash 74import Data.Torrent.InfoHash
71import Data.Torrent.Progress 75import Data.Torrent.Progress
@@ -226,63 +230,83 @@ data QueryParam
226 = ParamInfoHash 230 = ParamInfoHash
227 | ParamPeerId 231 | ParamPeerId
228 | ParamPort 232 | ParamPort
229 | ParamProgress 233 | ParamUploaded
234 | ParamLeft
235 | ParamDownloaded
230 | ParamIP 236 | ParamIP
231 | ParamNumWant 237 | ParamNumWant
232 | ParamEvent 238 | ParamEvent
233 deriving (Show, Eq, Ord, Enum) 239 deriving (Show, Eq, Ord, Enum)
234 240
235data ParamParseFailure 241data ParamParseFailure
236 = Missing QueryParam -- ^ param not found in query string; 242 = Missing QueryParam -- ^ param not found in query string;
237 | Invalid QueryParam Text -- ^ param present but not valid. 243 | Invalid QueryParam ByteString -- ^ param present but not valid.
238 deriving (Show, Eq) 244 deriving (Show, Eq)
239 245
240type ParamResult = Either ParamParseFailure 246paramName :: QueryParam -> ByteString
247paramName ParamInfoHash = "info_hash"
248paramName ParamPeerId = "peer_id"
249paramName ParamPort = "port"
250paramName ParamUploaded = "uploaded"
251paramName ParamLeft = "left"
252paramName ParamDownloaded = "downloaded"
253paramName ParamIP = "ip"
254paramName ParamNumWant = "numwant"
255paramName ParamEvent = "event"
256
257class FromParam a where
258 fromParam :: BS.ByteString -> Maybe a
259
260instance FromParam InfoHash where
261 fromParam = byteStringToInfoHash
262
263instance FromParam PeerId where
264 fromParam = byteStringToPeerId
265
266instance FromParam Word32 where
267 fromParam = readMaybe . BC.unpack
268
269instance FromParam Word64 where
270 fromParam = readMaybe . BC.unpack
271
272instance FromParam Int where
273 fromParam = readMaybe . BC.unpack
241 274
242textToPeerId :: Text -> Maybe PeerId 275instance FromParam PortNumber where
243textToPeerId = undefined 276 fromParam bs = fromIntegral <$> (fromParam bs :: Maybe Word32)
244 277
245textToPortNumber :: Text -> Maybe PortNumber 278instance FromParam Event where
246textToPortNumber = undefined 279 fromParam bs = case BC.uncons bs of
280 Nothing -> Nothing
281 Just (x, xs) -> readMaybe $ BC.unpack $ BC.cons (Char.toUpper x) xs
247 282
248textToHostAddress :: Text -> Maybe HostAddress 283withError e = maybe (Left e) Right
249textToHostAddress = undefined
250 284
251textToNumWant :: Text -> Maybe Int 285reqParam param xs = do
252textToNumWant = undefined 286 val <- withError (Missing param) $ L.lookup (paramName param) xs
287 withError (Invalid param val) (fromParam val)
253 288
254textToEvent :: Text -> Maybe Event 289optParam param ps
255textToEvent = undefined 290 | Just x <- L.lookup (paramName param) ps
291 = pure <$> withError (Invalid param x) (fromParam x)
292 | otherwise = pure Nothing
256 293
257paramName :: QueryParam -> Text 294parseProgress :: SimpleQuery -> Either ParamParseFailure Progress
258paramName ParamInfoHash = "info_hash" 295parseProgress params = Progress
259paramName ParamPeerId = "peer_id" 296 <$> reqParam ParamDownloaded params
260paramName ParamPort = "port" 297 <*> reqParam ParamLeft params
298 <*> reqParam ParamUploaded params
261 299
262-- | Parse announce request from a decoded query string. 300-- | Parse announce request from a query string.
263parseAnnounceQuery :: [(Text, Text)] -> Either ParamParseFailure AnnounceQuery 301parseAnnounceQuery :: SimpleQuery -> Either ParamParseFailure AnnounceQuery
264parseAnnounceQuery params = AnnounceQuery 302parseAnnounceQuery params = AnnounceQuery
265 <$> reqParam ParamInfoHash textToInfoHash params 303 <$> reqParam ParamInfoHash params
266 <*> reqParam ParamPeerId textToPeerId params 304 <*> reqParam ParamPeerId params
267 <*> reqParam ParamPort textToPortNumber params 305 <*> reqParam ParamPort params
268 <*> progress params 306 <*> parseProgress params
269 <*> optParam ParamIP textToHostAddress params 307 <*> optParam ParamIP params
270 <*> optParam ParamNumWant textToNumWant params 308 <*> optParam ParamNumWant params
271 <*> optParam ParamEvent textToEvent params 309 <*> optParam ParamEvent params
272 where
273 withError e = maybe (Left e) Right
274 reqParam param p = withError (Missing param) . L.lookup (paramName param)
275 >=> \x -> withError (Invalid param x) (p x)
276
277 optParam param p ps
278 | Just x <- L.lookup (paramName param) ps
279 = pure <$> withError (Invalid param x) (p x)
280 | otherwise = pure Nothing
281
282 progress = undefined
283 ip = undefined
284 numwant = undefined
285 event = undefined
286 310
287-- TODO add extension datatype 311-- TODO add extension datatype
288 312
diff --git a/tests/Network/BitTorrent/Core/PeerIdSpec.hs b/tests/Network/BitTorrent/Core/PeerIdSpec.hs
index 7153ef68..4e9f1655 100644
--- a/tests/Network/BitTorrent/Core/PeerIdSpec.hs
+++ b/tests/Network/BitTorrent/Core/PeerIdSpec.hs
@@ -1,15 +1,18 @@
1module Network.BitTorrent.Core.PeerIdSpec (spec) where 1module Network.BitTorrent.Core.PeerIdSpec (spec) where
2import Control.Applicative 2import Control.Applicative
3import Data.Text.Encoding as T
3import Test.Hspec 4import Test.Hspec
4import Test.QuickCheck 5import Test.QuickCheck
5import Test.QuickCheck.Instances 6import Test.QuickCheck.Instances ()
6import Network.BitTorrent.Core.PeerId 7import Network.BitTorrent.Core.PeerId
7 8
8 9
9instance Arbitrary PeerId where 10instance Arbitrary PeerId where
10 arbitrary = oneof 11 arbitrary = oneof
11 [ azureusStyle defaultClientId defaultVersionNumber <$> arbitrary 12 [ azureusStyle defaultClientId defaultVersionNumber
12 , shadowStyle 'X' defaultVersionNumber <$> arbitrary 13 <$> pure ""
14-- , shadowStyle 'X' defaultVersionNumber
15-- <$> (T.encodeUtf8 <$> arbitrary)
13 ] 16 ]
14 17
15spec :: Spec 18spec :: Spec
diff --git a/tests/Network/BitTorrent/Tracker/MessageSpec.hs b/tests/Network/BitTorrent/Tracker/MessageSpec.hs
index 8a06aaba..2a882c5d 100644
--- a/tests/Network/BitTorrent/Tracker/MessageSpec.hs
+++ b/tests/Network/BitTorrent/Tracker/MessageSpec.hs
@@ -44,8 +44,8 @@ instance Arbitrary AnnounceQuery where
44baseURI :: URI 44baseURI :: URI
45baseURI = fromJust $ parseURI "http://a" 45baseURI = fromJust $ parseURI "http://a"
46 46
47parseUriQuery :: URI -> [(Text, Text)] 47parseUriQuery :: URI -> SimpleQuery
48parseUriQuery = filterMaybes . parseQueryText . BC.pack . uriQuery 48parseUriQuery = filterMaybes . parseQuery . BC.pack . uriQuery
49 where 49 where
50 filterMaybes :: [(a, Maybe b)] -> [(a, b)] 50 filterMaybes :: [(a, Maybe b)] -> [(a, b)]
51 filterMaybes = catMaybes . L.map f 51 filterMaybes = catMaybes . L.map f
@@ -53,16 +53,10 @@ parseUriQuery = filterMaybes . parseQueryText . BC.pack . uriQuery
53 f (a, Nothing) = Nothing 53 f (a, Nothing) = Nothing
54 f (a, Just b ) = Just (a, b) 54 f (a, Just b ) = Just (a, b)
55 55
56test = do
57 let q = unGen arbitrary (mkStdGen 0) 0
58 print $ renderAnnounceQuery baseURI q
59 print $ parseUriQuery $ renderAnnounceQuery baseURI q
60
61spec :: Spec 56spec :: Spec
62spec = do 57spec = do
63 describe "Announce" $ do 58 describe "Announce" $ do
64 before test $ 59 it "properly url encoded" $ property $ \ q ->
65 it "properly url encoded" $ property $ \ q ->
66 parseAnnounceQuery (parseUriQuery (renderAnnounceQuery baseURI q)) 60 parseAnnounceQuery (parseUriQuery (renderAnnounceQuery baseURI q))
67 `shouldBe` Right q 61 `shouldBe` Right q
68 62