packages feed

sockets-0.6.1.0: test/Main.hs

{-# language BangPatterns #-}
{-# language DeriveAnyClass #-}
{-# language DerivingStrategies #-}
{-# language LambdaCase #-}
{-# language MagicHash #-}
{-# language ScopedTypeVariables #-}
{-# language TypeFamilies #-}
{-# language UnboxedTuples #-}

import Control.Applicative (liftA3)
import Control.Concurrent (forkIO)
import Control.Concurrent.STM (newTVarIO)
import Control.Concurrent.Async (concurrently)
import Control.Monad (replicateM_)
import Control.Exception (Exception)
import Control.Exception (throwIO)
import Control.Monad (when)
import Control.Monad.ST (runST)
import Data.ByteString (ByteString)
import Data.Bytes.Types (Bytes(..),MutableBytes(..),UnmanagedBytes(..))
import Data.Primitive (ByteArray,MutableByteArray(..))
import Data.Primitive.Addr (Addr(..))
import Data.Primitive.Unlifted.Array (UnliftedArray)
import Data.Word (Word16,Word8)
import GHC.Exts (RealWorld)
import GHC.IO (IO(..))
import System.Exit (exitFailure)
import System.IO (stderr,hPutStrLn)
import Test.Tasty
import Test.Tasty.HUnit

import qualified Data.Bytes as Bytes
import qualified Data.ByteString as B
import qualified Data.Primitive as PM
import qualified Data.Primitive.Unlifted.Array as PM
import qualified Data.Primitive.MVar as PM
import qualified GHC.Exts as E
import qualified Net.IPv4 as IPv4
import qualified Socket.SequencedPacket.Unix as SP
import qualified Socket.SequencedPacket.Uninterruptible.Bytes as SP
import qualified Socket.Datagram.Unix.Connected as DUC
import qualified Socket.Datagram.IPv4.Unconnected as DIU
import qualified Socket.Datagram.IPv4.Connected as DIC
import qualified Socket.Datagram.Uninterruptible.Bytes as DUB
import qualified Socket.Datagram.Interruptible.Bytes as DIB
import qualified Socket.Stream.IPv4 as SI
import qualified Socket.Stream.Uninterruptible.Bytes as UB
import qualified Socket.Stream.Uninterruptible.MutableBytes as UMB
import qualified Socket.Stream.Uninterruptible.ByteString as UBS
import qualified Socket.Stream.Uninterruptible.Hybrid as UHYB

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests = testGroup "socket"
  [ testGroup "datagram"
    [ testGroup "ipv4"
      [ testGroup "unconnected"
        [ testCase "A" testDatagramUndestinedA
        , testCase "B" testDatagramUndestinedB
        , testCase "C" testDatagramUndestinedC
        , testCase "D" testDatagramUndestinedD
        , testCase "E" testDatagramUndestinedE
        , testCase "F" testDatagramUndestinedF
        , testCase "G" testDatagramUndestinedG
        ]
      , testGroup "connected"
        [ testCase "A" testDatagramConnectedA
        , testCase "B" testDatagramConnectedB
        ]
      ]
    , testGroup "unix"
      [ testGroup "connected"
        [ testCase "C" testDatagramConnectedC
        ]
      ]
    ]
  , testGroup "seqpacket"
    [ testGroup "unix"
      [ testCase "A" testSeqpacketA
      , testCase "B" testSeqpacketB
      ]
    ]
  , testGroup "stream"
    [ testGroup "ipv4"
      [ testCase "A" testStreamA
      , testGroup "B"
        [ testCase "1MB" (testStreamB 1)
        , testCase "4MB" (testStreamB 4)
        , testCase "32MB" (testStreamB 32)
        ]
      , testCase "C" testStreamC
      , testCase "D" testStreamD
      , testCase "E" testStreamE
      , testGroup "F"
        [ testCase "3KB" (testStreamF 1)
        , testCase "3MB" (testStreamF (1 * 1024))
        , testCase "12MB" (testStreamF (4 * 1024))
        , testCase "48MB" (testStreamF (16 * 1024))
        ]
      , testCase "G" testStreamG
      , testCase "H" testStreamH
      ]
    ]
  ]

unsliced :: ByteArray -> Bytes
unsliced arr = Bytes arr 0 (PM.sizeofByteArray arr)

unhandled :: Exception e => IO (Either e a) -> IO a
unhandled action = action >>= either throwIO pure

unhandledClose :: Either SI.CloseException () -> a -> IO a
unhandledClose m a = case m of
  Right () -> pure a
  Left e -> throwIO e

data MagicByteMismatch = MagicByteMismatch
  deriving stock (Show,Eq)
  deriving anyclass (Exception)

data NegativeByteCount = NegativeByteCount
  deriving stock (Show,Eq)
  deriving anyclass (Exception)

testDatagramConnectedA :: Assertion
testDatagramConnectedA = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  (port,received) <- concurrently (sender m) (receiver m)
  received @=? DIU.Message (DIU.Peer IPv4.loopback port) message
  where
  message = E.fromList [0,1,2,3] :: ByteArray
  sz = PM.sizeofByteArray message
  sender :: PM.MVar RealWorld Word16 -> IO Word16
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ DIC.withSocket (DUB.Peer IPv4.loopback 0) (DUB.Peer IPv4.loopback dstPort) $ \sock srcPort -> do
      unhandled $ DUB.send sock (unsliced message)
      pure srcPort
  receiver :: PM.MVar RealWorld Word16 -> IO DIU.Message
  receiver m = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    unhandled $ DUB.receiveFromIPv4 sock sz

testDatagramConnectedB :: Assertion
testDatagramConnectedB = do
  intr <- newTVarIO True
  unhandled $ DIC.withSocket
    (DIU.Peer IPv4.loopback 0)
    (DIU.Peer IPv4.loopback 43245)
    (\sock _ -> DIB.receive intr sock 42 >>= \case
      Left DIC.ReceiveInterrupted -> pure ()
      Left e -> throwIO e
      Right _ -> fail "testDatagramUndestinedB: received datagram"
    )

-- This checks that unix-domain datagram socketpair works.
testDatagramConnectedC :: Assertion
testDatagramConnectedC = do
  ((),actual) <- either throwIO pure =<< DUC.withPair
    (\src dst -> concurrently
      ( unhandled $ DUB.send src (unsliced message) )
      ( unhandled $ DUB.receive dst sz )
    )
  message @=? actual
  where
  message = E.fromList [0,1,2,3] :: ByteArray
  sz = PM.sizeofByteArray message

-- This checks that unix-domain seqpacket socketpair works.
testSeqpacketA :: Assertion
testSeqpacketA = do
  ((),actual) <- either throwIO pure =<< SP.withPair
    (\src dst -> concurrently
      ( unhandled $ SP.send src (unsliced message) )
      ( unhandled $ SP.receive dst (sz + 1) )
    )
  message @=? actual
  where
  message = E.fromList [0,1,2,3] :: ByteArray
  sz = PM.sizeofByteArray message

testSeqpacketB :: Assertion
testSeqpacketB = do
  (m :: PM.MVar RealWorld ()) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  received @=? message
  where
  -- Socket in abstract namespace
  addr = DUC.UnixAddress (E.fromList [0,97,98,99,100,101])
  message = E.fromList (enumFromTo 0 (100 :: Word8)) :: ByteArray
  sender :: PM.MVar RealWorld () -> IO ()
  sender m = do
    PM.takeMVar m
    unhandled $ SP.withConnection addr unhandledClose $ \conn -> do
      unhandled $ SP.send conn (Bytes.fromByteArray message)
  receiver :: PM.MVar RealWorld () -> IO ByteArray
  receiver m = unhandled $ SP.withListener addr $ \listener -> do
    PM.putMVar m ()
    unhandled $ SP.withAccepted listener unhandledClose $ \conn -> do
      unhandled $ SP.receive conn 256

testDatagramUndestinedA :: Assertion
testDatagramUndestinedA = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  (port,received) <- concurrently (sender m) (receiver m)
  received @=? DIU.Message (DIU.Peer IPv4.loopback port) message
  where
  message = E.fromList [0,1,2,3] :: ByteArray
  sz = PM.sizeofByteArray message
  sender :: PM.MVar RealWorld Word16 -> IO Word16
  sender m = unhandled $ DIU.withSocket (DUB.Peer IPv4.loopback 0) $ \sock srcPort -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message)
    pure srcPort
  receiver :: PM.MVar RealWorld Word16 -> IO DIU.Message
  receiver m = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    unhandled $ DUB.receiveFromIPv4 sock sz

testDatagramUndestinedB :: Assertion
testDatagramUndestinedB = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  (n :: PM.MVar RealWorld ()) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m n) (receiver m n)
  let expected = (message1,message2)
  expected @=? received
  where
  message1 = E.fromList [0..3] :: ByteArray
  message2 = E.fromList [4..11] :: ByteArray
  sz1 = PM.sizeofByteArray message1
  sz2 = PM.sizeofByteArray message2
  sender :: PM.MVar RealWorld Word16 -> PM.MVar RealWorld () -> IO ()
  sender m n = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock _ -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message1)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message2)
    PM.putMVar n ()
  receiver :: PM.MVar RealWorld Word16 -> PM.MVar RealWorld () -> IO (ByteArray,ByteArray)
  receiver m n = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    PM.takeMVar n
    slab <- DUB.newPeerlessSlab 2
    let sz = max sz1 sz2
    msgs <- unhandled $ DUB.receiveMany sock slab sz
    let msgCount = PM.sizeofUnliftedArray msgs
    if msgCount == 2
      then pure (PM.indexUnliftedArray msgs 0, PM.indexUnliftedArray msgs 1)
      else fail ("received a number of messages other than 2: " ++ show msgCount)
      
testDatagramUndestinedC :: Assertion
testDatagramUndestinedC = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  (n :: PM.MVar RealWorld ()) <- PM.newEmptyMVar
  (port,received) <- concurrently (sender m n) (receiver m n)
  let expected = 
        ( DIU.Message (DIU.Peer IPv4.loopback port) message1
        , DIU.Message (DIU.Peer IPv4.loopback port) message2
        , DIU.Message (DIU.Peer IPv4.loopback port) message3
        , DIU.Message (DIU.Peer IPv4.loopback port) message4
        )
  expected @=? received
  where
  message1 = E.fromList (enumFromTo 0 9):: ByteArray
  message2 = E.fromList (enumFromTo 10 12) :: ByteArray
  message3 = E.fromList (enumFromTo 13 27) :: ByteArray
  message4 = E.fromList (enumFromTo 28 31) :: ByteArray
  sz1 = PM.sizeofByteArray message1
  sz2 = PM.sizeofByteArray message2
  sz3 = PM.sizeofByteArray message3
  sz4 = PM.sizeofByteArray message4
  sender :: PM.MVar RealWorld Word16 -> PM.MVar RealWorld () -> IO Word16
  sender m n = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock srcPort -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message1)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message2)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message3)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message4)
    PM.putMVar n ()
    pure srcPort
  receiver :: PM.MVar RealWorld Word16 -> PM.MVar RealWorld ()
           -> IO (DIU.Message,DIU.Message,DIU.Message,DIU.Message)
  receiver m n = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    PM.takeMVar n
    let sz = max sz1 (max sz2 (max sz3 sz4))
    slab <- DUB.newIPv4Slab 3
    msgsX <- unhandled $ DUB.receiveManyFromIPv4 sock slab sz
    let msgCountX = PM.sizeofSmallArray msgsX
    (msg1,msg2,msg3) <- if PM.sizeofSmallArray msgsX == 3
      then pure (PM.indexSmallArray msgsX 0, PM.indexSmallArray msgsX 1, PM.indexSmallArray msgsX 2)
      else fail $ "received a number of messages other than 3: " ++ show msgCountX
    msgsY <- unhandled $ DUB.receiveManyFromIPv4 sock slab sz
    let msgCountY = PM.sizeofSmallArray msgsY
    msg4 <- if msgCountY == 1
      then pure (PM.indexSmallArray msgsY 0)
      else fail $ "received a number of messages other than 1: " ++ show msgCountY
    pure (msg1,msg2,msg3,msg4)

testDatagramUndestinedD :: Assertion
testDatagramUndestinedD = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  let expected = (message1,message2,message3)
  expected @=? received
  where
  message1 = E.fromList (enumFromTo 0 15):: ByteArray
  message2 = E.fromList (enumFromTo 16 18) :: ByteArray
  message3 = E.fromList (enumFromTo 19 23) :: ByteArray
  sz1 = PM.sizeofByteArray message1
  sz2 = PM.sizeofByteArray message2
  sz3 = PM.sizeofByteArray message3
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = unhandled $ DIU.withSocket (DUB.Peer IPv4.loopback 0) $ \sock _ -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message1)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message2)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message3)
  receiver :: PM.MVar RealWorld Word16 -> IO (ByteArray,ByteArray,ByteArray)
  receiver m = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    liftA3 (,,)
      (unhandled $ DUB.receive sock sz1)
      (unhandled $ DUB.receive sock sz2)
      (unhandled $ DUB.receive sock sz3)

testDatagramUndestinedE :: Assertion
testDatagramUndestinedE = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  (n :: PM.MVar RealWorld ()) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m n) (receiver m n)
  let expected = (message1,message2,message3)
  expected @=? received
  where
  message1 = E.fromList (enumFromTo 0 9):: ByteArray
  message2 = E.fromList (enumFromTo 10 14) :: ByteArray
  message3 = E.fromList (enumFromTo 15 21) :: ByteArray
  sz1 = PM.sizeofByteArray message1
  sz2 = PM.sizeofByteArray message2
  sz3 = PM.sizeofByteArray message3
  sender :: PM.MVar RealWorld Word16 -> PM.MVar RealWorld () -> IO ()
  sender !m !n = unhandled $ DIU.withSocket (DUB.Peer IPv4.loopback 0) $ \sock _ -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message1)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message2)
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message3)
    PM.putMVar n ()
  receiver :: PM.MVar RealWorld Word16 -> PM.MVar RealWorld () -> IO (ByteArray,ByteArray,ByteArray)
  receiver m n = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    PM.takeMVar n
    slab <- DUB.newPeerlessSlab 1
    let sz = max sz1 (max sz2 sz3)
    msgsX <- unhandled $ DUB.receiveMany sock slab sz
    when (PM.sizeofUnliftedArray msgsX /= 1) $ fail "more than one message for X"
    msgsY <- unhandled $ DUB.receiveMany sock slab sz
    when (PM.sizeofUnliftedArray msgsX /= 1) $ fail "more than one message for Y"
    msgsZ <- unhandled $ DUB.receiveMany sock slab sz
    when (PM.sizeofUnliftedArray msgsX /= 1) $ fail "more than one message for Z"
    pure (PM.indexUnliftedArray msgsX 0,PM.indexUnliftedArray msgsY 0,PM.indexUnliftedArray msgsZ 0)

testDatagramUndestinedF :: Assertion
testDatagramUndestinedF = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  let expected = (Left (DUB.ReceiveTruncated sz))
  expected @=? received
  where
  message = E.fromList (enumFromTo 0 11):: ByteArray
  sz = PM.sizeofByteArray message
  sender !m = unhandled $ DIU.withSocket (DUB.Peer IPv4.loopback 0) $ \sock _ -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message)
  receiver m = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    slab <- DUB.newPeerlessSlab 1
    DUB.receiveMany sock slab (sz - 1)

testDatagramUndestinedG :: Assertion
testDatagramUndestinedG = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  received @=? message
  where
  message = E.fromList [0,1,2,3] :: ByteArray
  sz = PM.sizeofByteArray message
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = unhandled $ DIU.withSocket (DUB.Peer IPv4.loopback 0) $ \sock _ -> do
    dstPort <- PM.takeMVar m
    unhandled $ DUB.sendToIPv4 sock (DIU.Peer IPv4.loopback dstPort) (unsliced message)
  receiver :: PM.MVar RealWorld Word16 -> IO ByteArray
  receiver m = unhandled $ DIU.withSocket (DIU.Peer IPv4.loopback 0) $ \sock port -> do
    PM.putMVar m port
    unhandled $ DUB.receive sock sz

-- This test involves a made up protocol that goes like this:
-- The sender always starts by sending the length of the rest
-- of the payload as a native-endian encoded machine-sized int.
-- (This could only ever work for a machine that is communicating
-- with itself). Then, it sends a bytearray of that specified
-- length. Then, both ends are expected to shutdown their sides
-- of the connection.
testStreamA :: Assertion
testStreamA = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  received @=? message
  where
  message = E.fromList (enumFromTo 0 (100 :: Word8)) :: ByteArray
  sz = PM.sizeofByteArray message
  szb = runST $ do
    marr <- PM.newByteArray (PM.sizeOf (undefined :: Int))
    PM.writeByteArray marr 0 sz
    PM.unsafeFreezeByteArray marr
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
      unhandled $ UB.send conn (unsliced szb)
      unhandled $ UB.send conn (unsliced message)
  receiver :: PM.MVar RealWorld Word16 -> IO ByteArray
  receiver m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
      serializedSize <- unhandled $ UB.receiveExactly conn (PM.sizeOf (undefined :: Int))
      let theSize = PM.indexByteArray serializedSize 0 :: Int
      result <- unhandled $ UB.receiveExactly conn theSize
      pure result

testStreamC :: Assertion
testStreamC = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  received @=? message
  where
  message = B.pack (enumFromTo 0 (100 :: Word8)) :: ByteString
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
      unhandled $ UBS.send conn message
  receiver :: PM.MVar RealWorld Word16 -> IO ByteString
  receiver m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
      unhandled $ UBS.receiveExactly conn (B.length message)

-- This is intended to test creating sockets after other sockets have been
-- closed.
testStreamD :: Assertion
testStreamD = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),()) <- concurrently (server m) (client m)
  pure ()
  where
  message = E.fromList (replicate 50000 (0 :: Word8)) :: ByteArray
  totalConns = 20
  simultaneousClients = 5
  client :: PM.MVar RealWorld Word16 -> IO ()
  client m = do
    dstPort <- PM.takeMVar m
    counter <- PM.newEmptyMVar
    replicateM_ simultaneousClients $ forkIO $ do
      replicateM_ (div totalConns simultaneousClients) $ do
        _ <- unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
          let go = UB.send conn (unsliced message) >>= \case
                Left SI.SendShutdown -> pure ()
                Left e -> throwIO e
                Right () -> go
          go
        pure ()
      PM.putMVar counter ()
    replicateM_ simultaneousClients $ PM.takeMVar counter
  server :: PM.MVar RealWorld Word16 -> IO ()
  server m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    counter <- PM.newEmptyMVar
    replicateM_ totalConns $ do
      SI.forkAcceptedUnmasked listener
        (\e () -> case e of
          Left SI.ClosePeerContinuedSending -> PM.putMVar counter ()
          Right () -> fail "testStreamD: unexpected behavior"
        )
        (\conn _ -> do
          _ <- unhandled $ UB.receiveExactly conn 1
          pure ()
        )
    replicateM_ totalConns $ PM.takeMVar counter


-- The sender sends a large amount of traffic that may exceed
-- the size of the operating system's TCP send buffer. The 
-- amount is configurable because the test suite wants to
-- check this for several values.
testStreamB :: Int -> Assertion
testStreamB megabytes = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),()) <- concurrently (sender m) (receiver m)
  pure ()
  where
  message = E.fromList (replicate (32 * 1024) magicByte) :: ByteArray
  chunkSize = PM.sizeofByteArray message
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
      replicateM_ (32 * megabytes) $ unhandled $ UB.send conn (unsliced message)
  receiver :: PM.MVar RealWorld Word16 -> IO ()
  receiver m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
      buffer <- PM.newByteArray chunkSize
      let receiveLoop !remaining
            | remaining > 0 = do
                let recvSize = min remaining chunkSize
                PM.setByteArray buffer 0 chunkSize (0 :: Word8)
                bytesReceived <- unhandled
                  (UMB.receiveOnce conn (MutableBytes buffer 0 recvSize))
                verifyClientSendBytes buffer bytesReceived >>= \case
                  True -> receiveLoop (remaining - bytesReceived)
                  False -> throwIO MagicByteMismatch
            | remaining == 0 = pure ()
            | otherwise = throwIO NegativeByteCount
      receiveLoop (32 * megabytes * chunkSize)
      pure ()

magicByte :: Word8
magicByte = 0xFA

verifyClientSendBytes :: PM.MutableByteArray RealWorld -> Int -> IO Bool
verifyClientSendBytes arr len = go (len - 1)
  where
  go !ix = if ix >= 0
    then do
      w <- PM.readByteArray arr ix
      if w == magicByte then go (ix - 1) else pure False
    else pure True

-- This tests that hybrid sending works correctly.
testStreamF :: Int -> Assertion
testStreamF megabytes = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  expectedMut <- PM.newByteArray (3 * payloadBytes)
  PM.setByteArray expectedMut 0 (2 * payloadBytes) (0xE6 :: Word8)
  PM.setByteArray expectedMut (2 * payloadBytes) payloadBytes (0xB4 :: Word8)
  expected <- PM.unsafeFreezeByteArray expectedMut
  let lenExp = PM.sizeofByteArray expected
  let lenRec = PM.sizeofByteArray received
  when (lenExp /= lenRec) $ assertFailure $
    "Incorrect result: expected length " ++
    show lenExp ++ " but got " ++ show lenRec
  when (expected /= received) $ do
    let ix = differentByte expected received lenExp
    assertFailure ("Incorrect result differing at byte " ++ show ix)
  where
  payloadBytes = megabytes * 1024
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    let peer = DIU.Peer IPv4.loopback dstPort
    bufA <- PM.newByteArray (2 * payloadBytes)
    PM.setByteArray bufA 0 (2 * payloadBytes) (0xE6 :: Word8)
    bufB <- PM.newPinnedByteArray payloadBytes
    PM.setByteArray bufB 0 payloadBytes (0xB4 :: Word8)
    unhandled $ SI.withConnection peer unhandledClose $ \conn -> do
      unhandled $ UHYB.sendMutableBytesUnmanagedBytes conn
        (MutableBytes bufA 0 (payloadBytes * 2))
        (UnmanagedBytes (ptrToAddr (PM.mutableByteArrayContents bufB)) payloadBytes)
      touchMutableByteArray bufB
  receiver :: PM.MVar RealWorld Word16 -> IO ByteArray
  receiver m = do
    let peer = (SI.Peer IPv4.loopback 0)
    unhandled $ SI.withListener peer $ \listener port -> do
      PM.putMVar m port
      unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
        unhandled $ UB.receiveExactly conn (payloadBytes * 3)
      
testStreamE :: Assertion
testStreamE = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  received @=? (mconcat [messageA, messageB, messageC, messageD])
  where
  messageA = E.fromList [0..17] :: ByteArray
  messageB = E.fromList [18..92] :: ByteArray
  messageC = E.fromList [93..182] :: ByteArray
  messageD = E.fromList [183..255] :: ByteArray
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
      unhandled $ UB.send conn (unsliced messageA)
      unhandled $ UB.send conn (unsliced messageB)
      unhandled $ UB.send conn (unsliced messageC)
      unhandled $ UB.send conn (unsliced messageD)
  receiver :: PM.MVar RealWorld Word16 -> IO ByteArray
  receiver m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
      marr <- PM.newByteArray 256
      x <- unhandled $ UMB.receiveBetween conn (MutableBytes marr 0 60) 20
      y <- unhandled $ UMB.receiveBetween conn (MutableBytes marr x 150) 100
      unhandled $ UMB.receiveExactly conn (MutableBytes marr (x + y) (256 - (x + y)))
      PM.unsafeFreezeByteArray marr

testStreamG :: Assertion
testStreamG = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),received) <- concurrently (sender m) (receiver m)
  received @=? (mconcat [messageA, messageB, messageC, messageD])
  where
  messageA = E.fromList [0..17] :: ByteArray
  messageB = E.fromList [18..92] :: ByteArray
  messageC = E.fromList [93..182] :: ByteArray
  messageD = E.fromList [183..255] :: ByteArray
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
      let msgs = E.fromList [messageA,messageB,messageC,messageD]
      unhandled $ UB.sendMany conn msgs
  receiver :: PM.MVar RealWorld Word16 -> IO ByteArray
  receiver m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
      marr <- PM.newByteArray 256
      x <- unhandled $ UMB.receiveBetween conn (MutableBytes marr 0 60) 20
      y <- unhandled $ UMB.receiveBetween conn (MutableBytes marr x 150) 100
      unhandled $ UMB.receiveExactly conn (MutableBytes marr (x + y) (256 - (x + y)))
      PM.unsafeFreezeByteArray marr

-- The sender sends a large amount of traffic that may exceed
-- the size of the operating system's TCP send buffer, currently
-- 32MB. This uses sendmsg.
testStreamH :: Assertion
testStreamH = do
  (m :: PM.MVar RealWorld Word16) <- PM.newEmptyMVar
  ((),()) <- concurrently (sender m) (receiver m)
  pure ()
  where
  recvChunkSize = 32 * 1024
  message = E.fromList (replicate (1024 * 256) magicByte) :: ByteArray
  messages = E.fromList (replicate 128 message) :: UnliftedArray ByteArray
  sender :: PM.MVar RealWorld Word16 -> IO ()
  sender m = do
    dstPort <- PM.takeMVar m
    unhandled $ SI.withConnection (DIU.Peer IPv4.loopback dstPort) unhandledClose $ \conn -> do
      unhandled $ UB.sendMany conn messages
  receiver :: PM.MVar RealWorld Word16 -> IO ()
  receiver m = unhandled $ SI.withListener (SI.Peer IPv4.loopback 0) $ \listener port -> do
    PM.putMVar m port
    unhandled $ SI.withAccepted listener unhandledClose $ \conn _ -> do
      buffer <- PM.newByteArray recvChunkSize
      let receiveLoop !remaining
            | remaining > 0 = do
                let recvSize = min remaining recvChunkSize
                PM.setByteArray buffer 0 recvChunkSize (0 :: Word8)
                bytesReceived <- unhandled
                  (UMB.receiveOnce conn (MutableBytes buffer 0 recvSize))
                verifyClientSendBytes buffer bytesReceived >>= \case
                  True -> receiveLoop (remaining - bytesReceived)
                  False -> throwIO MagicByteMismatch
            | remaining == 0 = pure ()
            | otherwise = throwIO NegativeByteCount
      receiveLoop (32 * 1024 * 1024)
      pure ()

touchMutableByteArray :: MutableByteArray RealWorld -> IO ()
touchMutableByteArray (MutableByteArray x) = touchMutableByteArray# x

touchMutableByteArray# :: E.MutableByteArray# RealWorld -> IO ()
touchMutableByteArray# x = IO $ \s -> case E.touch# x s of s' -> (# s', () #)

ptrToAddr :: E.Ptr a -> Addr
ptrToAddr (E.Ptr x) = Addr x

differentByte :: ByteArray -> ByteArray -> Int -> Int
differentByte a b len = go 0 where
  go !ix = if ix < len
    then if PM.indexByteArray a ix == (PM.indexByteArray b ix :: Word8)
      then go (ix + 1)
      else ix
    else len