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

module Network.TLS.Packet13 (
    encodeHandshake13,
    decodeHandshakeRecord13,
    decodeHandshake13,
    decodeHandshakes13,
    encodeCertificate13,
) where

import Codec.Compression.Zlib
import qualified Control.Exception as E
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Data.X509 (
    CertificateChain,
    CertificateChainRaw (..),
    decodeCertificateChain,
    encodeCertificateChain,
 )
import System.IO.Unsafe

import Network.TLS.ErrT
import Network.TLS.Imports
import Network.TLS.Packet
import Network.TLS.Struct
import Network.TLS.Struct13
import Network.TLS.Types
import Network.TLS.Wire

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

encodeHandshake13 :: Handshake13 -> ByteString
encodeHandshake13 hdsk = pkt
  where
    tp = typeOfHandshake13 hdsk
    content = encodeHandshake13' hdsk
    len = B.length content
    header = encodeHandshakeHeader13 tp len
    pkt = B.concat [header, content]

-- TLS 1.3 does not use "select (extensions_present)".
putExtensions :: [ExtensionRaw] -> Put
putExtensions es = putOpaque16 (runPut $ mapM_ putExtension es)

encodeHandshake13' :: Handshake13 -> ByteString
encodeHandshake13' (ServerHello13 SH{..}) = runPut $ do
    putBinaryVersion shVersion
    putServerRandom32 shRandom
    putSession shSession
    putWord16 $ fromCipherId shCipher
    putWord8 shComp
    putExtensions shExtensions
encodeHandshake13'
    ( NewSessionTicket13
            life
            ageadd
            (TicketNonce nonce)
            (SessionIDorTicket_ label)
            exts
        ) = runPut $ do
        putWord32 life
        putWord32 ageadd
        putOpaque8 nonce
        putOpaque16 label
        putExtensions exts
encodeHandshake13' EndOfEarlyData13 = ""
encodeHandshake13' (EncryptedExtensions13 exts) = runPut $ putExtensions exts
encodeHandshake13' (Certificate13 reqctx (CertificateChain_ cc) ess) = encodeCertificate13 reqctx cc ess
encodeHandshake13' (CertRequest13 reqctx exts) = runPut $ do
    putOpaque8 reqctx
    putExtensions exts
encodeHandshake13' (CertVerify13 (DigitallySigned hs sig)) = runPut $ do
    putSignatureHashAlgorithm hs
    putOpaque16 sig
encodeHandshake13' (Finished13 (VerifyData dat)) = runPut $ putBytes dat
encodeHandshake13' (KeyUpdate13 UpdateNotRequested) = runPut $ putWord8 0
encodeHandshake13' (KeyUpdate13 UpdateRequested) = runPut $ putWord8 1
encodeHandshake13' (CompressedCertificate13 reqctx (CertificateChain_ cc) ess) = runPut $ do
    putWord16 1 -- zlib: fixme
    let bs = encodeCertificate13 reqctx cc ess
    putWord24 $ fromIntegral $ B.length bs
    putOpaque24 $ BL.toStrict $ compress $ BL.fromStrict bs

encodeHandshakeHeader13 :: HandshakeType -> Int -> ByteString
encodeHandshakeHeader13 ty len = runPut $ do
    putWord8 (fromHandshakeType ty)
    putWord24 len

encodeCertificate13
    :: CertReqContext -> CertificateChain -> [[ExtensionRaw]] -> ByteString
encodeCertificate13 reqctx cc ess = runPut $ do
    putOpaque8 reqctx
    putOpaque24 (runPut $ mapM_ putCert $ zip certs ess)
  where
    CertificateChainRaw certs = encodeCertificateChain cc
    putCert (certRaw, exts) = do
        putOpaque24 certRaw
        putExtensions exts

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

decodeHandshakes13 :: MonadError TLSError m => ByteString -> m [Handshake13]
decodeHandshakes13 bs = case decodeHandshakeRecord13 bs of
    GotError err -> throwError err
    GotPartial _cont -> error "decodeHandshakes13"
    GotSuccess (ty, content) -> case decodeHandshake13 ty content of
        Left e -> throwError e
        Right h -> return [h]
    GotSuccessRemaining (ty, content) left -> case decodeHandshake13 ty content of
        Left e -> throwError e
        Right h -> (h :) <$> decodeHandshakes13 left

decodeHandshakeRecord13 :: ByteString -> GetResult (HandshakeType, ByteString)
decodeHandshakeRecord13 = runGet "handshake-record" $ do
    ty <- getHandshakeType
    content <- getOpaque24
    return (ty, content)

{- FOURMOLU_DISABLE -}
decodeHandshake13
    :: HandshakeType -> ByteString -> Either TLSError Handshake13
decodeHandshake13 ty = runGetErr ("handshake[" ++ show ty ++ "]") $ case ty of
    HandshakeType_ServerHello           -> decodeServerHello13
    HandshakeType_NewSessionTicket      -> decodeNewSessionTicket13
    HandshakeType_EndOfEarlyData        -> return EndOfEarlyData13
    HandshakeType_EncryptedExtensions   -> decodeEncryptedExtensions13
    HandshakeType_Certificate           -> decodeCertificate13
    HandshakeType_CertRequest           -> decodeCertRequest13
    HandshakeType_CertVerify            -> decodeCertVerify13
    HandshakeType_Finished              -> decodeFinished13
    HandshakeType_KeyUpdate             -> decodeKeyUpdate13
    HandshakeType_CompressedCertificate -> decodeCompressedCertificate13
    (HandshakeType x) -> fail $ "Unsupported HandshakeType " ++ show x
{- FOURMOLU_ENABLE -}

decodeServerHello13 :: Get Handshake13
decodeServerHello13 = do
    ver <- getBinaryVersion
    random <- getServerRandom32
    session <- getSession
    cipherid <- CipherId <$> getWord16
    comp <- getWord8
    exts <- getWord16 >>= getExtensions . fromIntegral
    return $
        ServerHello13 $
            SH
                { shVersion = ver
                , shRandom = random
                , shSession = session
                , shCipher = cipherid
                , shComp = comp
                , shExtensions = exts
                }

decodeNewSessionTicket13 :: Get Handshake13
decodeNewSessionTicket13 = do
    life <- getWord32
    ageadd <- getWord32
    nonce <- TicketNonce <$> getOpaque8
    label <- SessionIDorTicket_ <$> getOpaque16
    len <- fromIntegral <$> getWord16
    exts <- getExtensions len
    return $ NewSessionTicket13 life ageadd nonce label exts

decodeEncryptedExtensions13 :: Get Handshake13
decodeEncryptedExtensions13 =
    EncryptedExtensions13 <$> do
        len <- fromIntegral <$> getWord16
        getExtensions len

decodeCertificate13 :: Get Handshake13
decodeCertificate13 = do
    reqctx <- getOpaque8
    len <- fromIntegral <$> getWord24
    (certRaws, ess) <- unzip <$> getList len getCert
    case decodeCertificateChain $ CertificateChainRaw certRaws of
        Left (i, s) -> fail ("error certificate parsing " ++ show i ++ ":" ++ s)
        Right cc -> return $ Certificate13 reqctx (CertificateChain_ cc) ess
  where
    getCert = do
        l <- fromIntegral <$> getWord24
        cert <- getBytes l
        len <- fromIntegral <$> getWord16
        exts <- getExtensions len
        return (3 + l + 2 + len, (cert, exts))

decodeCertRequest13 :: Get Handshake13
decodeCertRequest13 = do
    reqctx <- getOpaque8
    len <- fromIntegral <$> getWord16
    exts <- getExtensions len
    return $ CertRequest13 reqctx exts

decodeCertVerify13 :: Get Handshake13
decodeCertVerify13 =
    CertVerify13 <$> (DigitallySigned <$> getSignatureHashAlgorithm <*> getOpaque16)

decodeFinished13 :: Get Handshake13
decodeFinished13 = Finished13 . VerifyData <$> (remaining >>= getBytes)

decodeKeyUpdate13 :: Get Handshake13
decodeKeyUpdate13 = do
    ru <- getWord8
    case ru of
        0 -> return $ KeyUpdate13 UpdateNotRequested
        1 -> return $ KeyUpdate13 UpdateRequested
        x -> fail $ "Unknown request_update: " ++ show x

decodeCompressedCertificate13 :: Get Handshake13
decodeCompressedCertificate13 = do
    algo <- getWord16
    when (algo /= 1) $ fail "comp algo is not supported" -- fixme
    len <- getWord24
    bs <- getOpaque24
    if bs == ""
        then fail "empty compressed certificate"
        else case decompressIt bs of
            Left e -> fail (show e)
            Right bs' -> do
                when (B.length bs' /= len) $ fail "plain length is wrong"
                case runGetMaybe decodeCertificate13 bs' of
                    Just (Certificate13 reqctx certs ess) -> return $ CompressedCertificate13 reqctx certs ess
                    --                    _ -> fail "compressed certificate cannot be parsed"
                    _ -> fail $ "invalid compressed certificate: len = " ++ show len

decompressIt :: ByteString -> Either DecompressError ByteString
decompressIt inp = unsafePerformIO $ E.handle handler $ do
    Right . BL.toStrict <$> E.evaluate (decompress (BL.fromStrict inp))
  where
    handler e = return $ Left (e :: DecompressError)
