{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes       #-}
module Tox.Conduit.Network where

import           Control.Monad                      (void)
import           Control.Monad.IO.Class             (MonadIO, liftIO)
import           Data.Binary                        (Binary)
import qualified Data.ByteString                    as BS
import           Data.Conduit                       (ConduitT, await, yield)
import qualified Data.Conduit.List                  as CL
import qualified Network.Socket                     as Socket
import qualified Network.Socket.ByteString          as SocketBS

import           Tox.Network.Core.HostAddress       (HostAddress (..))
import           Tox.Network.Core.NodeInfo          (NodeInfo (..))
import           Tox.Network.Core.Packet            (Packet (..))
import           Tox.Network.Core.PortNumber        (PortNumber (..))
import           Tox.Network.Core.SocketAddress     (SocketAddress (..))
import           Tox.Network.Core.TransportProtocol (TransportProtocol (..))

-- | Convert Tox 'SocketAddress' to 'Socket.SockAddr'.
toSockAddr :: SocketAddress -> Socket.SockAddr
toSockAddr :: SocketAddress -> SockAddr
toSockAddr (SocketAddress (IPv4 HostAddress
addr) (PortNumber Word16
port)) =
    PortNumber -> HostAddress -> SockAddr
Socket.SockAddrInet (Word16 -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
port) HostAddress
addr
toSockAddr (SocketAddress (IPv6 HostAddress6
addr) (PortNumber Word16
port)) =
    PortNumber
-> HostAddress -> HostAddress6 -> HostAddress -> SockAddr
Socket.SockAddrInet6 (Word16 -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
port) HostAddress
0 HostAddress6
addr HostAddress
0

-- | Convert 'Socket.SockAddr' to Tox 'SocketAddress'.
fromSockAddr :: Socket.SockAddr -> Maybe SocketAddress
fromSockAddr :: SockAddr -> Maybe SocketAddress
fromSockAddr (Socket.SockAddrInet PortNumber
port HostAddress
addr) =
    SocketAddress -> Maybe SocketAddress
forall a. a -> Maybe a
Just (SocketAddress -> Maybe SocketAddress)
-> SocketAddress -> Maybe SocketAddress
forall a b. (a -> b) -> a -> b
$ HostAddress -> PortNumber -> SocketAddress
SocketAddress (HostAddress -> HostAddress
IPv4 HostAddress
addr) (PortNumber -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral PortNumber
port)
fromSockAddr (Socket.SockAddrInet6 PortNumber
port HostAddress
_ HostAddress6
addr HostAddress
_) =
    SocketAddress -> Maybe SocketAddress
forall a. a -> Maybe a
Just (SocketAddress -> Maybe SocketAddress)
-> SocketAddress -> Maybe SocketAddress
forall a b. (a -> b) -> a -> b
$ HostAddress -> PortNumber -> SocketAddress
SocketAddress (HostAddress6 -> HostAddress
IPv6 HostAddress6
addr) (PortNumber -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral PortNumber
port)
fromSockAddr SockAddr
_ = Maybe SocketAddress
forall a. Maybe a
Nothing

-- | A conduit source that reads from a UDP socket.
udpSource :: MonadIO m => Socket.Socket -> ConduitT i (Socket.SockAddr, BS.ByteString) m ()
udpSource :: Socket -> ConduitT i (SockAddr, ByteString) m ()
udpSource Socket
sock = do
    (ByteString
bs, SockAddr
addr) <- IO (ByteString, SockAddr)
-> ConduitT i (SockAddr, ByteString) m (ByteString, SockAddr)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ByteString, SockAddr)
 -> ConduitT i (SockAddr, ByteString) m (ByteString, SockAddr))
-> IO (ByteString, SockAddr)
-> ConduitT i (SockAddr, ByteString) m (ByteString, SockAddr)
forall a b. (a -> b) -> a -> b
$ Socket -> Int -> IO (ByteString, SockAddr)
SocketBS.recvFrom Socket
sock Int
4096
    (SockAddr, ByteString) -> ConduitT i (SockAddr, ByteString) m ()
forall (m :: * -> *) o i. Monad m => o -> ConduitT i o m ()
yield (SockAddr
addr, ByteString
bs)
    Socket -> ConduitT i (SockAddr, ByteString) m ()
forall (m :: * -> *) i.
MonadIO m =>
Socket -> ConduitT i (SockAddr, ByteString) m ()
udpSource Socket
sock

-- | A conduit sink that writes to a UDP socket.
udpSink :: MonadIO m => Socket.Socket -> ConduitT (Socket.SockAddr, BS.ByteString) o m ()
udpSink :: Socket -> ConduitT (SockAddr, ByteString) o m ()
udpSink Socket
sock = ((SockAddr, ByteString) -> m ())
-> ConduitT (SockAddr, ByteString) o m ()
forall (m :: * -> *) a o.
Monad m =>
(a -> m ()) -> ConduitT a o m ()
CL.mapM_ (((SockAddr, ByteString) -> m ())
 -> ConduitT (SockAddr, ByteString) o m ())
-> ((SockAddr, ByteString) -> m ())
-> ConduitT (SockAddr, ByteString) o m ()
forall a b. (a -> b) -> a -> b
$ \(SockAddr
addr, ByteString
bs) ->
    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ IO Int -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO Int -> IO ()) -> IO Int -> IO ()
forall a b. (a -> b) -> a -> b
$ Socket -> ByteString -> SockAddr -> IO Int
SocketBS.sendTo Socket
sock ByteString
bs SockAddr
addr