packages feed

quic-0.3.5: Network/QUIC/Stream/Reass.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Network.QUIC.Stream.Reass (
    takeRecvStreamQwithSize,
    putRxStreamData,
    putRxCryptoData,
    FlowCntl (..),
    tryReassemble,
) where

import qualified Data.ByteString as BS
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq

import Network.QUIC.Imports
import Network.QUIC.Stream.Frag
import Network.QUIC.Stream.Misc
import Network.QUIC.Stream.Queue
import qualified Network.QUIC.Stream.Skew as Skew
import Network.QUIC.Stream.Types
import Network.QUIC.Types

----------------------------------------------------------------

getEndOfStream :: Stream -> IO Bool
getEndOfStream Stream{..} = readIORef $ endOfStream streamRecvQ

setEndOfStream :: Stream -> IO ()
setEndOfStream Stream{..} = writeIORef (endOfStream streamRecvQ) True

readPendingData :: Stream -> IO (Maybe ByteString)
readPendingData Stream{..} = readIORef $ pendingData streamRecvQ

writePendingData :: Stream -> ByteString -> IO ()
writePendingData Stream{..} bs = writeIORef (pendingData streamRecvQ) $ Just bs

clearPendingData :: Stream -> IO ()
clearPendingData Stream{..} = writeIORef (pendingData streamRecvQ) Nothing

----------------------------------------------------------------

takeRecvStreamQwithSize
    :: Stream
    -> Int
    -- ^ Number of bytes to receive.
    -> IO ByteString
takeRecvStreamQwithSize strm siz0 = do
    eos <- getEndOfStream strm
    if eos
        then return ""
        else do
            mb <- readPendingData strm
            case mb of
                Nothing -> do
                    b0 <- takeRecvStreamQ strm
                    if b0 == ""
                        then do
                            setEndOfStream strm
                            return ""
                        else handleBytes b0
                Just b0 -> do
                    clearPendingData strm
                    handleBytes b0
  where
    handleBytes b0 =
        let len = BS.length b0
         in case len `compare` siz0 of
                LT -> tryRead (siz0 - len) (b0 :)
                EQ -> return b0
                GT -> do
                    let (b1, b2) = BS.splitAt siz0 b0
                    writePendingData strm b2
                    return b1
    tryRead siz build = do
        mb <- tryTakeRecvStreamQ strm
        case mb of
            Nothing -> return $ BS.concat $ build []
            Just b -> do
                if b == ""
                    then do
                        setEndOfStream strm
                        return $ BS.concat $ build []
                    else do
                        let len = BS.length b
                        case len `compare` siz of
                            LT -> tryRead (siz - len) (build . (b :))
                            EQ -> return $ BS.concat $ build [b]
                            GT -> do
                                let (b1, b2) = BS.splitAt siz b
                                writePendingData strm b2
                                return $ BS.concat $ build [b1]

----------------------------------------------------------------
----------------------------------------------------------------

data FlowCntl
    = -- | Past the octets the peer is allowed to have outstanding.
      OverLimit
    | -- | Past the number of separate pieces we will hold for one stream.
      TooFragmented
    | Duplicated
    | Reassembled
    deriving (Eq, Show)

putRxStreamData :: Stream -> RxStreamData -> IO FlowCntl
putRxStreamData s rx@(RxStreamData _ off len _) = do
    lim <- getRxMaxStreamData s
    if len + off > lim
        then return OverLimit
        else do
            tryReassemble s rx put putFin
  where
    put "" = return ()
    put d = do
        addRxStreamData s $ BS.length d
        putRecvStreamQ s d
    putFin = putRecvStreamQ s ""

-- | Feed a CRYPTO frame to the reassembly of its stream, refusing anything
--   that would leave us holding more than @lim@ octets past the point the
--   stream has reached in order.
--
-- CRYPTO frames sit outside the flow control that bounds stream data -- they
-- have to, since they carry the handshake that settles those limits -- so
-- this is the only thing standing between a peer and an unbounded pile of
-- fragments at scattered offsets.  Bounding the window bounds the pile: every
-- fragment we keep lies within it.
putRxCryptoData
    :: Stream -> Int -> RxStreamData -> (StreamData -> IO ()) -> IO FlowCntl
putRxCryptoData s lim rx@(RxStreamData _ off len _) put = do
    StreamState off0 _ <- readIORef $ streamStateRx s
    if off + len > off0 + lim
        then return OverLimit
        else tryReassemble s rx put (return ())

-- fin of StreamState off fin means see-fin-already.
tryReassemble
    :: Stream -> RxStreamData -> (StreamData -> IO ()) -> IO () -> IO FlowCntl
tryReassemble Stream{} (RxStreamData "" _ _ False) _ _ = return Duplicated
tryReassemble Stream{..} x@(RxStreamData "" off _ True) _ putFin = do
    si0@(StreamState off0 fin0) <- readIORef streamStateRx
    let si1 = si0{streamFin = True}
    if fin0
        then do
            -- stdoutLogger "Illegal Fin" -- fixme
            return Duplicated
        else case off `compare` off0 of
            LT -> return Duplicated
            EQ -> do
                writeIORef streamStateRx si1
                putFin
                return Reassembled
            GT -> do
                writeIORef streamStateRx si1
                hold streamReass x
tryReassemble Stream{..} x@(RxStreamData dat off len False) put putFin = do
    si0@(StreamState off0 _) <- readIORef streamStateRx
    case off `compare` off0 of
        LT -> return Duplicated
        EQ -> do
            put dat
            loop si0 (off0 + len)
            return Reassembled
        GT -> hold streamReass x
  where
    loop si0 xff = do
        mrxs <- atomicModifyIORef' streamReass $ \(n, sk) ->
            let (sk', mrxs) = Skew.deleteMinIf xff sk
             in ((n - maybe 0 length mrxs, sk'), mrxs)
        case mrxs of
            Nothing -> writeIORef streamStateRx si0{streamOffset = xff}
            Just rxs -> do
                mapM_ (put . rxstrmData) rxs
                let xff1 = nextOff rxs
                if hasFin rxs
                    then do
                        putFin
                    else do
                        loop si0 xff1
tryReassemble Stream{..} x@(RxStreamData dat off len True) put putFin = do
    si0@(StreamState off0 fin0) <- readIORef streamStateRx
    let si1 = si0{streamFin = True}
    if fin0
        then return Duplicated
        else case off `compare` off0 of
            LT -> return Duplicated
            EQ -> do
                let off1 = off0 + len
                writeIORef streamStateRx si1{streamOffset = off1}
                put dat
                putFin
                return Reassembled
            GT -> do
                writeIORef streamStateRx si1
                hold streamReass x

-- | Keep a fragment that cannot be delivered yet, unless we are already
--   holding as many as we are willing to.
--
-- Flow control bounds the octets, not the pieces, and a peer that sends its
-- window one octet at a time at scattered offsets pays for the octets while
-- we pay for the pieces.
hold :: IORef (Int, Skew.Skew RxStreamData) -> RxStreamData -> IO FlowCntl
hold ref x = atomicModifyIORef' ref $ \st@(n, sk) ->
    if n >= maxReassFragments
        then (st, TooFragmented)
        else ((n + 1, Skew.insert x sk), Reassembled)

hasFin :: Seq RxStreamData -> Bool
hasFin s = case Seq.viewr s of
    Seq.EmptyR -> False
    _ Seq.:> x -> rxstrmFin x