packages feed

conjure-0.1: src/Conjure/Protocol/PWP/Parser.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Conjure.Protocol.PWP.Parser
-- Copyright   :  (c) Lemmih 2005-2006
-- License     :  BSD-like
--
-- Maintainer  :  lemmih@gmail.com
-- Stability   :  experimental
-- Portability :  non-portable (requires Foreign.*)
--
-- Input handler for the Peer Wire Protocol.
-----------------------------------------------------------------------------
module Conjure.Protocol.PWP.Parser
    ( hGetHandshake
    , hGetMessage
    -- For tests
    , parseMessage
    ) where

import Control.Monad ( liftM )
import System.IO
import System.IO.Unsafe
import Foreign

import qualified Data.ByteString.Char8 as Char8
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import Data.ByteString (ByteString)

import Conjure.Protocol.PWP.Types
import Conjure.Utils

-- | Read a 4-byte big-endian @Int@ off the wire
hGetInt :: Handle -> IO Int
hGetInt h = do [a,b,c,d] <- mapM (liftM fromEnum) (replicate 4 (hGetChar h))
               return (a `shiftL` 24 + b `shiftL` 16 + c `shiftL` 8 + d)

-- | Parse a 4-byte big-endian @Int@
parseInt :: ByteString -> (Int, ByteString)
parseInt str
    = let [a,b,c,d] = map (fromIntegral . BS.index str) [0,1,2,3]
      in (a `shiftL` 24 .|. b `shiftL` 16 .|. c `shiftL` 8 .|. d, BS.drop 4 str)


hGetHandshake :: Handle -> IO Handshake
hGetHandshake h = do c <- hGetChar h
                     let len = fromEnum c
                     if len /= 19 then error "Unknown protocol in handshake" else do
                     msg <- BS.hGet h len
                     if msg /= Char8.pack "BitTorrent protocol" then error "Unknown protocol in handshake" else do
                     BS.hGet h 8 -- Those 8 reserved bytes ...
                     infohash <- BS.hGet h 20
                     client <- unsafeInterleaveIO $ BS.hGet h 20
                     -- 'unsafeInterleaveIO' is required because
                     -- handshake could be without client id. This
                     -- happens when tracker wants to check whether
                     -- we are alive and not behind the NAT. This
                     -- way we could postpone reading client id for
                     -- later, after we have sent our own handshake
                     return $ Handshake infohash (PeerId client)

-- This a bit ugly.
-- | Read a @Message@ from a @Handle@ and report when chunks are downloaded.
hGetMessage :: (Integer -- ^ Current time in milliseconds.
                -> Int  -- ^ Bytes per second.
                -> IO ())
            -> Handle -> IO Message
hGetMessage report h
    = do len <- hGetInt h
         if len == 0 then return KeepAlive else do
         let loop l ptr
                 = do hWaitForInput h (-1)
                      realLen <- hGetBufNonBlocking h ptr l
                      now <- getCurrentTime
                      report now realLen
                      if realLen == l
                         then return ()
                         else loop (l-realLen) (advancePtr ptr realLen)
         msgID <- fmap BS.head $ BS.hGet h 1
         msg <- if msgID == 7
                then do fp <- mallocForeignPtrBytes (len-1)
                        withForeignPtr fp (loop (len-1))
                        return $ BS.fromForeignPtr fp 0 (len-1)
                else BS.hGet h (len - 1)
         return $! parseMessage msgID msg

-- FIXME: error handling.
parseMessage :: Word8 -> ByteString -> Message
parseMessage msgID payload
    = case msgID of
        0 -> Choke
        1 -> Unchoke
        2 -> Interested
        3 -> NotInterested
        4 -> Have (fst $ parseInt payload)
        5 -> BitField payload
        6 -> let [idx, offset, len] = take 3 (combine parseInt)
             in Request idx offset len
        7 -> let (idx,str') = parseInt payload
                 (offset,str'') = parseInt str'
             in RequestedPiece idx offset str''
        8 -> let [idx, offset, len] = take 3 (combine parseInt)
             in Cancel idx offset len
        _ -> Unknown msgID
    where combine f = let loop str = let (val,str') = f str in val : loop str'
                      in loop payload