-- |Learns the location of hosts at each switch.
--
-- 'pktsByLocation' produces a stream of NetCore policies and a stream of
-- hosts' locations. The NetCore policies inspect packets at all switches.
-- At each switch, the NetCore policies learn the ingress port that receives
-- packets from each host. These learned locations are output to the other
-- stream.
--
--  The program handles host mobility: if the host starts sending from
--  a new port, the program learns the new location of the host.
pktsByLocation :: IO (Chan (EthernetAddress, Loc), Chan Policy)
pktsByLocation = do
  locChan <- newChan -- hosts' locations
  polChan <- newChan -- policy to monitor new hosts and moved hosts
  (pktChan, act) <- getPkts
  -- In a loop, inspect packets, and learn the ingress port for hosts  
  let loop :: Map.Map (Switch, EthernetAddress) (Port, Predicate)
           -> IO ()
      loop locs = do
        (Loc sw port, pkt) <- readChan pktChan
        let srcMac = pktDlSrc pkt
        case Map.lookup (sw, srcMac) locs of
          Just (port', _) | port == port' -> do
            -- We aleady know the host and its location is unchanged.
            loop locs
          otherwise -> do
            -- Either (1) srcMac has never sent a packet to the switch, or
            -- (2) it has before, but from a different port.
            let pred = DlSrc srcMac <&&>
                       Switch sw <&&>
                       IngressPort port
            let locs' = Map.insert (sw, srcMac) (port, pred) locs
            writeChan locChan (srcMac, Loc sw port)
            -- Update the policy so that we do not see packets from known
            -- hosts at known locations.
            let knownHosts = prOr (map snd (Map.elems locs'))
            writeChan polChan $
              Not (DlSrc broadcastAddress <||> knownHosts) ==> act
            loop locs'
  -- Initially, inspect all packets (excluding broadcasts)
  writeChan polChan (Not (DlSrc broadcastAddress) ==> act)
  forkIO (loop Map.empty)
  return (locChan, polChan)

-- |'fwdTo locs ((sw, dstMac), port)' builds a policy that forwards
-- packets to 'dstMac', at switch 'sw' on port 'port' from hosts in
-- 'locs'. The policy only forwards packets on 'sw', so it ignores
-- members of 'locs' on other switches.
pktsByLocation :: IO (Chan (EthernetAddress, Loc), Chan Policy)
pktsByLocation = do
  locChan <- newChan -- hosts' locations
  polChan <- newChan -- policy to monitor new hosts and moved hosts
  (pktChan, act) <- getPkts
  -- In a loop, inspect packets, and learn the ingress port for hosts  
  let loop :: Map.Map (Switch, EthernetAddress) (Port, Predicate)
           -> IO ()
      loop locs = do
        (Loc sw port, pkt) <- readChan pktChan
        let srcMac = pktDlSrc pkt
        case Map.lookup (sw, srcMac) locs of
          Just (port', _) | port == port' -> do
            -- We aleady know the host and its location is unchanged.
            loop locs
          otherwise -> do
            -- Either (1) srcMac has never sent a packet to the switch, or
            -- (2) it has before, but from a different port.
            let pred = DlSrc srcMac <&&>
                       Switch sw <&&>
                       IngressPort port
            let locs' = Map.insert (sw, srcMac) (port, pred) locs
            writeChan locChan (srcMac, Loc sw port)
            -- Update the policy so that we do not see packets from known
            -- hosts at known locations.
            let knownHosts = prOr (map snd (Map.elems locs'))
            writeChan polChan $
              Not (DlSrc broadcastAddress <||> knownHosts) ==> act
            loop locs'
  -- Initially, inspect all packets (excluding broadcasts)
  writeChan polChan (Not (DlSrc broadcastAddress) ==> act)
  forkIO (loop Map.empty)
  return (locChan, polChan)

-- |'fwdTo locs ((sw, dstMac), port)' builds a policy that forwards
-- packets to 'dstMac', at switch 'sw' on port 'port' from hosts in
-- 'locs'. The policy only forwards packets on 'sw', so it ignores
-- members of 'locs' on other switches.
pktsByLocation = do
  locChan <- newChan -- hosts' locations
  polChan <- newChan -- policy to monitor new hosts and moved hosts
  (pktChan, act) <- getPkts
  -- In a loop, inspect packets, and learn the ingress port for hosts  
  let loop :: Map.Map (Switch, EthernetAddress) (Port, Predicate)
           -> IO ()
      loop locs = do
        (Loc sw port, pkt) <- readChan pktChan
        let srcMac = pktDlSrc pkt
        case Map.lookup (sw, srcMac) locs of
          Just (port', _) | port == port' -> do
            -- We aleady know the host and its location is unchanged.
            loop locs
          otherwise -> do
            -- Either (1) srcMac has never sent a packet to the switch, or
            -- (2) it has before, but from a different port.
            let pred = DlSrc srcMac <&&>
                       Switch sw <&&>
                       IngressPort port
            let locs' = Map.insert (sw, srcMac) (port, pred) locs
            writeChan locChan (srcMac, Loc sw port)
            -- Update the policy so that we do not see packets from known
            -- hosts at known locations.
            let knownHosts = prOr (map snd (Map.elems locs'))
            writeChan polChan $
              Not (DlSrc broadcastAddress <||> knownHosts) ==> act
            loop locs'
  -- Initially, inspect all packets (excluding broadcasts)
  writeChan polChan (Not (DlSrc broadcastAddress) ==> act)
  forkIO (loop Map.empty)
  return (locChan, polChan)

-- |'fwdTo locs ((sw, dstMac), port)' builds a policy that forwards
-- packets to 'dstMac', at switch 'sw' on port 'port' from hosts in
-- 'locs'. The policy only forwards packets on 'sw', so it ignores
-- members of 'locs' on other switches.