content: {-# LANGUAGE ExistentialQuantification #-}
  -- |Defines the network API required for a Tor implementation to run.
  module Tor.NetworkStack(
           TorNetworkStack(..)
         , SomeNetworkStack(..)
         , toBackend
         , recvAll
         , recvLine
         )
   where
  
  import qualified Data.ByteString      as S
  import qualified Data.ByteString.Lazy as L
  import Data.Word
  import Network.TLS
  import Tor.DataFormat.TorAddress
  
  -- |A network stack, but with the type variables hidden.
  data SomeNetworkStack = forall lsock sock . HasBackend sock =>
         MkNS (TorNetworkStack lsock sock)
  
  -- |The type of a Tor-compatible network stack. The first type variable is the
  -- type of a listener socket, the second the type of a standard connection
  -- socket. 
  data TorNetworkStack lsock sock = TorNetworkStack {
         connect    :: String -> Word16       -> IO (Maybe sock)
         -- |Lookup the given hostname and return any IP6 (Left) or IP4 (Right)
         -- addresses associated with it.
       , getAddress :: String                 -> IO [TorAddress]
       , listen     :: Word16                 -> IO lsock
       , accept     :: lsock                  -> IO (sock, TorAddress)
       , recv       :: sock   -> Int          -> IO S.ByteString
       , write      :: sock   -> L.ByteString -> IO ()
       , flush      :: sock                   -> IO ()
       , close      :: sock                   -> IO ()
       , lclose     :: lsock                  -> IO ()
       }
  
  -- |Receive a line of ASCII text from a socket.
  recvLine :: TorNetworkStack ls s -> s -> IO L.ByteString
  recvLine ns s = go []
   where
    go acc =
      do next <- recv ns s 1
         case S.uncons next of
           Nothing      -> return (L.pack (reverse acc))
           Just (10, _) -> return (L.pack (reverse acc))
           Just (f, _)  -> go (f:acc)
  
  -- |Receive all the input from the socket as a lazy ByteString; this may cause
  -- the system to block upon some ByteString operations to fetch more data.
  recvAll :: TorNetworkStack ls s -> s -> IO L.ByteString
  recvAll ns s = go []
   where
    go acc =
      do next <- recv ns s 4096
         if S.null next
            then return (L.fromChunks (reverse acc))
            else go (next:acc)
  
  -- |Convert a Tor-compatible network stack to a TLS-compatible Backend
  -- structure.
  toBackend :: TorNetworkStack ls s -> s -> Backend
  toBackend ns s = Backend {
      backendFlush = flush ns s
    , backendClose = close ns s
    , backendRecv  = recv  ns s
    , backendSend  = write ns s . L.fromStrict
    }
  