{-# LANGUAGE OverloadedStrings #-}

module Network.Socket.ByteStringSpec (main, spec) where

import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
import Control.Monad
import Data.Bits
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as C
import Data.Maybe
import Network.Socket
import Network.Socket.ByteString
import Network.Test.Common

import System.Environment

import Test.Hspec

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
    describe "send" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` testMsg
                client sock = send sock testMsg
            tcpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock = do
                    close sock
                    send sock testMsg `shouldThrow` anyException
            tcpTest client server

        it "checks -1 correctly on Windows" $ do
            sock <- socket AF_INET Stream defaultProtocol
            send sock "hello world" `shouldThrow` anyException

    describe "sendAll" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` testMsg
                client sock = sendAll sock testMsg
            tcpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock = do
                    close sock
                    sendAll sock testMsg `shouldThrow` anyException
            tcpTest client server

    describe "sendTo" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` testMsg
                client sock addr = sendTo sock testMsg addr
            udpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock addr = do
                    close sock
                    sendTo sock testMsg addr `shouldThrow` anyException
            udpTest client server

    describe "sendAllTo" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` testMsg
                client sock addr = sendAllTo sock testMsg addr
            udpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock addr = do
                    close sock
                    sendAllTo sock testMsg addr `shouldThrow` anyException
            udpTest client server

    describe "sendMany" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` S.append seg1 seg2
                client sock = sendMany sock [seg1, seg2]

                seg1 = C.pack "This is a "
                seg2 = C.pack "test message."
            tcpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock = do
                    close sock
                    sendMany sock [seg1, seg2] `shouldThrow` anyException

                seg1 = C.pack "This is a "
                seg2 = C.pack "test message."
            tcpTest client server

    describe "sendManyTo" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` S.append seg1 seg2
                client sock addr = sendManyTo sock [seg1, seg2] addr

                seg1 = C.pack "This is a "
                seg2 = C.pack "test message."
            udpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock addr = do
                    close sock
                    sendManyTo sock [seg1, seg2] addr `shouldThrow` anyException

                seg1 = C.pack "This is a "
                seg2 = C.pack "test message."
            udpTest client server

    describe "recv" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` testMsg
                client sock = send sock testMsg
            tcpTest client server

        it "throws when closed" $ do
            let server sock = do
                    close sock
                    recv sock 1024 `shouldThrow` anyException
                client sock = send sock testMsg
            tcpTest client server

        it "can treat overflow" $ do
            let server sock = do
                    seg1 <- recv sock (S.length testMsg - 3)
                    seg2 <- recv sock 1024
                    let msg = S.append seg1 seg2
                    msg `shouldBe` testMsg
                client sock = send sock testMsg
            tcpTest client server

        it "returns empty string at EOF" $ do
            let client s = recv s 4096 `shouldReturn` S.empty
                server s = shutdown s ShutdownSend
            tcpTest client server

        it "checks -1 correctly on Windows" $ do
            sock <- socket AF_INET Stream defaultProtocol
            recv sock 1024 `shouldThrow` anyException

    describe "recvFrom" $ do
        it "works well" $ do
            let server sock = do
                    (msg, _) <- recvFrom sock 1024
                    testMsg `shouldBe` msg
                client sock = do
                    addr <- getPeerName sock
                    sendTo sock testMsg addr
            tcpTest client server

        it "throws when closed" $ do
            let server sock = do
                    close sock
                    recvFrom sock 1024 `shouldThrow` anyException
                client sock = do
                    addr <- getPeerName sock
                    sendTo sock testMsg addr
            tcpTest client server

        it "can treat overflow" $ do
            let server sock = do
                    (seg1, _) <- recvFrom sock (S.length testMsg - 3)
                    (seg2, _) <- recvFrom sock 1024
                    let msg = S.append seg1 seg2
                    testMsg `shouldBe` msg
                client sock = send sock testMsg
            tcpTest client server

        it "returns empty string at EOF" $ do
            let server sock = do
                    (seg1, _) <- recvFrom sock (S.length testMsg - 3)
                    seg1 `shouldBe` S.empty
                client sock = shutdown sock ShutdownSend
            tcpTest client server

    describe "sendMsg" $ do
        it "works well" $ do
            let server sock = recv sock 1024 `shouldReturn` S.append seg1 seg2
                client sock addr = sendMsg sock addr [seg1, seg2] [] mempty

                seg1 = C.pack "This is a "
                seg2 = C.pack "test message."
            udpTest client server

        it "throws when closed" $ do
            let server _ = return ()
                client sock addr = do
                    close sock
                    sendMsg sock addr [seg1, seg2] [] mempty `shouldThrow` anyException

                seg1 = C.pack "This is a "
                seg2 = C.pack "test message."
            udpTest client server

    describe "recvMsg" $ do
        it "works well" $ do
            let server sock = do
                    (_, msg, cmsgs, flags) <- recvMsg sock 1024 0 mempty
                    msg `shouldBe` seg
                    cmsgs `shouldBe` []
                    flags `shouldBe` mempty
                client sock addr = sendTo sock seg addr

                seg = C.pack "This is a test message"
            udpTest client server

        it "receives truncated flag" $ do
            let server sock = do
                    (_, _, _, flags) <- recvMsg sock (S.length seg - 2) 0 mempty
                    flags .&. MSG_TRUNC `shouldBe` MSG_TRUNC
                client sock addr = sendTo sock seg addr

                seg = C.pack "This is a test message"
            udpTest client server

        it "peek" $ do
            let server sock = do
                    (_, msgs, _, _flags) <- recvMsg sock 1024 0 MSG_PEEK
                    -- flags .&. MSG_PEEK `shouldBe` MSG_PEEK -- Mac only
                    (_, msgs', _, _) <- recvMsg sock 1024 0 mempty
                    msgs `shouldBe` msgs'
                client sock addr = sendTo sock seg addr

                seg = C.pack "This is a test message"
            udpTest client server

        it "receives control messages for IPv4" $ do
            -- This test behaves strange on AppVeyor and I don't know why so skip
            -- TOS for now.
            isAppVeyor <- isJust <$> lookupEnv "APPVEYOR"

            -- Avoid race condition between the client sending the message and
            -- the server finishing its socket configuration.  Otherwise the
            -- message may be received with default socket options!
            serverReady <- newEmptyMVar

            let server sock = do
                    whenSupported RecvIPv4TTL $ setSocketOption sock RecvIPv4TTL 1
                    whenSupported RecvIPv4PktInfo $ setSocketOption sock RecvIPv4PktInfo 1
                    whenSupported RecvIPv4TOS $ setSocketOption sock RecvIPv4TOS 1
                    putMVar serverReady ()

                    (_, _, cmsgs, _) <- recvMsg sock 1024 128 mempty

                    whenSupported RecvIPv4PktInfo $
                        ((lookupCmsg CmsgIdIPv4PktInfo cmsgs >>= decodeCmsg) :: Maybe IPv4PktInfo)
                            `shouldNotBe` Nothing
                    when (not isAppVeyor) $ do
                        whenSupported RecvIPv4TTL $
                            ((lookupCmsg CmsgIdIPv4TTL cmsgs >>= decodeCmsg) :: Maybe IPv4TTL)
                                `shouldNotBe` Nothing
                        whenSupported RecvIPv4TOS $
                            ((lookupCmsg CmsgIdIPv4TOS cmsgs >>= decodeCmsg) :: Maybe IPv4TOS)
                                `shouldNotBe` Nothing
                client sock addr = takeMVar serverReady >> sendTo sock seg addr

                seg = C.pack "This is a test message"
            udpTest client server

        it "receives control messages for IPv6" $ do
            -- Avoid race condition between the client sending the message and
            -- the server finishing its socket configuration.  Otherwise the
            -- message may be received with default socket options!
            serverReady <- newEmptyMVar

            let server sock = do
                    whenSupported RecvIPv6HopLimit $ setSocketOption sock RecvIPv6HopLimit 1
                    whenSupported RecvIPv6TClass $ setSocketOption sock RecvIPv6TClass 1
                    whenSupported RecvIPv6PktInfo $ setSocketOption sock RecvIPv6PktInfo 1
                    putMVar serverReady ()

                    (_, _, cmsgs, _) <- recvMsg sock 1024 128 mempty

                    whenSupported RecvIPv6HopLimit $
                        ((lookupCmsg CmsgIdIPv6HopLimit cmsgs >>= decodeCmsg) :: Maybe IPv6HopLimit)
                            `shouldNotBe` Nothing
                    whenSupported RecvIPv6TClass $
                        ((lookupCmsg CmsgIdIPv6TClass cmsgs >>= decodeCmsg) :: Maybe IPv6TClass)
                            `shouldNotBe` Nothing
                    whenSupported RecvIPv6PktInfo $
                        ((lookupCmsg CmsgIdIPv6PktInfo cmsgs >>= decodeCmsg) :: Maybe IPv6PktInfo)
                            `shouldNotBe` Nothing
                client sock addr = takeMVar serverReady >> sendTo sock seg addr

                seg = C.pack "This is a test message"
            udpTest6 client server

        it "receives truncated control messages" $ do
            -- Avoid race condition between the client sending the message and
            -- the server finishing its socket configuration.  Otherwise the
            -- message may be received with default socket options!
            serverReady <- newEmptyMVar

            let server sock = do
                    whenSupported RecvIPv4TTL $ setSocketOption sock RecvIPv4TTL 1
                    whenSupported RecvIPv4TOS $ setSocketOption sock RecvIPv4TOS 1
                    whenSupported RecvIPv4PktInfo $ setSocketOption sock RecvIPv4PktInfo 1
                    putMVar serverReady ()

                    (_, _, _, flags) <- recvMsg sock 1024 10 mempty
                    flags .&. MSG_CTRUNC `shouldBe` MSG_CTRUNC

                client sock addr = takeMVar serverReady >> sendTo sock seg addr

                seg = C.pack "This is a test message"
            udpTest client server
