Merge remote-tracking branch 'teamforge/master'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c7ccd3b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Created by https://www.gitignore.io
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+
+
+### Java ###
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# Linux temporary files
+*~
diff --git a/nb-configuration.xml b/nb-configuration.xml
new file mode 100644
index 0000000..39376a3
--- /dev/null
+++ b/nb-configuration.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+	<!--
+	This file contains additional configuration written by modules in the NetBeans IDE.
+	The configuration is intended to be shared among all the users of project and
+	therefore it is assumed to be part of version control checkout.
+	Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+	-->
+	<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+		<!--
+		Properties that influence various parts of the IDE, especially code formatting and the like. 
+		You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+		That way multiple projects can share the same settings (useful for formatting rules for example).
+		Any value defined here will override the pom.xml file value but is only applicable to the current project.
+		-->
+		<netbeans.hint.license>intel-license</netbeans.hint.license>
+		<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+		<org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>4</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+		<org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>4</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+		<org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>4</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+		<org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+		<org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+		<org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+		<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+		<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>4</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+		<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+		<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+		<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+		<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap>
+		<org-netbeans-modules-editor-indent.text.x-fortran.CodeStyle.project.text-limit-width>132</org-netbeans-modules-editor-indent.text.x-fortran.CodeStyle.project.text-limit-width>
+	</properties>
+</project-shared-configuration>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..726e83a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.intel.jndn.utils</groupId>
+	<artifactId>jndn-utils</artifactId>
+	<version>0.9.3</version>
+	<packaging>jar</packaging>
+	<description>Collection of tools to simplify synchronous and asynchronous data transfer over the NDN network</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.named_data.jndn</groupId>
+			<artifactId>jndn</artifactId>
+			<version>RELEASE</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-api</artifactId>
+			<version>2.1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-core</artifactId>
+			<version>2.1</version>
+		</dependency>
+		<!-- Test dependencies -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.10</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.intel.jndn.mock</groupId>
+			<artifactId>jndn-mock</artifactId>
+			<version>0.9</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<maven.compiler.source>1.7</maven.compiler.source>
+		<maven.compiler.target>1.7</maven.compiler.target>
+	</properties>
+	<repositories>
+		<repository>
+			<id>ubit-artifactory-or.intel.com</id>
+			<name>IT Artifactory (libs)</name>
+			<url>https://ubit-artifactory-or.intel.com/artifactory/libs-release-local</url>
+		</repository>
+	</repositories>
+	<distributionManagement>
+		<repository>
+			<id>ubit-artifactory-or.intel.com</id>
+			<name>ubit-artifactory-or.intel.com-releases</name>
+			<url>https://ubit-artifactory-or.intel.com/artifactory/libs-release-local</url>
+		</repository>
+		<snapshotRepository>
+			<id>ubit-artifactory-or.intel.com</id>
+			<name>ubit-artifactory-or.intel.com-snapshots</name>
+			<url>https://ubit-artifactory-or.intel.com/artifactory/libs-snapshot-local</url>
+		</snapshotRepository>
+	</distributionManagement>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/intel/jndn/utils/Client.java b/src/main/java/com/intel/jndn/utils/Client.java
new file mode 100644
index 0000000..39c4ada
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/Client.java
@@ -0,0 +1,207 @@
+/*
+ * File name: Client.java
+ * 
+ * Purpose: Provide a client to simplify information retrieval over the NDN
+ * network.
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import java.io.IOException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnTimeout;
+import net.named_data.jndn.encoding.EncodingException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Provide a client to simplify information retrieval over the NDN network.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class Client {
+
+  public static final long DEFAULT_SLEEP_TIME = 20;
+  public static final long DEFAULT_TIMEOUT = 2000;
+  private static final Logger logger = LogManager.getLogger();
+  private static Client defaultInstance;
+
+  /**
+   * Singleton access for simpler client use
+   *
+   * @return
+   */
+  public static Client getDefault() {
+    if (defaultInstance == null) {
+      defaultInstance = new Client();
+    }
+    return defaultInstance;
+  }
+
+  /**
+   * Synchronously retrieve the Data for an Interest; this will block until
+   * complete (i.e. either data is received or the interest times out).
+   *
+   * @param face
+   * @param interest
+   * @return Data packet or null
+   */
+  public Data getSync(Face face, Interest interest) {
+    // setup event
+    long startTime = System.currentTimeMillis();
+    final NDNEvent event = new NDNEvent(); // this event is used without observer/observables for speed; just serves as a final reference into the callbacks
+
+    // send interest
+    try {
+      face.expressInterest(interest, new OnData() {
+        @Override
+        public void onData(Interest interest, Data data) {
+          event.fromPacket(data);
+        }
+      }, new OnTimeout() {
+        @Override
+        public void onTimeout(Interest interest) {
+          event.fromPacket(new Exception("Interest timed out: " + interest.getName().toUri()));
+        }
+      });
+    } catch (IOException e) {
+      logger.warn("IO failure while sending interest.", e);
+      return null;
+    }
+
+    // process eventCount until a response is received or timeout
+    while (event.getPacket() == null) {
+      try {
+        synchronized (face) {
+          face.processEvents();
+        }
+      } catch (IOException | EncodingException e) {
+        logger.warn("Failed to process events.", e);
+        return null;
+      }
+      sleep();
+    }
+
+    // return
+    logger.debug("Request time (ms): " + (event.getTimestamp() - startTime));
+    return (event.isSuccess()) ? (Data) event.getPacket() : null;
+  }
+
+  /**
+   * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
+   * second timeout); this will block until complete (i.e. either data is
+   * received or the interest times out).
+   *
+   * @param face
+   * @param name
+   * @return
+   */
+  public Data getSync(Face face, Name name) {
+    return getSync(face, getDefaultInterest(name));
+  }
+
+  /**
+   * Asynchronously retrieve the Data for a given interest; use the returned
+ NDNObserver to handle the Data when it arrives. For example (with lambdas):
+   * <pre><code>
+   * Client.getDefault().get(face, interest).then((event) -> doSomething(event));
+   * </code></pre>
+   * 
+   * If you want to block until the response returns, try something like:
+   * <pre><code>
+ NDNObserver observer = Client.getDefault().get(face, interest);
+ while(observer.eventCount() == 0){
+   Thread.sleep(50);
+ }
+ doSomething(observer.getFirst());
+ </code></pre>
+   *
+   * @param face
+   * @param interest
+   * @return
+   */
+  public NDNObserver get(final Face face, final Interest interest) {
+    // setup observer
+    final NDNObserver observer = new NDNObserver();
+    final NDNObservable eventHandler = new NDNObservable();
+    eventHandler.addObserver(observer);
+
+    // setup background thread
+    Thread backgroundThread = new Thread(new Runnable() {
+      @Override
+      public void run() {
+        // send interest
+        try {
+          face.expressInterest(interest, eventHandler, eventHandler);
+        } catch (IOException e) {
+          logger.warn("IO failure while sending interest.", e);
+          eventHandler.notify(e);
+        }
+
+        // process eventCount until a response is received or timeout
+        while (observer.dataCount() == 0 && observer.errorCount() == 0 && !observer.mustStop()) {
+          try {
+            synchronized (face) {
+              face.processEvents();
+            }
+          } catch (IOException | EncodingException e) {
+            logger.warn("Failed to process events.", e);
+            eventHandler.notify(e);
+          }
+          sleep();
+        }
+
+        // finished
+        logger.trace("Received response; stopping thread.");
+      }
+    });
+    backgroundThread.setName(String.format("Client.get(%s)", interest.getName().toUri()));
+    backgroundThread.setDaemon(true);
+    backgroundThread.start();
+
+    // return
+    return observer;
+  }
+
+  /**
+   * Asynchronously retrieve the Data for a Name using default Interest
+   * parameters; see get(Face, Interest) for examples.
+   *
+   * @param face
+   * @param name
+   * @return
+   */
+  public NDNObserver get(Face face, Name name) {
+    return get(face, getDefaultInterest(name));
+  }
+
+  /**
+   * Put the current thread to sleep to allow time for IO
+   */
+  protected void sleep() {
+    try {
+      Thread.currentThread().sleep(DEFAULT_SLEEP_TIME);
+    } catch (InterruptedException e) {
+      logger.error("Event loop interrupted.", e);
+    }
+  }
+
+  /**
+   * Create a default interest for a given Name using some common settings: -
+   * lifetime: 2 seconds
+   *
+   * @param name
+   * @return
+   */
+  public Interest getDefaultInterest(Name name) {
+    Interest interest = new Interest(name, DEFAULT_TIMEOUT);
+    return interest;
+  }
+}
diff --git a/src/main/java/com/intel/jndn/utils/InternalFace.java b/src/main/java/com/intel/jndn/utils/InternalFace.java
new file mode 100644
index 0000000..1985902
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/InternalFace.java
@@ -0,0 +1,19 @@
+/*
+ * File name: InternalFace.java
+ * 
+ * Purpose: Handle data traffic within an application storing all packets in
+ * memory; useful for standardizing methods for getting/putting data
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+/**
+ * TODO waiting on Face to become overridable
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class InternalFace {
+
+}
diff --git a/src/main/java/com/intel/jndn/utils/NDNEvent.java b/src/main/java/com/intel/jndn/utils/NDNEvent.java
new file mode 100644
index 0000000..5316ae1
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/NDNEvent.java
@@ -0,0 +1,77 @@
+/*
+ * File name: NDNEvent.java
+ * 
+ * Purpose: Signals a Client event for observers to act on
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+/**
+ * Signals an event (from Client or Server) for observers to act on
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NDNEvent<T> {
+
+  protected boolean success;
+  protected long timestamp;
+  protected T packet;
+
+  /**
+   * Constructor
+   */
+  public NDNEvent() {
+    timestamp = System.currentTimeMillis();
+    success = false; // an event without a packet is a failed event
+  }
+  
+  /**
+   * Constructor
+   * 
+   * @param packet 
+   */
+  public NDNEvent(T packet) {
+    fromPacket(packet);
+  }
+
+  /**
+   * Build this event from a passed packet; the event is considered a failure
+   * if the packet is any type of Exception
+   * 
+   * @param packet 
+   */
+  public final void fromPacket(T packet) {
+    this.timestamp = System.currentTimeMillis();
+    this.success = !Exception.class.isInstance(packet);
+    this.packet = packet;
+  }
+
+  /**
+   * Retrieve success status
+   * 
+   * @return 
+   */
+  public boolean isSuccess() {
+    return success;
+  }
+
+  /**
+   * Retrieve event timestamp
+   * 
+   * @return 
+   */
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  /**
+   * Retrieve event packet
+   * 
+   * @return 
+   */
+  public T getPacket() {
+    return packet;
+  }
+}
diff --git a/src/main/java/com/intel/jndn/utils/NDNObservable.java b/src/main/java/com/intel/jndn/utils/NDNObservable.java
new file mode 100644
index 0000000..003fe45
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/NDNObservable.java
@@ -0,0 +1,130 @@
+/*
+ * File name: NDNObservable.java
+ * 
+ * Purpose: 
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Observable;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnInterest;
+import net.named_data.jndn.OnTimeout;
+import net.named_data.jndn.transport.Transport;
+
+/**
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NDNObservable extends Observable implements OnData, OnTimeout, OnInterest {
+
+  protected List<NDNEvent> events = new ArrayList<>();
+  protected List<Interest> incomingInterestPackets = new ArrayList<>();
+  protected List<Data> incomingDataPackets;
+
+  /**
+   * Generic notification
+   * 
+   * @param <T>
+   * @param packet
+   */
+  public <T> void notify(T packet) {
+    setChanged();
+    notifyObservers(new NDNEvent(packet));
+  }
+
+  /**
+   * Handle data packets
+   * 
+   * @param interest
+   * @param data 
+   */
+  @Override
+  public void onData(Interest interest, Data data) {
+    notify(data);
+  }
+
+  /**
+   * Handle exceptions
+   * 
+   * @param e 
+   */
+  public void onError(Exception e) {
+    notify(e);
+  }
+
+  /**
+   * Handle timeouts
+   * 
+   * @param interest 
+   */
+  @Override
+  public void onTimeout(Interest interest) {
+    notify(new Exception("Interest timed out: " + interest.getName().toUri()));
+  }
+
+  /**
+   * Handle incoming interests
+   * 
+   * @param prefix
+   * @param interest
+   * @param transport
+   * @param registeredPrefixId 
+   */
+  @Override
+  public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+    notify(interest); // TODO wrap
+  }
+
+  /**
+   * Helper to reference both outgoing interest and incoming data packets
+   */
+  class InterestDataPacket {
+
+    private Interest interest;
+    private Data data;
+
+    public InterestDataPacket(Interest interest, Data data) {
+      this.interest = interest;
+      this.data = data;
+    }
+
+    public Data getData() {
+      return data;
+    }
+
+    public Interest getInterest() {
+      return interest;
+    }
+  }
+  
+  /**
+   * Helper to reference both incoming interest and the transport to send data on
+   */
+  class InterestTransportPacket{
+    private Interest interest;
+    private Transport transport;
+
+    public InterestTransportPacket(Interest interest, Transport transport) {
+      this.interest = interest;
+      this.transport = transport;
+    }
+
+    public Interest getInterest() {
+      return interest;
+    }
+
+    public Transport getTransport() {
+      return transport;
+    }
+    
+  }
+}
diff --git a/src/main/java/com/intel/jndn/utils/NDNObserver.java b/src/main/java/com/intel/jndn/utils/NDNObserver.java
new file mode 100644
index 0000000..5b9ddd9
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/NDNObserver.java
@@ -0,0 +1,179 @@
+/*
+ * File name: NDNObserver.java
+ * 
+ * Purpose: Track asynchronous events from Client and Server
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+
+/**
+ * Track asynchronous events from Client and Server
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NDNObserver implements Observer {
+
+  protected List<NDNEvent> events = new ArrayList<>();
+  protected long timestamp;
+  protected OnEvent then;
+  protected boolean stopThread;
+
+  /**
+   * Constructor
+   */
+  public NDNObserver() {
+    timestamp = System.currentTimeMillis();
+  }
+
+  /**
+   * Receive notifications from observables
+   *
+   * @param o
+   * @param arg
+   */
+  @Override
+  public void update(Observable o, Object arg) {
+    NDNEvent event = (NDNEvent) arg;
+    events.add(event);
+    // call onData callbacks
+    if (Data.class.isInstance(event.packet) && then != null) {
+      then.onEvent(event);
+    }
+  }
+
+  /**
+   * Register a handler for events
+   *
+   * @param callback
+   * @return
+   */
+  public NDNObserver then(OnEvent callback) {
+    then = callback;
+    return this;
+  }
+
+  /**
+   * Count the number of eventCount observed
+   *
+   * @return
+   */
+  public int eventCount() {
+    return events.size();
+  }
+
+  /**
+   * Count the number of interest packets observed (received or sent)
+   *
+   * @return
+   */
+  public int interestCount() {
+    return count(Interest.class);
+  }
+
+  /**
+   * Count the number of Data packets observed
+   *
+   * @return
+   */
+  public int dataCount() {
+    return count(Data.class);
+  }
+
+  /**
+   * Count the number of errors observed
+   *
+   * @return
+   */
+  public int errorCount() {
+    return count(Exception.class);
+  }
+
+  /**
+   * Count the number of observed packets by type
+   *
+   * @param type
+   * @return
+   */
+  public int count(Class type) {
+    int count = 0;
+    for (NDNEvent event : events) {
+      if (type.isInstance(event.packet)) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  /**
+   * Calculate time elapsed since observer started observing until this method
+   * is called
+   *
+   * @return
+   */
+  public long getTimeSinceStart() {
+    if (getLast() != null) {
+      return getLast().getTimestamp() - timestamp;
+    }
+    return -1;
+  }
+
+  /**
+   * Retrieve a list of observed events
+   *
+   * @return event or null
+   */
+  public List<NDNEvent> getEvents() {
+    return events;
+  }
+
+  /**
+   * Retrieve the first event
+   *
+   * @return event or null
+   */
+  public NDNEvent getFirst() {
+    if (events.size() > 0) {
+      return events.get(0);
+    }
+    return null;
+  }
+
+  /**
+   * Retrieve the last event
+   *
+   * @return event or null
+   */
+  public NDNEvent getLast() {
+    if (events.size() > 0) {
+      return events.get(events.size() - 1);
+    }
+    return null;
+  }
+
+  /**
+   * Stop the current Client thread; used by asynchronous Client methods to
+   * stop the request/response thread
+   */
+  public void stop() {
+    stopThread = true;
+  }
+
+  /**
+   * Check the current stop status; used by asynchronous Client methods to
+   * stop the request/response thread
+   *
+   * @return
+   */
+  public boolean mustStop() {
+    return stopThread;
+  }
+}
diff --git a/src/main/java/com/intel/jndn/utils/OnEvent.java b/src/main/java/com/intel/jndn/utils/OnEvent.java
new file mode 100644
index 0000000..62bdd56
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/OnEvent.java
@@ -0,0 +1,20 @@
+/*
+ * File name: OnEvent.java
+ * 
+ * Purpose: Interface for registering generic event handlers
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+/**
+ * Interface for registering generic event handlers. E.g.: observer.then((event)
+ * => doSomething(event.getFirst()));
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface OnEvent {
+  public void onEvent(NDNEvent event);
+}
diff --git a/src/main/java/com/intel/jndn/utils/OnServeInterest.java b/src/main/java/com/intel/jndn/utils/OnServeInterest.java
new file mode 100644
index 0000000..0f235d6
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/OnServeInterest.java
@@ -0,0 +1,22 @@
+/*
+ * File name: OnServeInterest.java
+ * 
+ * Purpose: Functional interface for serving data from Server.on()
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+
+/**
+ * Functional interface for serving data from Server.on()
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface OnServeInterest {
+    public Data onInterest(Name prefix, Interest interest);
+}
diff --git a/src/main/java/com/intel/jndn/utils/Server.java b/src/main/java/com/intel/jndn/utils/Server.java
new file mode 100644
index 0000000..2f58ac9
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/Server.java
@@ -0,0 +1,349 @@
+/*
+ * File name: Server.java
+ * 
+ * Purpose: Provide a server to simplify serving data over the NDN
+ * network.
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import java.io.IOException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnInterest;
+import net.named_data.jndn.OnRegisterFailed;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.security.KeyChain;
+import net.named_data.jndn.transport.Transport;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Provide a server to simplify serving data over the NDN network. Exposes two
+ * main methods: put() for serving static, known data packets and on() for
+ * serving dynamically created packets on-demand.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class Server {
+
+  public static final long DEFAULT_SLEEP_TIME = 20;
+  public static final long DEFAULT_TIMEOUT = 2000;
+  private static final Logger logger = LogManager.getLogger();
+  private static Server defaultInstance;
+  private KeyChain keyChain;
+  private Name certificateName;
+
+  /**
+   * Singleton access for simpler server use
+   *
+   * @return
+   */
+  public static Server getDefault() {
+    if (defaultInstance == null) {
+      defaultInstance = new Server();
+    }
+    return defaultInstance;
+  }
+
+  /**
+   * Constructor
+   */
+  public Server() {
+    // no signing
+  }
+
+  /**
+   * Constructor; enables signing
+   *
+   * @param keyChain
+   * @param certificateName
+   */
+  public Server(KeyChain keyChain, Name certificateName) {
+    this.keyChain = keyChain;
+    this.certificateName = certificateName;
+  }
+
+  /**
+   * Synchronously serve a Data on the given face until one request accesses the
+   * data; will return incoming Interest request.
+   * <pre><code> Interest request = Client.putSync(face, data); </code></pre>
+   *
+   * @param face
+   * @param data
+   * @return
+   */
+  public Interest putSync(Face face, final Data data) {
+    // setup event
+    long startTime = System.currentTimeMillis();
+    final String dataName = data.getName().toUri();
+    final NDNEvent event = new NDNEvent();
+
+    // setup flags
+    ForwardingFlags flags = new ForwardingFlags();
+    flags.setCapture(true);
+
+    // register the data name on the face
+    try {
+      face.registerPrefix(data.getName(), new OnInterest() {
+        @Override
+        public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+          // sign packet
+          if (keyChain != null) {
+            try {
+              keyChain.sign(data, certificateName != null ? certificateName : keyChain.getDefaultCertificateName());
+            } catch (net.named_data.jndn.security.SecurityException e) {
+              logger.error("Failed to sign data for: " + dataName, e);
+              event.fromPacket(e);
+            }
+          }
+
+          // send packet
+          try {
+            transport.send(data.wireEncode().buf());
+            logger.debug("Sent data: " + dataName);
+            event.fromPacket(interest);
+          } catch (IOException e) {
+            logger.error("Failed to send data for: " + dataName);
+            event.fromPacket(e);
+          }
+        }
+      }, new OnRegisterFailed() {
+        @Override
+        public void onRegisterFailed(Name prefix) {
+          event.fromPacket(new Exception("Failed to register name: " + dataName));
+        }
+      }, flags);
+      logger.info("Registered data: " + dataName);
+    } catch (IOException e) {
+      logger.error("Could not connect to face to register prefix: " + dataName, e);
+      event.fromPacket(e);
+    } catch (net.named_data.jndn.security.SecurityException e) {
+      logger.error("Error registering prefix: " + dataName, e);
+      event.fromPacket(e);
+    }
+
+    // process eventCount until one response is sent or error
+    while (event.getPacket() == null) {
+      try {
+        synchronized (face) {
+          face.processEvents();
+        }
+      } catch (IOException | EncodingException e) {
+        logger.warn("Failed to process events.", e);
+        event.fromPacket(e);
+      }
+      sleep();
+    }
+
+    // return
+    logger.debug("Request time (ms): " + (event.getTimestamp() - startTime));
+    return (event.isSuccess()) ? (Interest) event.getPacket() : null;
+  }
+
+  /**
+   * Asynchronously serve a Data on the given face until an observer stops it.
+   * E.g.: NDNObserver observer = Client.put(face, data); // when finished
+   * serving the data, stop the background thread observer.stop();
+   *
+   * @param face
+   * @param data
+   * @return
+   */
+  public NDNObserver put(final Face face, final Data data) {
+    // setup observer
+    final NDNObserver observer = new NDNObserver();
+    final NDNObservable eventHandler = new NDNObservable();
+    eventHandler.addObserver(observer);
+
+    // setup interest handler
+    final OnInterest interestHandler = new OnInterest() {
+      @Override
+      public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+        // sign packet
+        if (keyChain != null) {
+          try {
+            keyChain.sign(data, certificateName != null ? certificateName : keyChain.getDefaultCertificateName());
+          } catch (net.named_data.jndn.security.SecurityException e) {
+            logger.error("Failed to sign data for: " + data.getName().toUri(), e);
+            eventHandler.notify(e);
+          }
+        }
+
+        // send packet
+        try {
+          transport.send(data.wireEncode().buf());
+        } catch (IOException e) {
+          logger.error("Failed to send data for: " + data.getName().toUri());
+          eventHandler.notify(e);
+        }
+      }
+    };
+
+    // setup failure handler
+    final OnRegisterFailed failureHandler = new OnRegisterFailed() {
+      @Override
+      public void onRegisterFailed(Name prefix) {
+        logger.error("Failed to register name to put: " + data.getName().toUri());
+        eventHandler.notify(new Exception("Failed to register name to put: " + data.getName().toUri()));
+      }
+    };
+
+    // setup forwarding flags
+    final ForwardingFlags flags = new ForwardingFlags();
+    flags.setCapture(true); // no shorter routes will answer for this prefix, see http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#Route-inheritance
+    flags.setChildInherit(false); // the interest name must be exact, no child components after the prefix
+
+    // start background thread
+    Thread backgroundThread = new Thread(new Runnable() {
+      @Override
+      public void run() {
+        // register name on the face
+        try {
+          face.registerPrefix(data.getName(), interestHandler, failureHandler, flags);
+          logger.info("Registered data : " + data.getName().toUri());
+        } catch (IOException e) {
+          logger.error("Could not connect to face to register prefix: " + data.getName().toUri(), e);
+          eventHandler.notify(e);
+        } catch (net.named_data.jndn.security.SecurityException e) {
+          logger.error("Error registering prefix: " + data.getName().toUri(), e);
+          eventHandler.notify(e);
+        }
+
+        // process eventCount until a request is received
+        while (observer.interestCount() == 0 && observer.errorCount() == 0 && !observer.mustStop()) {
+          try {
+            synchronized (face) {
+              face.processEvents();
+            }
+          } catch (IOException | EncodingException e) {
+            logger.warn("Failed to process events.", e);
+            eventHandler.notify(e);
+          }
+          sleep();
+        }
+      }
+    });
+    backgroundThread.setName(String.format("Server.put(%s)", data.getName().toUri()));
+    backgroundThread.setDaemon(true);
+    backgroundThread.start();
+
+    return observer;
+  }
+
+  /**
+   * Register a prefix on the face to serve Data packets for incoming Interests.
+   * This method will create a background thread to process events until the
+   * user calls stop() on the returned observer
+   *
+   * @param face
+   * @param prefix
+   * @param handler
+   * @return
+   */
+  public NDNObserver on(final Face face, final Name prefix, final OnServeInterest handler) {
+    // setup observer
+    final NDNObserver observer = new NDNObserver();
+    final NDNObservable eventHandler = new NDNObservable();
+    eventHandler.addObserver(observer);
+
+    // setup interest handler
+    final OnInterest interestHandler = new OnInterest() {
+      @Override
+      public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+        // notify observers of interest received
+        eventHandler.notify(interest);
+
+        // grab data from OnServeInterest handler
+        Data data = handler.onInterest(prefix, interest);
+
+        // sign packet
+        if (keyChain != null) {
+          try {
+            keyChain.sign(data, certificateName != null ? certificateName : keyChain.getDefaultCertificateName());
+          } catch (net.named_data.jndn.security.SecurityException e) {
+            logger.error("Failed to sign data for: " + interest.getName().toUri(), e);
+            eventHandler.notify(e);
+          }
+        }
+
+        // send packet
+        try {
+          transport.send(data.wireEncode().buf());
+          eventHandler.notify(data); // notify observers of data sent
+        } catch (IOException e) {
+          logger.error("Failed to send data for: " + interest.getName().toUri());
+          eventHandler.notify(e);
+        }
+      }
+    };
+
+    // setup failure handler
+    final OnRegisterFailed failureHandler = new OnRegisterFailed() {
+      @Override
+      public void onRegisterFailed(Name prefix) {
+        logger.error("Failed to register name to put: " + prefix.toUri());
+        eventHandler.notify(new Exception("Failed to register name to put: " + prefix.toUri()));
+      }
+    };
+
+    // setup forwarding flags
+    final ForwardingFlags flags = new ForwardingFlags();
+    flags.setCapture(true); // no shorter routes will answer for this prefix, see http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#Route-inheritance
+    flags.setChildInherit(true); // the interest name may have child components after the prefix
+
+    // start background thread
+    Thread backgroundThread = new Thread(new Runnable() {
+      @Override
+      public void run() {
+        // register name on the face
+        try {
+          face.registerPrefix(prefix, interestHandler, failureHandler, flags);
+          logger.info("Registered data : " + prefix.toUri());
+        } catch (IOException e) {
+          logger.error("Could not connect to face to register prefix: " + prefix.toUri(), e);
+          eventHandler.notify(e);
+        } catch (net.named_data.jndn.security.SecurityException e) {
+          logger.error("Error registering prefix: " + prefix.toUri(), e);
+          eventHandler.notify(e);
+        }
+
+        // process events until told to stop or error bubbles up
+        while (observer.errorCount() == 0 && !observer.mustStop()) {
+          try {
+            synchronized (face) {
+              face.processEvents();
+            }
+          } catch (IOException | EncodingException e) {
+            logger.warn("Failed to process events.", e);
+            eventHandler.notify(e);
+          }
+          sleep();
+        }
+      }
+    });
+    backgroundThread.setName(String.format("Server.on(%s)", prefix.toUri()));
+    backgroundThread.setDaemon(true);
+    backgroundThread.start();
+
+    return observer;
+  }
+
+  /**
+   * Put the current thread to sleep to allow time for IO
+   */
+  protected void sleep() {
+    try {
+      Thread.currentThread().sleep(DEFAULT_SLEEP_TIME);
+    } catch (InterruptedException e) {
+      logger.error("Event loop interrupted.", e);
+    }
+  }
+
+}
diff --git a/src/main/resources/log4j2-test.xml b/src/main/resources/log4j2-test.xml
new file mode 100644
index 0000000..badfbef
--- /dev/null
+++ b/src/main/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+	<Appenders>
+		<Console name="Console" target="SYSTEM_OUT">
+			<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+		</Console>
+	</Appenders>
+	<Loggers>
+		<Root level="trace">
+			<AppenderRef ref="Console"/>
+		</Root>
+	</Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..66435f2
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+	<Appenders>
+		<Console name="Console" target="SYSTEM_OUT">
+			<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+		</Console>
+	</Appenders>
+	<Loggers>
+		<Root level="warn">
+			<AppenderRef ref="Console"/>
+		</Root>
+	</Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/src/test/java/com/intel/jndn/utils/ClientTest.java b/src/test/java/com/intel/jndn/utils/ClientTest.java
new file mode 100644
index 0000000..88c39cc
--- /dev/null
+++ b/src/test/java/com/intel/jndn/utils/ClientTest.java
@@ -0,0 +1,139 @@
+/*
+ * File name: ClientTest.java
+ * 
+ * Purpose: Test Client.java
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import com.intel.jndn.mock.MockTransport;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Test Client.java
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ClientTest {
+
+  /**
+   * Setup logging
+   */
+  private static final Logger logger = LogManager.getLogger();
+
+  /**
+   * Test retrieving data synchronously
+   */
+  @Test
+  public void testGetSync() {
+    // setup face
+    MockTransport transport = new MockTransport();
+    Face face = new Face(transport, null);
+
+    // setup return data
+    Data response = new Data(new Name("/test/sync"));
+    response.setContent(new Blob("..."));
+    transport.respondWith(response);
+
+    // retrieve data
+    logger.info("Client expressing interest synchronously: /test/sync");
+    Client client = new Client();
+    Data data = client.getSync(face, new Name("/test/sync"));
+    assertEquals(new Blob("...").buf(), data.getContent().buf());
+  }
+
+  /**
+   * Test retrieving data asynchronously
+   *
+   * @throws InterruptedException
+   */
+  @Test
+  public void testGetAsync() throws InterruptedException {
+    // setup face
+    MockTransport transport = new MockTransport();
+    Face face = new Face(transport, null);
+
+    // setup return data
+    Data response = new Data(new Name("/test/async"));
+    response.setContent(new Blob("..."));
+    transport.respondWith(response);
+
+    // retrieve data
+    logger.info("Client expressing interest asynchronously: /test/async");
+    Client client = new Client();
+    NDNObserver observer = client.get(face, new Name("/test/async"));
+
+    // wait 
+    while (observer.eventCount() == 0) {
+      Thread.sleep(10);
+    }
+    assertEquals(1, observer.eventCount());
+    assertEquals(1, observer.dataCount());
+    Data data = (Data) observer.getFirst().getPacket();
+    assertEquals(new Blob("...").buf(), data.getContent().buf());
+  }
+
+  /**
+   * Test that asynchronous client times out correctly
+   * 
+   * @throws InterruptedException 
+   */
+  @Test
+  public void testTimeout() throws InterruptedException {
+    // setup face
+    MockTransport transport = new MockTransport();
+    Face face = new Face(transport, null);
+
+    // retrieve non-existent data, should timeout
+    logger.info("Client expressing interest asynchronously: /test/timeout");
+    NDNObserver observer = Client.getDefault().get(face, new Name("/test/timeout"));
+
+    // wait 
+    while (observer.errorCount() == 0) {
+      Thread.sleep(100);
+    }
+    Exception e = (Exception) observer.getFirst().getPacket();
+    assertEquals(1, observer.errorCount());
+  }
+  
+  /**
+   * Test that callback is called on event
+   * @throws InterruptedException 
+   */
+  @Test
+  public void testCallback() throws InterruptedException {
+    // setup face
+    MockTransport transport = new MockTransport();
+    Face face = new Face(transport, null);
+    
+    // setup return data
+    Data response = new Data(new Name("/test/callback"));
+    response.setContent(new Blob("..."));
+    transport.respondWith(response);
+
+    // retrieve non-existent data, should timeout
+    logger.info("Client expressing interest asynchronously: /test/callback");
+    NDNObserver observer = Client.getDefault().get(face, new Name("/test/callback"));
+    observer.then(new OnEvent(){
+      @Override
+      public void onEvent(NDNEvent event) {
+        assertEquals(new Blob("...").buf(), ((Data) event.getPacket()).getContent().buf());
+      }
+    });
+
+    // wait 
+    while (observer.eventCount() == 0) {
+      Thread.sleep(100);
+    }
+    assertEquals(1, observer.eventCount());
+  }
+}
diff --git a/src/test/java/com/intel/jndn/utils/ServerTest.java b/src/test/java/com/intel/jndn/utils/ServerTest.java
new file mode 100644
index 0000000..a2b1880
--- /dev/null
+++ b/src/test/java/com/intel/jndn/utils/ServerTest.java
@@ -0,0 +1,58 @@
+/*
+ * File name: ServerTest.java
+ * 
+ * Purpose: Test Server.java 
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.mock.MockTransport;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Test Server.java
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ServerTest {
+
+  private static final Logger logger = LogManager.getLogger();
+
+  /**
+   * Test on functionality
+   * TODO more comprehensive tests with InternalFace
+   * @throws java.lang.InterruptedException
+   */
+  @Test
+  public void testOn() throws InterruptedException {
+    // setup face
+    MockTransport transport = new MockTransport();
+    Face face = new Face(transport, null);
+
+    // setup server
+    NDNObserver observer = Server.getDefault().on(face, new Name("/test/server/on"), new OnServeInterest() {
+      @Override
+      public Data onInterest(Name prefix, Interest interest) {
+        Data data = new Data(interest.getName());
+        data.setContent(new Blob("..."));
+        return data;
+      }
+    });
+    
+    // wait for background threads to run
+    Thread.sleep(100);
+
+    // check
+    assertEquals(1, transport.getSentInterestPackets().size());
+  }
+}