packages feed

http3-0.0.10: util/qif.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Main where

import Conduit hiding (yield)
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import Data.Attoparsec.ByteString (Parser)
import qualified Data.Attoparsec.ByteString as P
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.Conduit.Attoparsec
import System.Environment
import System.IO
import qualified UnliftIO.Exception as E

import Network.QPACK

data Block = Block Int ByteString deriving (Show)

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

main :: IO ()
main = do
    args <- getArgs
    case args of
        [size, efile] -> dump (read size) efile
        [size, efile, qfile] -> test (read size) efile qfile
        _ -> putStrLn "qif size <encode-file> [<qif-file>]"

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

dump :: Int -> FilePath -> IO ()
dump size efile = do
    (dec, insthdr) <-
        newQDecoderS defaultQDecoderConfig{dcDynamicTableSize = size} True
    runConduitRes
        ( sourceFile efile
            .| conduitParser block
            .| mapM_C (liftIO . dumpSwitch dec insthdr)
        )

dumpSwitch
    :: (ByteString -> IO HeaderList)
    -> EncoderInstructionHandlerS
    -> (a, Block)
    -> IO ()
dumpSwitch dec insthdr (_, Block n bs)
    | n == 0 = do
        putStrLn "---- Stream 0"
        insthdr bs
    | otherwise = do
        putStrLn $ "---- Stream " ++ show n
        _ <- dec bs
        return ()

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

test :: Int -> FilePath -> FilePath -> IO ()
test size efile qfile = do
    (dec, insthdr') <-
        newQDecoderS defaultQDecoderConfig{dcDynamicTableSize = size} False
    q <- newTQueueIO
    let recv = atomically $ readTQueue q
        send x = atomically $ writeTQueue q x
        insthdr bs = do
            emp <- atomically $ isEmptyTQueue q
            unless emp yield
            insthdr' bs
            yield
    mvar <- newEmptyMVar
    withFile qfile ReadMode $ \h -> do
        tid <- forkIO $ decode dec h recv mvar
        runConduitRes
            ( sourceFile efile
                .| conduitParser block
                .| mapM_C (liftIO . testSwitch send insthdr)
            )
        takeMVar mvar
        killThread tid

testSwitch
    :: (Block -> IO ())
    -> EncoderInstructionHandlerS
    -> (a, Block)
    -> IO ()
testSwitch send insthdr (_, blk@(Block n bs))
    | n == 0 = insthdr bs
    | otherwise = send blk

decode :: QDecoderS -> Handle -> IO Block -> MVar () -> IO ()
decode dec h recv mvar = loop
  where
    loop = do
        hdr' <- fromCaseSensitive <$> headerlist h
        if null hdr'
            then putMVar mvar ()
            else do
                Block n bs <- recv
                hdr <- dec bs
                if hdr == hdr'
                    then loop
                    else do
                        putStrLn $ "---- Stream " ++ show n
                        mapM_ print hdr
                        putStrLn "----"
                        mapM_ print hdr'
                        putStrLn "----"
                        putMVar mvar ()

fromCaseSensitive :: HeaderList -> HeaderList
fromCaseSensitive = map (\(k, v) -> (foldedCase $ mk k, v))

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

block :: Parser Block
block = do
    num <- toInt <$> P.take 8
    len <- toInt <$> P.take 4
    dat <- P.take len
    return $ Block num dat

toInt :: ByteString -> Int
toInt = BS.foldl' f 0
  where
    f n w8 = n * 256 + fromIntegral w8

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

headerlist :: Handle -> IO HeaderList
headerlist h = loop id
  where
    loop b = do
        ml <- line h
        case ml of
            Nothing -> return $ b []
            Just l
                | l == "" -> return $ b []
                | otherwise -> do
                    let (k, v0) = BS8.break (== '\t') l
                        v = BS8.drop 1 v0
                    loop (b . ((k, v) :))

line :: Handle -> IO (Maybe ByteString)
line h = do
    el <- E.try $ BS8.hGetLine h
    case el of
        Left (_ :: E.IOException) -> return Nothing
        Right l
            | BS8.take 1 l == "#" -> line h
            | otherwise -> return $ Just l