{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE PatternGuards #-}

module Hans.Layer.Tcp.Handlers (
    handleIncomingTcp
  , outputSegments
  ) where

import Hans.Address.IP4
import Hans.Layer
import Hans.Layer.Tcp.Messages
import Hans.Layer.Tcp.Monad
import Hans.Layer.Tcp.Timers
import Hans.Layer.Tcp.Types
import Hans.Layer.Tcp.Window
import Hans.Message.Ip4
import Hans.Message.Tcp

import Control.Monad (guard,when,unless,join)
import Data.Bits (bit)
import Data.Int (Int64)
import Data.Maybe (fromMaybe,isJust,isNothing)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.Foldable as F


-- Incoming Packets ------------------------------------------------------------

-- | Process a single incoming tcp packet.
handleIncomingTcp :: IP4Header -> S.ByteString -> Tcp ()
handleIncomingTcp ip4 bytes = do

  let src = ip4SourceAddr ip4
      dst = ip4DestAddr ip4

  guard (validateTcpChecksumIP4 src dst bytes)
  (hdr,body) <- liftRight (parseTcpPacket bytes)

  withConnection' src hdr (segmentArrives src hdr body)
    $ timeWaitConnection src hdr
    $ noConnection src hdr body

noConnection :: IP4 -> TcpHeader -> S.ByteString -> Tcp ()
noConnection src hdr @ TcpHeader { .. } body =
  do output (putStrLn "no connection")
     if | tcpRst    -> return ()
        | tcpAck    -> sendSegment src (mkRst hdr)                    L.empty
        | otherwise -> sendSegment src (mkRstAck hdr (S.length body)) L.empty
     finish


-- Arrival to a TIME_WAIT socket------------------------------------------------

-- | Handle an incoming packet in the TimeWait state
timeWaitConnection :: IP4 -> TcpHeader -> Tcp () -> Tcp ()
timeWaitConnection src hdr noTimeWait =
  do mb <- getTimeWait src hdr
     case mb of
       Just (sid,tcp) -> handleTimeWait sid tcp hdr
       Nothing        -> noTimeWait


handleTimeWait :: SocketId -> TimeWaitSock -> TcpHeader -> Tcp ()
handleTimeWait sid TimeWaitSock { .. } TcpHeader { .. }
  | tcpRst || tcpSyn = removeTimeWait sid
  | tcpAck           = do resetTimeWait2MSL sid

                          let addTimestamp
                                | Just ts <- twTimestamp = setTcpOption (mkTimestamp ts)
                                | otherwise              = id

                              hdr = addTimestamp emptyTcpHeader
                                        { tcpDestPort   = tcpSourcePort
                                        , tcpSourcePort = tcpDestPort
                                        , tcpSeqNum     = twSeqNum
                                          -- advance RCV.NXT over the FIN
                                        , tcpAckNum     = tcpSeqNum + 1
                                        , tcpAck        = True
                                        }

                          -- ACK the retransmitted FIN,ACK
                          when tcpFin (sendSegment (sidRemoteHost sid) hdr L.empty)
  | otherwise        = return ()



-- Segment Arrival -------------------------------------------------------------

{-# INLINE discardAndReturn #-}
discardAndReturn :: Sock a
discardAndReturn  = do outputSegments
                       escape

{-# INLINE done #-}
done :: Sock a
done  = do outputSegments
           escape

segmentArrives :: IP4 -> TcpHeader -> S.ByteString -> Sock ()
segmentArrives src hdr body =
  do shouldDrop <- modifyTcpSocket (updateTimestamp hdr)
     when shouldDrop discardAndReturn

     whenState Closed (inTcp (noConnection src hdr body))

     whenState Listen $
       do when (tcpAck hdr) (rst hdr)

          when (tcpSyn hdr) $ do child <- createConnection src hdr
                                 _     <- withChild child synAck
                                 return ()

          -- RST will be dropped at this point, as will anything else that
          -- wasn't covered by the above two cases.
          done

     whenState SynSent $
       do tcp <- getTcpSocket

          -- check the ACK
          when (tcpAck hdr) $
            do when (tcpAckNum hdr <= tcpIss tcp ||
                     tcpAckNum hdr >  tcpSndNxt tcp) $
                 do unless (tcpRst hdr) (rst hdr)
                    discardAndReturn

          -- one of these has to be set to continue processing in this state.
          unless (tcpSyn hdr || tcpRst hdr) discardAndReturn

          -- check RST
          let accAcceptable = tcpSndUna tcp <= tcpAckNum hdr &&
                              tcpAckNum hdr <= tcpSndNxt tcp

          when (tcpRst hdr) $
            do when accAcceptable $ do notify False
                                       closeSocket
               discardAndReturn

          -- this is where a security/compartment check would be done

          -- check SYN

          when (tcpSyn hdr) $
            do advanceRcvNxt 1
               modifyTcpSocket_ $ \ sock -> sock
                 { tcpOutMSS      = fromMaybe (tcpInMSS sock) (getMSS hdr)

                   -- clear out, and configure the retransmit buffer
                 , tcpOut         = setSndWind (tcpWindow hdr)
                                  $ setSndWindScale (windowScale hdr)
                                  $ clearRetransmit
                                  $ tcpOut sock

                   -- this corresponds to setting IRS to SEQ.SEG
                 , tcpIn          = emptyLocalWindow (tcpSeqNum hdr) 14600 0
                 , tcpSack        = sackSupported hdr
                 , tcpWindowScale = isJust (findTcpOption OptTagWindowScaling hdr)
                 }

               when (tcpAck hdr) $
                 do handleAck hdr -- update SND.UNA
                    TcpSocket { .. } <- getTcpSocket
                    if tcpSndUna > tcpIss
                       then do ack
                               establishConnection
                               notify True

                               -- continue at step 6
                               when (tcpUrg hdr) (proceedFromStep6 hdr body)

                       else do setState SynReceived
                               synAck
                               -- XXX queue any additional data for processing
                               -- once Established has been reached

                    done

     -- make sure that the sequence numbers are valid
     checkSequenceNumber hdr body
     checkResetBit hdr
     -- skip security/precidence check
     checkSynBit hdr
     checkAckBit hdr
     proceedFromStep6 hdr body

proceedFromStep6 :: TcpHeader -> S.ByteString -> Sock ()
proceedFromStep6 hdr body =
  do -- XXX skipping URG processing
     processSegmentText hdr body
     checkFinBit hdr


-- | Make sure that there is space for the incoming segment
checkSequenceNumber :: TcpHeader -> S.ByteString -> Sock ()
checkSequenceNumber hdr body =
  do TcpSocket { .. } <- getTcpSocket

     -- RCV.NXT <= SEG.SEQ + off < RCV.NXT + RCV.WND
     let canReceive off =
           lwRcvNxt tcpIn <= segSeq &&
           segSeq         <  lwRcvNxt tcpIn + fromIntegral (lwRcvWind tcpIn)
           where
           segSeq = tcpSeqNum hdr + off

         len = fromIntegral (S.length body)

         shouldDiscard
           | len == 0  = if lwRcvWind tcpIn == 0
                                   -- SEQ.SEG = RCV.NXT
                            then tcpSeqNum hdr == lwRcvNxt tcpIn
                            else canReceive 0
           | otherwise = canReceive 0 || canReceive (len - 1)

     when (shouldDiscard && all not [tcpAck hdr, tcpUrg hdr, tcpRst hdr]) $
       do unless (tcpRst hdr) ack
          discardAndReturn

-- | Process the presence of the RST bit
checkResetBit :: TcpHeader -> Sock ()
checkResetBit hdr
  | tcpRst hdr =
    do TcpSocket { .. } <- getTcpSocket

       whenState SynReceived $
            -- from an active open
         do when (isNothing tcpParent) $ do flushQueues
                                            closeSocket
            done

       whenStates [Established,FinWait1,FinWait2,CloseWait] $
         do flushQueues
            closeSocket
            done

       whenStates [Closing,LastAck] $
         do closeSocket
            done

  | otherwise  = return ()

checkSynBit :: TcpHeader -> Sock ()
checkSynBit hdr
  | tcpSyn hdr = whenStates [SynReceived,Established,FinWait1,FinWait2
                            ,CloseWait,Closing,LastAck] $
    do tcp <- getTcpSocket

       when (tcpSeqNum hdr `inRcvWnd` tcp) $
         do flushQueues
            closeSocket
            done

  | otherwise  = return ()

checkAckBit :: TcpHeader -> Sock ()
checkAckBit hdr
  | tcpAck hdr =
    do whenState SynReceived $
         do tcp <- getTcpSocket
            if tcpSndUna tcp <= tcpAckNum hdr && tcpAckNum hdr <= tcpSndNxt tcp
               then establishConnection
               else rst hdr

       whenStates [Established,FinWait1,FinWait2,CloseWait,Closing] $
         do let TcpHeader { .. } = hdr
            TcpSocket { .. } <- getTcpSocket

            -- if the ack was to something that is outstanding, update SND.UNA,
            -- and filter things out of the retransmit queue
            when (tcpSndUna < tcpAckNum && tcpAckNum <= tcpSndNxt) (handleAck hdr)

            -- if the ack is to something that hasn't been sent yet, drop the
            -- segment
            when (tcpSndNxt < tcpAckNum) discardAndReturn

            whenState FinWait1 $
              do tcp <- getTcpSocket
                 when (nothingOutstanding tcp) (setState FinWait2)

            -- XXX no way to acknowledge the user's close request from FinWait2
            -- whenState FinWait2 $ ...

            whenState Closing $
              do tcp <- getTcpSocket
                 when (nothingOutstanding tcp) enterTimeWait

       whenState LastAck $
         do tcp <- getTcpSocket
            when (nothingOutstanding tcp) $ do closeSocket
                                               done

  | otherwise  = discardAndReturn

processSegmentText :: TcpHeader -> S.ByteString -> Sock ()
processSegmentText hdr body =
  whenStates [Established,FinWait1,FinWait2] $
    do if S.null body
          -- make sure that the the delayed ack flag gets set
          then when (tcpSyn hdr || tcpFin hdr) $ modifyTcpTimers_
                                               $ \ tt -> tt { ttDelayedAck = True }

          -- push the segment through the local window, if it was the next thing
          -- expected, a wakeup action will be returned, notifying a waiting
          -- thread that there's something to read in the input buffer.
          else do mb <- modifyTcpSocket (handleData hdr body)

                  -- notify any waiting threads that there is stuff to read
                  case mb of
                    Just wakeup -> outputS (tryAgain wakeup)
                    Nothing     -> return ()


  -- otherwise, ignore the segment text

checkFinBit :: TcpHeader -> Sock ()
checkFinBit hdr
  | tcpFin hdr =
    do -- do not process the FIN
       whenStates [Closed,Listen,SynSent]
         discardAndReturn

       -- advance RCV.NXT over the FIN
       advanceRcvNxt 1

       flushQueues
       ack

       -- don't emit a FIN here, as that should be done by the user with an
       -- explicit call to `close`

       whenStates [SynReceived,Established] (setState CloseWait)

       -- XXX this needs to check that the FIN was ACKed, instead of just
       -- entering TimeWait
       whenState FinWait1 $ do TcpSocket { .. } <- getTcpSocket
                               if tcpSndNxt <= tcpSndUna
                                  then enterTimeWait
                                  else setState Closing

       whenState FinWait2 enterTimeWait

       -- don't do anything else for the remaining states
       done

  | otherwise  = return ()


-- | Flush all pending outgoing state.
--
-- TODO:
-- * Push all data out of the incoming queue to the incoming buffer
-- * Mark the socket as not accepting any more user data
flushQueues :: Sock ()
flushQueues  =
  do finalizers <- modifyTcpSocket flush
     outputS finalizers
  where
  flush tcp = (fins,tcp')
    where

    -- empty the outgoing buffer, notifying all waiting processes that the send
    -- won't happen
    (fins,out') = flushWaiting (tcpOutBuffer tcp)

    tcp' = tcp { tcpOutBuffer = out'
               }


-- | Update the currently held timestamp for both sides, and return a boolean
-- that indicates whether or not the packet should be dropped.
--
-- RFC 1323
updateTimestamp :: TcpHeader -> TcpSocket -> (Bool,TcpSocket)
updateTimestamp hdr tcp = (shouldDrop,tcp { tcpTimestamp = ts' })
  where
  -- when the timestamp check fails from an ack, that's not a syn,ack, mark this
  -- packet as one to be dropped.
  shouldDrop = not (tcpSyn hdr || tcpRst hdr)
            && isJust (tcpTimestamp tcp)
            && isNothing ts'
  ts' = do
    ts                     <- tcpTimestamp tcp
    OptTimestamp them echo <- findTcpOption OptTagTimestamp hdr
    -- when this is an ack, the echo value should not be greater than the
    -- current timestamp.  this doesn't currently account for overflow, so
    -- connections lasting >27 days will probably fail.
    let rel       = tsTimestamp ts - echo
        isGreater = 0 < rel && rel < bit 31
    when (tcpAck hdr) (guard (tsTimestamp ts == echo || isGreater))
    return ts { tsLastTimestamp = them }

-- | Enqueue a new packet in the local window, attempting to place the bytes
-- received in the user buffer as they complete a part of the stream.  Bytes are
-- ack'd as they complete the stream, and bytes that would cause the local
-- buffer to overflow are dropped.
handleData :: TcpHeader -> S.ByteString -> TcpSocket
           -> (Maybe Wakeup, TcpSocket)
handleData hdr body tcp0 = fromMaybe (Nothing,tcp) $ do
  (wakeup,buf') <- putBytes bytes (tcpInBuffer tcp)
  let tcp' = tcp
        { tcpInBuffer = buf'
        , tcpTimers   = (tcpTimers tcp)
          { ttDelayedAck = or [ not (L.null bytes)
                              , tcpSyn hdr
                              , tcpFin hdr ]
          }
        }
  return (wakeup, tcp')
  where
  (segs,win') = incomingPacket hdr body (tcpIn tcp0)
  tcp         = tcp0 { tcpIn = win' }
  bytes       = L.fromChunks (map inBody (F.toList segs))


-- | Handle an ACK to a sent data segment.
handleAck :: TcpHeader -> Sock ()
handleAck hdr = do
  now <- inTcp time
  modifyTcpSocket_ (updateAck now)
  where
  -- using Karns algorithm, only calibrate the RTO if the packet that was ack'd
  -- has not yet been retransmitted.
  updateAck now tcp = case receiveAck hdr (tcpOut tcp) of
    Just (seg,out') ->
      let calibrate | outFresh seg = calibrateRTO now (outTime seg)
                    | otherwise    = id
       in tcp { tcpOut    = out'
              , tcpSndUna = tcpAckNum hdr
              , tcpTimers = calibrate (tcpTimers tcp)
              }
    Nothing -> tcp

-- | Setup the 2MSL timer, and enter the TIME_WAIT state.  We only enter
-- TimeWait if all of our sent data has been ACKed, so it's safe to clear out
-- the retransmit queue here.
enterTimeWait :: Sock ()
enterTimeWait  = do
  modifyTcpSocket_ $ \ tcp -> tcp { tcpOut = clearRetransmit (tcpOut tcp) }
  set2MSL mslTimeout
  setState TimeWait

createConnection :: IP4 -> TcpHeader -> Sock TcpSocket
createConnection ip4 hdr =
  do let parent = listenSocketId (tcpDestPort hdr)
     isn <- inTcp initialSeqNum
     tcp <- getTcpSocket
     return (emptyTcpSocket (tcpWindow hdr) (windowScale hdr))
       { tcpParent      = Just parent
       , tcpSocketId    = incomingSocketId ip4 hdr
       , tcpState       = SynReceived
       , tcpIss         = isn
       , tcpSndNxt      = isn
       , tcpSndUna      = isn
       , tcpIn          = emptyLocalWindow (tcpSeqNum hdr) 14600 0
       , tcpOutMSS      = fromMaybe defaultMSS (getMSS hdr)
       , tcpTimestamp   = do
           -- require that the parent had a timestamp
           ts <- tcpTimestamp tcp
           -- require that they have sent us a timestamp, before using them
           OptTimestamp val _ <- findTcpOption OptTagTimestamp hdr
           return ts { tsLastTimestamp = val }
       , tcpSack        = sackSupported hdr
       , tcpWindowScale = isJust (findTcpOption OptTagWindowScaling hdr)
       }

establishConnection :: Sock ()
establishConnection  =
  do mb <- inParent popAcceptor
     case join mb of
       Just k  -> do sid <- tcpSocketId `fmap` getTcpSocket
                     outputS (k sid)
                     setState Established

       -- no one available to accept the connection, close it
       Nothing -> do finAck
                     setState FinWait1
                     done


-- Buffer Delivery -------------------------------------------------------------

-- | Fill up the remote window with segments.
outputSegments :: Sock ()
outputSegments  = do
  now <- inTcp time
  (ws,segs) <- modifyTcpSocket (genSegments now)
  F.mapM_ outputSegment segs
  unless (null ws) (outputS (F.traverse_ tryAgain ws))


-- Utilities -------------------------------------------------------------------

-- | Extract the maximum segment size from a header, if the option is present.
getMSS :: TcpHeader -> Maybe Int64
getMSS hdr = do
  OptMaxSegmentSize n <- findTcpOption OptTagMaxSegmentSize hdr
  return (fromIntegral n)

windowScale :: TcpHeader -> Int
windowScale hdr = fromMaybe 0 $ do
  OptWindowScaling n <- findTcpOption OptTagWindowScaling hdr
  return (fromIntegral n)

sackSupported :: TcpHeader -> Bool
sackSupported  = isJust . findTcpOption OptTagSackPermitted