Implemented a mechanism to remember ARP requests and answer them when the response is received
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
index 2034118..cb361af 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
@@ -120,7 +120,7 @@
 				
 				List<Link> activeLinks = topoLinkService.getActiveLinks();
 				for (Link l : activeLinks){
-					log.debug("active link: {}", l);
+					//log.debug("active link: {}", l);
 				}
 				
 				Iterator<LDUpdate> it = linkUpdates.iterator();
@@ -224,7 +224,7 @@
 		
 		//TODO We'll initialise this here for now, but it should really be done as
 		//part of the controller core
-		proxyArp = new ProxyArpManager(floodlightProvider, topology);
+		proxyArp = new ProxyArpManager(floodlightProvider, topology, devices);
 		
 		linkUpdates = new ArrayList<LDUpdate>();
 		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
@@ -435,7 +435,7 @@
 				node.rib.routerId.getHostAddress()});
 		
 		//TODO this is wrong, we shouldn't be dealing with BGP peers here.
-		//We need to figure out where the device is attached and what it's
+		//We need to figure out where the device is attached and what its
 		//mac address is by learning. 
 		//The next hop is not necessarily the peer, and the peer's attachment
 		//point is not necessarily the next hop's attachment point.
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
new file mode 100644
index 0000000..20c6a28
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
@@ -0,0 +1,28 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+import net.floodlightcontroller.packet.ARP;
+
+public class HostArpRequester implements IArpRequester {
+
+	private IProxyArpService arpService;
+	private ARP arpRequest;
+	private long dpid;
+	private short port;
+	//private long requestTime; //in ms
+	
+	public HostArpRequester(IProxyArpService arpService, ARP arpRequest, 
+			long dpid, short port) {
+		
+		this.arpService = arpService;
+		this.arpRequest = arpRequest;
+		this.dpid = dpid;
+		this.port = port;
+		//this.requestTime = System.currentTimeMillis();
+	}
+
+	@Override
+	public void arpResponse(byte[] mac) {
+		arpService.sendArpReply(arpRequest, dpid, port, mac);
+	}
+
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
new file mode 100644
index 0000000..2a74944
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
@@ -0,0 +1,5 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+public interface IArpRequester {
+	public void arpResponse(byte[] mac);
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
new file mode 100644
index 0000000..1ff91e1
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
@@ -0,0 +1,41 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+import java.net.InetAddress;
+
+import net.floodlightcontroller.packet.ARP;
+
+public interface IProxyArpService {
+	
+	public final int ARP_REQUEST_TIMEOUT = 2000; //ms
+	
+	/**
+	 * Tell the IProxyArpService to send an ARP reply with the targetMac to 
+	 * the host on the specified switchport.
+	 * @param arpRequest
+	 * @param dpid
+	 * @param port
+	 * @param targetMac
+	 */
+	public void sendArpReply(ARP arpRequest, long dpid, short port, byte[] targetMac);
+	
+	/**
+	 * Returns the mac address if there is a valid entry in the cache.
+	 * Otherwise returns null.
+	 * @param ipAddress
+	 * @return
+	 */
+	public byte[] lookupMac(InetAddress ipAddress);
+	
+	/**
+	 * Tell the IProxyArpService to send an ARP request for the IP address.
+	 * The request will be broadcast out all edge ports in the network.
+	 * As an optimization, the IProxyArpService will first check its cache and
+	 * return the MAC address if it is already known. If not, the request will be
+	 * sent and the callback will be called when the MAC address is known
+	 * (or if the request times out). 
+	 * @param ipAddress
+	 * @param requester
+	 * @return
+	 */
+	public byte[] sendArpRequest(InetAddress ipAddress, IArpRequester requester);
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
index a5246c0..538bb41 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
@@ -6,18 +6,23 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
 
 import net.floodlightcontroller.core.FloodlightContext;
 import net.floodlightcontroller.core.IFloodlightProviderService;
 import net.floodlightcontroller.core.IOFMessageListener;
 import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.packet.ARP;
 import net.floodlightcontroller.packet.Ethernet;
 import net.floodlightcontroller.topology.ITopologyService;
-import net.floodlightcontroller.topology.NodePortTuple;
 import net.floodlightcontroller.util.MACAddress;
 
 import org.openflow.protocol.OFMessage;
@@ -31,27 +36,87 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class ProxyArpManager implements IOFMessageListener {
+public class ProxyArpManager implements IProxyArpService, IOFMessageListener {
 	private static Logger log = LoggerFactory.getLogger(ProxyArpManager.class);
 	
-	private final long ARP_ENTRY_TIMEOUT = 600000; //ms (== 5 mins)
+	private final long ARP_ENTRY_TIMEOUT = 600000; //ms (== 10 mins)
 	
+	private final long ARP_REQUEST_TIMEOUT_THREAD_PERIOD = 60000; //ms (== 1 min) 
+			
 	protected IFloodlightProviderService floodlightProvider;
 	protected ITopologyService topology;
+	protected IDeviceService devices;
 	
 	protected Map<InetAddress, ArpTableEntry> arpTable;
 	
+	//protected ConcurrentHashMap<InetAddress, Set<ArpRequest>> arpRequests;
+	protected ConcurrentHashMap<InetAddress, ArpRequest> arpRequests;
+	
+	private class ArpRequest {
+		private Set<IArpRequester> requesters;
+		private long requestTime;
+		
+		public ArpRequest(){
+			this.requesters = new HashSet<IArpRequester>();
+			this.requestTime = System.currentTimeMillis();
+		}
+		
+		public synchronized void addRequester(IArpRequester requester){
+			requestTime = System.currentTimeMillis();
+			requesters.add(requester);
+		}
+		
+		public boolean isExpired(){
+			return (System.currentTimeMillis() - requestTime) 
+					> IProxyArpService.ARP_REQUEST_TIMEOUT;
+		}
+		
+		public synchronized void dispatchReply(byte[] replyMacAddress){
+			for (IArpRequester requester : requesters){
+				requester.arpResponse(replyMacAddress);
+			}
+		}
+	}
+	
 	public ProxyArpManager(IFloodlightProviderService floodlightProvider,
-				ITopologyService topology){
+				ITopologyService topology, IDeviceService devices){
 		this.floodlightProvider = floodlightProvider;
 		this.topology = topology;
+		this.devices = devices;
 		
 		arpTable = new HashMap<InetAddress, ArpTableEntry>();
+		//arpRequests = new ConcurrentHashMap<InetAddress, Set<ArpRequest>>();
+		arpRequests = new ConcurrentHashMap<InetAddress, ArpRequest>();
+		
+		Timer arpRequestTimeoutTimer = new Timer();
+		arpRequestTimeoutTimer.scheduleAtFixedRate(new TimerTask() {
+			@Override
+			public void run() {
+				synchronized (arpRequests) {
+					log.debug("Current have {} outstanding requests", 
+							arpRequests.size());
+					
+					Iterator<Map.Entry<InetAddress, ArpRequest>> it 
+							= arpRequests.entrySet().iterator();
+					
+					while (it.hasNext()){
+						Map.Entry<InetAddress, ArpRequest> entry
+								= it.next();
+						
+						if (entry.getValue().isExpired()){
+							log.debug("Cleaning expired ARP request for {}", 
+									entry.getKey().getHostAddress());
+							it.remove();
+						}
+					}
+				}
+			}
+		}, 0, ARP_REQUEST_TIMEOUT_THREAD_PERIOD);
 	}
 	
 	@Override
 	public String getName() {
-		return  "ProxyArpManager";
+		return "ProxyArpManager";
 	}
 
 	@Override
@@ -78,42 +143,107 @@
                 IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
 		
 		if (eth.getEtherType() == Ethernet.TYPE_ARP){
-			
-			
 			ARP arp = (ARP) eth.getPayload();
 			
 			if (arp.getOpCode() == ARP.OP_REQUEST) {
-				log.debug("ARP request received for {}", bytesToStringAddr(arp.getTargetProtocolAddress()));
-				
-				byte[] mac = lookupArpTable(arp.getTargetProtocolAddress());
-				
-				if (mac == null){
-					//Mac address is not in our arp table. 
-					//We need to flood the request out edge ports
-					Set<NodePortTuple> broadcastPorts = topology.getBroadcastDomainPorts();
-					log.debug("size {}", broadcastPorts.size());
-					for (NodePortTuple nodePort : broadcastPorts){
-						log.debug("Port {}", nodePort);
-					}
-					broadcastArpRequestOutEdge(pi, sw.getId(), pi.getInPort());
-				}
-				else {
-					//We know the address, so send a reply
-					log.debug("Sending reply of {}", MACAddress.valueOf(mac).toString());
-					sendArpReply(arp, pi, mac, sw);
-				}
+				handleArpRequest(sw, pi, arp);
 			}
 			else if (arp.getOpCode() == ARP.OP_REPLY) {
-				log.debug("ARP reply recieved for {}", bytesToStringAddr(arp.getSenderProtocolAddress()));
-				
-				log.debug("arp table {}", arpTable.keySet());
-				
-				updateArpTable(arp);
+				handleArpReply(sw, pi, arp);
 			}
 		}
 		
+		//TODO should we propagate ARP or swallow it?
+		//Always propagate for now so DeviceManager can learn the host location
 		return Command.CONTINUE;
 	}
+	
+	protected void handleArpRequest(IOFSwitch sw, OFPacketIn pi, ARP arp) {
+		log.debug("ARP request received for {}", 
+				bytesToStringAddr(arp.getTargetProtocolAddress()));
+		
+		byte[] mac = lookupArpTable(arp.getTargetProtocolAddress());
+		
+		if (mac == null){
+			//Mac address is not in our arp table.
+			
+			//TODO check what the DeviceManager thinks
+			
+			//Record where the request came from so we know where to send the reply
+			InetAddress target;
+			try {
+				 target = InetAddress.getByAddress(arp.getTargetProtocolAddress());
+			} catch (UnknownHostException e) {
+				log.debug("Invalid address in ARP request", e);
+				//return Command.CONTINUE; //Continue or stop?
+				return;
+			}
+			
+			synchronized (arpRequests) {
+				//arpRequests.putIfAbsent(target, 
+						//Collections.synchronizedSet(new HashSet<ArpRequest>()));
+				//		new ArpRequest());
+				//Set<ArpRequest> requesters = arpRequests.get(target);
+				if (arpRequests.get(target) == null) {
+					arpRequests.put(target, new ArpRequest());
+				}
+				ArpRequest request = arpRequests.get(target);
+								
+				request.addRequester(new HostArpRequester(this, arp, 
+						sw.getId(), pi.getInPort()));
+			}
+			
+			
+			//Flood the request out edge ports
+			broadcastArpRequestOutEdge(pi, sw.getId(), pi.getInPort());
+		}
+		else {
+			//We know the address, so send a reply
+			log.debug("Sending reply of {}", MACAddress.valueOf(mac).toString());
+			//sendArpReply(arp, pi, mac, sw);
+			sendArpReply(arp, sw.getId(), pi.getInPort(), mac);
+		}
+	}
+	
+	protected void handleArpReply(IOFSwitch sw, OFPacketIn pi, ARP arp){
+		log.debug("ARP reply recieved for {}", 
+				bytesToStringAddr(arp.getSenderProtocolAddress()));
+		
+		updateArpTable(arp);
+		
+		//See if anyone's waiting for this ARP reply
+		InetAddress addr;
+		try {
+			addr = InetAddress.getByAddress(arp.getSenderProtocolAddress());
+		} catch (UnknownHostException e) {
+			return;
+		}
+		
+		ArpRequest request = null;
+		synchronized (arpRequests) {
+			request = arpRequests.get(addr);
+			if (request != null) {
+				arpRequests.remove(addr);
+			}
+		}
+		if (request != null && !request.isExpired()) {
+			request.dispatchReply(arp.getSenderHardwareAddress());
+		}
+		
+		/*
+		Set<ArpRequest> requests = arpRequests.get(addr);
+		if (requests != null){
+			
+			synchronized (requests) {
+				for (ArpRequest request : requests) {
+					if (!request.isExpired()){
+						request.getRequester().arpResponse(
+								arp.getSenderHardwareAddress());
+					}
+				}
+			}
+		}*/
+	}
 
 	private synchronized byte[] lookupArpTable(byte[] ipAddress){
 		InetAddress addr;
@@ -210,26 +340,30 @@
 		}
 	}
 	
-	private void sendArpReply(ARP arpRequest, OFPacketIn pi, byte[] macRequested, IOFSwitch sw){
+	public void sendArpReply(ARP arpRequest, long dpid, short port, byte[] targetMac) {
+	//private void sendArpReply(ARP arpRequest, OFPacketIn pi, byte[] macRequested, IOFSwitch sw){
 		ARP arpReply = new ARP();
 		arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
 			.setProtocolType(ARP.PROTO_TYPE_IP)
 			.setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
 			.setProtocolAddressLength((byte)4) //can't find the constant anywhere
 			.setOpCode(ARP.OP_REPLY)
-			.setSenderHardwareAddress(macRequested)
+			//.setSenderHardwareAddress(macRequested)
+			.setSenderHardwareAddress(targetMac)
 			.setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
 			.setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
 			.setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
 		
 		Ethernet eth = new Ethernet();
 		eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
-			.setSourceMACAddress(macRequested)
+			//.setSourceMACAddress(macRequested)
+			.setSourceMACAddress(targetMac)
 			.setEtherType(Ethernet.TYPE_ARP)
 			.setPayload(arpReply);
 		
 		List<OFAction> actions = new ArrayList<OFAction>();
-		actions.add(new OFActionOutput(pi.getInPort()));
+		//actions.add(new OFActionOutput(pi.getInPort()));
+		actions.add(new OFActionOutput(port));
 		
 		OFPacketOut po = new OFPacketOut();
 		po.setInPort(OFPort.OFPP_NONE)
@@ -242,9 +376,15 @@
 		
 		List<OFMessage> msgList = new ArrayList<OFMessage>();
 		msgList.add(po);
+
+		IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+		
+		if (sw == null) {
+			return;
+		}
 		
 		try {
-			log.debug("Sending ARP reply to {}/{}", HexString.toHexString(sw.getId()), pi.getInPort());
+			log.debug("Sending ARP reply to {}/{}", HexString.toHexString(sw.getId()), port);
 			sw.write(msgList, null);
 			sw.flush();
 		} catch (IOException e) {
@@ -265,4 +405,14 @@
 		if (addr == null) return "";
 		else return addr.getHostAddress();
 	}
+	
+	
+	public byte[] lookupMac(InetAddress ipAddress){
+		//TODO implement
+		return null;
+	}
+	public byte[] sendArpRequest(InetAddress ipAddress, IArpRequester requester){
+		//TODO implement
+		return null;
+	}
 }