{-# LANGUAGE CPP #-}
-- |Routines for integrating Tor with the standard network library.
module Tor.NetworkStack.System(systemNetworkStack) where

import Data.Binary.Put
import Data.ByteString(ByteString)
import Data.ByteString.Lazy(toStrict)
import qualified Data.ByteString as BS
import Data.Word
import Network(listenOn, PortID(..))
import Network.BSD
import Network.Socket as Sys hiding (recv)
import Network.Socket.ByteString.Lazy(sendAll)
import qualified Network.Socket.ByteString as Sys
import Tor.DataFormat.TorAddress
import Tor.NetworkStack

-- |A Tor-compatible network stack that uses the 'network' library.
systemNetworkStack :: TorNetworkStack Socket Socket
systemNetworkStack = TorNetworkStack {
    Tor.NetworkStack.connect    = systemConnect
  , Tor.NetworkStack.getAddress = systemLookup
  , Tor.NetworkStack.listen     = systemListen
  , Tor.NetworkStack.accept     = systemAccept
  , Tor.NetworkStack.recv       = systemRead
  , Tor.NetworkStack.write      = sendAll
  , Tor.NetworkStack.flush      = const (return ())
  , Tor.NetworkStack.close      = Sys.close
  , Tor.NetworkStack.lclose     = Sys.close
  }

systemConnect :: String -> Word16 -> IO (Maybe Socket)
systemConnect addrStr port =
  do let ainfo = defaultHints { addrFamily = AF_INET, addrSocketType = Stream }
         hname = addrStr
         sname = show port
     addrinfos <- getAddrInfo (Just ainfo) (Just hname) (Just sname)
     case addrinfos of
       []    -> return Nothing
       (x:_) ->
         do sock <- socket AF_INET Stream defaultProtocol
            Sys.connect sock (addrAddress x)
            return (Just sock)

systemLookup :: String -> IO [TorAddress]
systemLookup hostname =
  -- FIXME: Tack the hostname on the end, as a default?
  do res <- getAddrInfo Nothing (Just hostname) Nothing
     return (map (convertAddress . addrAddress) res)

systemListen :: Word16 -> IO Socket
systemListen port = listenOn (PortNumber (fromIntegral port))

convertAddress :: SockAddr -> TorAddress
convertAddress (SockAddrInet _ x) =
  IP4 (ip4ToString (toStrict (runPut (putWord32be x))))
convertAddress (SockAddrInet6 _ _ (a,b,c,d) _) =
  IP6 (ip6ToString (toStrict (runPut (mapM_ putWord32be [a,b,c,d]))))
convertAddress x =
  error ("Incompatible address type: " ++ show x)

systemAccept :: Socket -> IO (Socket, TorAddress)
systemAccept lsock =
  do (res, addr) <- Sys.accept lsock
     return (res, convertAddress addr)

systemRead :: Socket -> Int -> IO ByteString
systemRead _    0   = return BS.empty
systemRead sock amt =
  do start <- Sys.recv sock (fromIntegral amt)
     let left = fromIntegral (amt - fromIntegral (BS.length start))
     if BS.null start
        then return BS.empty
        else (start `BS.append`) `fmap` systemRead sock left