module Monky.Connectivity
( ConnHandle
, getConnH
, hasConn
)
where
import Data.Bits ((.|.))
import Control.Concurrent (threadWaitWrite, threadDelay, forkIO)
import Data.Word (Word64,Word32,Word16)
import Foreign.C.Error (getErrno, Errno(..), eINPROGRESS)
import Foreign.C.Types (CInt(..), CLong(..))
import Foreign.Marshal.Utils (with)
import Foreign.Marshal.Alloc (alloca, allocaBytes)
import Foreign.Ptr (Ptr, castPtr)
import Foreign.C.String (CString, withCString, peekCString)
import Foreign.C.Types (CChar)
import Foreign.Storable (Storable(..))
import System.Posix.Types (Fd(..))
import System.Timeout (timeout)
import Data.IORef (IORef, newIORef, writeIORef, readIORef)
import System.IO.Unsafe (unsafePerformIO)
newtype Port = Port Word16 deriving (Eq, Show)
newtype IP4 = IP4 Word32 deriving (Eq)
data Sockaddr = Socka Int Port IP4 deriving (Eq, Show)
foreign import ccall "socket" c_socket :: CInt -> CInt -> CInt -> IO CInt
foreign import ccall "close" c_close :: CInt -> IO ()
foreign import ccall "connect" c_connect :: CInt -> Ptr Sockaddr -> CInt -> IO CInt
foreign import ccall "inet_pton" c_pton :: CInt -> CString -> Ptr IP4 -> IO ()
foreign import ccall "inet_ntop" c_ntop :: CInt -> Ptr IP4 -> Ptr CChar -> Word64 -> IO (Ptr CChar)
foreign import ccall "htons" htons :: Word16 -> Word16
foreign import ccall "htonl" htonl :: Word32 -> Word32
foreign import ccall "ntohl" ntohl :: Word32 -> Word32
instance Storable Sockaddr where
sizeOf _ = (16)
alignment _ = alignment (undefined :: CLong)
peek p = do
fam <- (\hsc_ptr -> peekByteOff hsc_ptr 0) p
port <- (\hsc_ptr -> peekByteOff hsc_ptr 2) p
ip <- (\hsc_ptr -> peekByteOff hsc_ptr 4) p
return (Socka fam (Port port) (IP4 ip))
poke p (Socka fam (Port port) (IP4 ip)) = do
(\hsc_ptr -> pokeByteOff hsc_ptr 0) p fam
(\hsc_ptr -> pokeByteOff hsc_ptr 2) p port
(\hsc_ptr -> pokeByteOff hsc_ptr 4) p ip
instance Storable IP4 where
sizeOf _ = 4
alignment _ = alignment (undefined :: Word32)
peek p = fmap (IP4 . ntohl) . peek $ castPtr p
poke p (IP4 w) = poke (castPtr p) $ htonl w
instance Show IP4 where
show = showIP
showIPIO :: IP4 -> IO String
showIPIO ip = allocaBytes 16 (\str ->
with ip (\ptr -> c_ntop 2 ptr str 16) >> peekCString str)
showIP :: IP4 -> String
showIP ip = unsafePerformIO (showIPIO ip)
parseIPIO :: String -> IO IP4
parseIPIO xs =
withCString xs (\str -> do
alloca (\ptr -> c_pton 2 str ptr >> peek ptr))
parseIP :: String -> IP4
parseIP str = unsafePerformIO (parseIPIO str)
tryConn :: String -> Int -> IO Bool
tryConn ip port = do
socket <- c_socket 2 (1 .|. 2048) 0
_ <- with (Socka 2 (Port . htons $fromIntegral port) (parseIP ip))
(\ptr ->c_connect socket ptr (fromIntegral $sizeOf (undefined :: Sockaddr)))
(Errno con) <- getErrno
ret <- if (Errno con) == eINPROGRESS
then timeout (500 * 1000) (threadWaitWrite (Fd socket)) >>=
\case
Nothing -> return True
Just _ -> return False
else return False
c_close socket
return ret
data ConnHandle = ConnH String Int (IORef Bool)
hasConn :: ConnHandle -> IO Bool
hasConn (ConnH _ _ r) = readIORef r
updateLoop :: ConnHandle -> IO ()
updateLoop h@(ConnH ip port ref) = do
writeIORef ref =<< tryConn ip port
threadDelay (1000*1000)
updateLoop h
getConnH
:: String
-> Int
-> IO ConnHandle
getConnH ip port = do
ref <- newIORef False
let h = ConnH ip port ref
_ <- forkIO (updateLoop h)
return h