module Hans.Message.Arp where

import Hans.Address (Address(addrSize))
import Hans.Utils (chunk)

import Control.Applicative (Applicative(..),(<$>))
import Data.Serialize.Get (Get,runGet,getWord8,getWord16be)
import Data.Serialize.Put (Putter,runPut,putWord16be,putWord8)
import Data.Word (Word16)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L


-- Arp Packets -----------------------------------------------------------------

data ArpPacket hw p = ArpPacket
  { arpHwType :: !Word16
  , arpPType  :: !Word16
  , arpOper   :: ArpOper
  , arpSHA    :: hw
  , arpSPA    :: p
  , arpTHA    :: hw
  , arpTPA    :: p
  } deriving (Show)

-- | Parse an Arp packet, given a way to parse hardware and protocol addresses.
parseArpPacket :: Get hw -> Get p
               -> S.ByteString -> Either String (ArpPacket hw p)
parseArpPacket getHw getP = runGet $
      ArpPacket
  <$> getWord16be  -- hardware type
  <*> getWord16be  -- protocol type
  <*  getWord8     -- hardware address length (ignored)
  <*  getWord8     -- protocol address length (ignored)
  <*> parseArpOper -- operation
  <*> getHw        -- sender hardware address
  <*> getP         -- sender protocol address
  <*> getHw        -- target hardware address
  <*> getP         -- target protocol address

-- | Render an Arp packet, given a way to render hardware and protocol
-- addresses.
renderArpPacket :: (Address hw, Address p)
                => Putter hw -> Putter p
                -> ArpPacket hw p -> L.ByteString
renderArpPacket putHw putP arp = chunk $ runPut $ do
  putWord16be   (arpHwType arp)
  putWord16be   (arpPType arp)
  putWord8      (addrSize (arpSHA arp))
  putWord8      (addrSize (arpSPA arp))
  renderArpOper (arpOper arp)
  putHw         (arpSHA arp)
  putP          (arpSPA arp)
  putHw         (arpTHA arp)
  putP          (arpTPA arp)


-- Arp Opcodes -----------------------------------------------------------------

-- | Arp operations.
data ArpOper
  = ArpRequest -- ^ 0x1
  | ArpReply   -- ^ 0x2
  deriving (Show,Eq)

-- | Parse an Arp operation.
parseArpOper :: Get ArpOper
parseArpOper  = do
  b <- getWord16be
  case b of
    0x1 -> return ArpRequest
    0x2 -> return ArpReply
    _   -> fail "invalid Arp opcode"

-- | Render an Arp operation.
renderArpOper :: Putter ArpOper
renderArpOper op = case op of
  ArpRequest -> putWord16be 0x1
  ArpReply   -> putWord16be 0x2