dependencies: Temporary solution to version incompatibility of jndn-utils and jndn-management

This commits adds modified versions of jndn-utils and jndn-management
to address version incompatibility (wire format for status datasets has
been changed).

Change-Id: Ic321c7a478b47961f0c4523e89ef8013593b14d3
diff --git a/app/build.gradle b/app/build.gradle
index 41a6db8..57b585b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -133,16 +133,18 @@
     compile 'com.android.support:appcompat-v7:21.0.3'
     compile 'com.android.support:support-v4:21.0.3'
 
-    compile('com.intel.jndn.utils:jndn-utils:0.9.7') {
-      exclude group: 'com.intel.jndn.mock', module: 'jndn-mock'
-      exclude group: 'net.named-data', module: 'jndn'
-    }
+    /// Temporarily, the modified code is embedded within NFD app
+    //
+    // compile('com.intel.jndn.utils:jndn-utils:0.9.7') {
+    //   exclude group: 'com.intel.jndn.mock', module: 'jndn-mock'
+    //   exclude group: 'net.named-data', module: 'jndn'
+    // }
 
-    compile('com.intel.jndn.management:jndn-management:0.9.7') {
-      exclude group: 'net.named-data', module: 'jndn'
-      exclude group: 'com.intel.jndn.utils', module: 'jndn-utils'
-      exclude group: 'com.intel.jndn.mock', module: 'jndn-mock'
-    }
+    // compile('com.intel.jndn.management:jndn-management:0.9.7') {
+    //   exclude group: 'net.named-data', module: 'jndn'
+    //   exclude group: 'com.intel.jndn.utils', module: 'jndn-utils'
+    //   exclude group: 'com.intel.jndn.mock', module: 'jndn-mock'
+    // }
 
     // compile('net.named-data:jndn-android:0.7') {
     compile('net.named-data:jndn:0.4') {
diff --git a/app/src/main/java/com/intel/jndn/management/EncodingHelper.java b/app/src/main/java/com/intel/jndn/management/EncodingHelper.java
new file mode 100644
index 0000000..5d658ee
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/EncodingHelper.java
@@ -0,0 +1,144 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management;
+
+import java.nio.ByteBuffer;
+import net.named_data.jndn.ControlParameters;
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.Tlv;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Provide helper methods to cover areas too protected in Tlv0_1_1WireFormat;
+ * this class can be deprecated if WireFormats allow passing in an existing
+ * TlvEncoder/TlvDecoder (currently these methods are protected).
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class EncodingHelper {
+
+  /**
+   * Helper to decode names since Tlv0_1_1WireFormat.java uses its own internal,
+   * protected implementation.
+   *
+   * @param input
+   * @return
+   * @throws EncodingException
+   */
+  public static Name decodeName(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    return decodeName(decoder);
+  }
+
+  /**
+   * Helper to decode names using an existing decoding context; could be merged
+   * to Tlv0_1_1WireFormat.java.
+   *
+   * @param decoder
+   * @return
+   * @throws EncodingException
+   */
+  public static Name decodeName(TlvDecoder decoder) throws EncodingException {
+    Name name = new Name();
+    int endOffset = decoder.readNestedTlvsStart(Tlv.Name);
+    while (decoder.getOffset() < endOffset) {
+      name.append(new Blob(decoder.readBlobTlv(Tlv.NameComponent), true));
+    }
+
+    decoder.finishNestedTlvs(endOffset);
+    return name;
+  }
+
+  /**
+   * Helper to encode names since Tlv0_1_1WireFormat.java uses its own internal,
+   * protected implementation.
+   *
+   * @param name
+   * @return
+   */
+  public static Blob encodeName(Name name) {
+    TlvEncoder encoder = new TlvEncoder();
+    encodeName(name, encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Helper to encode names using an existing encoding context; could be merged
+   * to Tlv0_1_1WireFormat.java.
+   *
+   * @param name
+   * @param encoder
+   */
+  public static final void encodeName(Name name, TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    for (int i = name.size() - 1; i >= 0; --i) {
+      encoder.writeBlobTlv(Tlv.NameComponent, name.get(i).getValue().buf());
+    }
+    encoder.writeTypeAndLength(Tlv.Name, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Helper to encode control parameters using an existing encoding context;
+   * could be merged to Tlv0_1_1WireFormat.java.
+   *
+   * @param controlParameters
+   * @param encoder
+   */
+  public static final void encodeControlParameters(ControlParameters controlParameters, TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+
+    // Encode backwards.
+    encoder.writeOptionalNonNegativeIntegerTlvFromDouble(Tlv.ControlParameters_ExpirationPeriod,
+            controlParameters.getExpirationPeriod());
+
+    // Encode strategy
+    if (controlParameters.getStrategy().size() != 0) {
+      int strategySaveLength = encoder.getLength();
+      encodeName(controlParameters.getStrategy(), encoder);
+      encoder.writeTypeAndLength(Tlv.ControlParameters_Strategy,
+              encoder.getLength() - strategySaveLength);
+    }
+
+    // Encode ForwardingFlags
+    int flags = controlParameters.getForwardingFlags().getNfdForwardingFlags();
+    if (flags != new ForwardingFlags().getNfdForwardingFlags()) // The flags are not the default value.
+    {
+      encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_Flags, flags);
+    }
+
+    encoder.writeOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_Cost, controlParameters.getCost());
+    encoder.writeOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_Origin, controlParameters.getOrigin());
+    encoder.writeOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_LocalControlFeature,
+            controlParameters.getLocalControlFeature());
+
+    // Encode URI
+    if (!controlParameters.getUri().isEmpty()) {
+      encoder.writeBlobTlv(Tlv.ControlParameters_Uri,
+              new Blob(controlParameters.getUri()).buf());
+    }
+
+    encoder.writeOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_FaceId, controlParameters.getFaceId());
+
+    // Encode name
+    if (controlParameters.getName().size() != 0) {
+      encodeName(controlParameters.getName(), encoder);
+    }
+
+    encoder.writeTypeAndLength(Tlv.ControlParameters_ControlParameters, encoder.getLength() - saveLength);
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/management/ManagementException.java b/app/src/main/java/com/intel/jndn/management/ManagementException.java
new file mode 100644
index 0000000..70a15d1
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/ManagementException.java
@@ -0,0 +1,73 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management;
+
+import com.intel.jndn.management.types.ControlResponse;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a failure to correctly manage the NDN Forwarding Daemon (NFD).
+ * Inspect this object with getCause() to see why the management operation
+ * failed.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ManagementException extends Exception {
+
+  /**
+   *
+   * @param message
+   */
+  public ManagementException(String message) {
+    super(message);
+  }
+
+  /**
+   *
+   * @param message
+   * @param cause
+   */
+  public ManagementException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  /**
+   * Parse an NFD response to create a ManagementException.
+   *
+   * @param forwarderResponse
+   * @return
+   */
+  public static ManagementException fromResponse(Blob forwarderResponse) {
+    ControlResponse response = new ControlResponse();
+    try {
+      response.wireDecode(forwarderResponse.buf());
+      String message = "Action failed, forwarder returned: " + response.getStatusCode() + " " + response.getStatusText();
+      return new ManagementException(message);
+    } catch (EncodingException e) {
+      return new ManagementException("Action failed and forwarder response was unparseable.", e);
+    }
+  }
+
+  /**
+   * Parse an NFD response to create a ManagementException.
+   *
+   * @param forwarderResponse
+   * @return
+   */
+  public static ManagementException fromResponse(ControlResponse response) {
+    String message = "Action failed, forwarder returned: " + response.getStatusCode() + " " + response.getStatusText();
+    return new ManagementException(message);
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/management/NFD.java b/app/src/main/java/com/intel/jndn/management/NFD.java
new file mode 100644
index 0000000..b62cbe6
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/NFD.java
@@ -0,0 +1,520 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management;
+
+import com.intel.jndn.management.types.StatusDataset;
+import com.intel.jndn.management.types.ControlResponse;
+import com.intel.jndn.management.types.FaceStatus;
+import com.intel.jndn.management.types.FibEntry;
+import com.intel.jndn.management.types.ForwarderStatus;
+import com.intel.jndn.management.types.LocalControlHeader;
+import com.intel.jndn.management.types.RibEntry;
+import com.intel.jndn.utils.SimpleClient;
+import com.intel.jndn.utils.SegmentedClient;
+import java.io.IOException;
+import java.util.List;
+import net.named_data.jndn.ControlParameters;
+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.encoding.EncodingException;
+import net.named_data.jndn.security.SecurityException;
+import java.util.logging.Logger;
+
+import net.named_data.nfd.utils.G;
+
+/**
+ * Helper class for interacting with an NDN forwarder daemon; see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/Management">http://redmine.named-data.net/projects/nfd/wiki/Management</a>
+ * for explanations of the various protocols used.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NFD {
+
+  public final static long DEFAULT_TIMEOUT = 2000;
+  public final static int OK_STATUS = 200;
+  static private final Logger logger = Logger.getLogger(NFD.class.getName());
+
+  /**
+   * Ping a forwarder on an existing face to verify that the forwarder is
+   * working and responding to requests; this version sends a discovery packet
+   * to /localhost/nfd which should always respond if the requestor is on the
+   * same machine as the NDN forwarding daemon.
+   *
+   * @param face only a localhost Face
+   * @return true if successful, false otherwise
+   */
+  public static boolean pingLocal(Face face) {
+    return ping(face, new Name("/localhost/nfd"));
+  }
+
+  /**
+   * Request a name on an existing face to verify the forwarder is working and
+   * responding to requests. Note that the name must be served or cached on the
+   * forwarder for this to return true.
+   *
+   * @param face
+   * @param name
+   * @return true if successful, false otherwise
+   */
+  public static boolean ping(Face face, Name name) {
+    // build interest
+    Interest interest = new Interest(name);
+    interest.setInterestLifetimeMilliseconds(DEFAULT_TIMEOUT);
+    interest.setMustBeFresh(true);
+
+    // send packet
+    try {
+      Data data = SimpleClient.getDefault().getSync(face, interest);
+      return data != null;
+    } catch (IOException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Retrieve the status of the given forwarder; calls /localhost/nfd/status
+   * which requires a local Face (all non-local packets are dropped)
+   *
+   * @param forwarder only a localhost Face
+   * @return the forwarder status object, see
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/ForwarderStatus">
+   * http://redmine.named-data.net/projects/nfd/wiki/ForwarderStatus</a>.
+   * @throws java.lang.Exception
+   */
+  public static ForwarderStatus getForwarderStatus(Face forwarder) throws Exception {
+    Interest interest = new Interest(new Name("/localhost/nfd/status"));
+    interest.setMustBeFresh(true);
+    interest.setChildSelector(Interest.CHILD_SELECTOR_RIGHT);
+    interest.setInterestLifetimeMilliseconds(DEFAULT_TIMEOUT);
+
+    Data data = SimpleClient.getDefault().getSync(forwarder, interest);
+    G.Log("-----------" + data.getName().toUri());
+    ForwarderStatus status = new ForwarderStatus();
+    status.wireDecode(data.getContent().buf());
+    G.Log("----------- done");
+    return status;
+  }
+
+  /**
+   * Retrieve a list of faces and their status from the given forwarder; calls
+   * /localhost/nfd/faces/list which requires a local Face (all non-local
+   * packets are dropped)
+   *
+   * @param forwarder only a localhost Face
+   * @return a list of face status objects, see
+   * http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt.
+   * @throws java.lang.Exception
+   */
+  public static List<FaceStatus> getFaceList(Face forwarder) throws Exception {
+    Data data = retrieveDataSet(forwarder, new Name("/localhost/nfd/faces/list"));
+    return StatusDataset.wireDecode(data.getContent(), FaceStatus.class);
+  }
+
+  /**
+   * Retrieve a list of FIB entries and their NextHopRecords from the given
+   * forwarder; calls /localhost/nfd/fib/list which requires a local Face (all
+   * non-local packets are dropped).
+   *
+   * @param forwarder only a localhost Face
+   * @return a list of FIB entries, see
+   * http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#FIB-Dataset.
+   * @throws java.lang.Exception
+   */
+  public static List<FibEntry> getFibList(Face forwarder) throws Exception {
+    Data data = retrieveDataSet(forwarder, new Name("/localhost/nfd/fib/list"));
+    return StatusDataset.wireDecode(data.getContent(), FibEntry.class);
+  }
+
+  /**
+   * Retrieve a list of routing entries from the RIB; calls
+   * /localhost/nfd/rib/list which requires a local Face (all non-local packets
+   * are dropped).
+   *
+   * @param forwarder only a localhost Face
+   * @return a list of RIB entries, i.e. routes, see
+   * http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#RIB-Dataset.
+   * @throws java.lang.Exception
+   */
+  public static List<RibEntry> getRouteList(Face forwarder) throws Exception {
+    Data data = retrieveDataSet(forwarder, new Name("/localhost/nfd/rib/list"));
+    return StatusDataset.wireDecode(data.getContent(), RibEntry.class);
+  }
+
+  /**
+   * Helper method to register a new face on the forwarder; as mentioned at
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>,
+   * this is more for debugging; use 'register' instead
+   *
+   * @param forwarder only a localhost Face
+   * @param faceId
+   * @param prefix
+   * @throws java.lang.Exception
+   */
+  public static void addNextHop(Face forwarder, int faceId, Name prefix) throws Exception {
+    // build command name
+    Name command = new Name("/localhost/nfd/fib/add-nexthop");
+    ControlParameters parameters = new ControlParameters();
+    parameters.setName(prefix);
+    parameters.setFaceId(faceId);
+    command.append(parameters.wireEncode());
+
+    // send the interest
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Create a new face on the given forwarder. Ensure the forwarding face is on
+   * the local machine (management requests are to /localhost/...) and that
+   * command signing has been set up (e.g. forwarder.setCommandSigningInfo()).
+   *
+   * @param forwarder only a localhost Face
+   * @param uri
+   * @return
+   * @throws java.lang.Exception
+   */
+  public static int createFace(Face forwarder, String uri) throws Exception {
+    Name command = new Name("/localhost/nfd/faces/create");
+    ControlParameters parameters = new ControlParameters();
+    parameters.setUri(uri);
+    command.append(parameters.wireEncode());
+
+    // send the interest
+    ControlResponse response = sendCommand(forwarder, new Interest(command));
+
+    // return
+    return response.getBody().get(0).getFaceId();
+  }
+
+  /**
+   * Destroy a face on given forwarder. Ensure the forwarding face is on the
+   * local machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()).
+   *
+   * @param forwarder only a localhost Face
+   * @param faceId
+   * @throws java.lang.Exception
+   */
+  public static void destroyFace(Face forwarder, int faceId) throws Exception {
+    Name command = new Name("/localhost/nfd/faces/destroy");
+    ControlParameters parameters = new ControlParameters();
+    parameters.setFaceId(faceId);
+    command.append(parameters.wireEncode());
+
+    // send the interest
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Enable a local control feature on the given forwarder. See
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Enable-a-LocalControlHeader-feature">http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Enable-a-LocalControlHeader-feature</a>
+   *
+   * @param forwarder
+   * @param header
+   * @throws Exception
+   */
+  public static void enableLocalControlHeader(Face forwarder, LocalControlHeader header) throws Exception {
+    // build command name
+    Name command = new Name("/localhost/nfd/faces/enable-local-control");
+    ControlParameters parameters = new ControlParameters();
+    parameters.setLocalControlFeature(header.getNumericValue());
+    command.append(parameters.wireEncode());
+
+    // send command and return
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Disable a local control feature on the given forwarder. See
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Disable-a-LocalControlHeader-feature">http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Disable-a-LocalControlHeader-feature</a>
+   *
+   * @param forwarder
+   * @param header
+   * @throws Exception
+   */
+  public static void disableLocalControlHeader(Face forwarder, LocalControlHeader header) throws Exception {
+    // build command name
+    Name command = new Name("/localhost/nfd/faces/disable-local-control");
+    ControlParameters parameters = new ControlParameters();
+    parameters.setLocalControlFeature(header.getNumericValue());
+    command.append(parameters.wireEncode());
+
+    // send command and return
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Register a route on the forwarder; see
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>
+   * for command-line usage and
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt</a>
+   * for protocol documentation. Ensure the forwarding face is on the local
+   * machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()).
+   *
+   * @param forwarder only a localhost Face
+   * @param controlParameters
+   * @throws Exception
+   */
+  public static void register(Face forwarder, ControlParameters controlParameters) throws Exception {
+    // build command name
+    Name command = new Name("/localhost/nfd/rib/register");
+    command.append(controlParameters.wireEncode());
+
+    // send the interest
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Register a route on a forwarder; this will create a new face on the
+   * forwarder to the given URI/route pair. See register(Face,
+   * ControlParameters) for more details documentation.
+   *
+   * @param forwarder only a localhost Face
+   * @param uri
+   * @param cost
+   * @param route
+   * @throws java.lang.Exception
+   */
+  public static void register(Face forwarder, String uri, Name route, int cost) throws Exception {
+    // create the new face
+    int faceId = createFace(forwarder, uri);
+
+    // run base method
+    register(forwarder, faceId, route, cost);
+  }
+
+  /**
+   * Register a route on a forwarder; this will not create a new face since it
+   * is provided a faceId. See register(Face, ControlParameters) for full
+   * documentation
+   *
+   * @param forwarder only a localhost Face
+   * @param faceId
+   * @param route
+   * @param cost
+   * @throws java.lang.Exception
+   */
+  public static void register(Face forwarder, int faceId, Name route, int cost) throws Exception {
+    // build command name
+    ControlParameters parameters = new ControlParameters();
+    parameters.setName(route);
+    parameters.setFaceId(faceId);
+    parameters.setCost(cost);
+    ForwardingFlags flags = new ForwardingFlags();
+    flags.setCapture(true);
+    flags.setChildInherit(true);
+    parameters.setForwardingFlags(flags);
+
+    // run base method
+    register(forwarder, parameters);
+  }
+
+  /**
+   * Unregister a route on a forwarder; see
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>
+   * for command-line usage and
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt</a>
+   * for protocol documentation. Ensure the forwarding face is on the local
+   * machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()
+   *
+   * @param forwarder
+   * @param controlParameters
+   * @throws java.lang.Exception
+   */
+  public static void unregister(Face forwarder, ControlParameters controlParameters) throws Exception {
+    // build command name
+    Name command = new Name("/localhost/nfd/rib/unregister");
+    command.append(controlParameters.wireEncode());
+
+    // send the interest
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Unregister a route on a forwarder; see
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>
+   * for command-line usage and
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt</a>
+   * for protocol documentation. Ensure the forwarding face is on the local
+   * machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()
+   *
+   * @param forwarder
+   * @param route
+   * @throws java.lang.Exception
+   */
+  public static void unregister(Face forwarder, Name route) throws Exception {
+    // build command name
+    ControlParameters controlParameters = new ControlParameters();
+    controlParameters.setName(route);
+
+    // send the interest
+    unregister(forwarder, controlParameters);
+  }
+
+  /**
+   * Unregister a route on a forwarder; see
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>
+   * for command-line usage and
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt</a>
+   * for protocol documentation. Ensure the forwarding face is on the local
+   * machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()
+   *
+   * @param forwarder
+   * @param route
+   * @param faceId
+   * @throws java.lang.Exception
+   */
+  public static void unregister(Face forwarder, Name route, int faceId) throws Exception {
+    // build command name
+    ControlParameters controlParameters = new ControlParameters();
+    controlParameters.setName(route);
+    controlParameters.setFaceId(faceId);
+
+    // send the interest
+    unregister(forwarder, controlParameters);
+  }
+
+  /**
+   * Unregister a route on a forwarder; see
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>
+   * for command-line usage and
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt</a>
+   * for protocol documentation. Ensure the forwarding face is on the local
+   * machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()
+   *
+   * @param forwarder
+   * @param route
+   * @param uri
+   * @throws java.lang.Exception
+   */
+  public static void unregister(Face forwarder, Name route, String uri) throws Exception {
+    int faceId = -1;
+    for (FaceStatus face : getFaceList(forwarder)) {
+      if (face.getUri().matches(uri)) {
+        faceId = face.getFaceId();
+        break;
+      }
+    }
+
+    if (faceId == -1) {
+      throw new ManagementException("Face not found: " + uri);
+    }
+
+    // send the interest
+    unregister(forwarder, route, faceId);
+  }
+
+  /**
+   * Set a strategy on the forwarder; see
+   * <a href="http://named-data.net/doc/NFD/current/manpages/nfdc.html">http://named-data.net/doc/NFD/current/manpages/nfdc.html</a>
+   * for command-line usage and
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/StrategyChoice">http://redmine.named-data.net/projects/nfd/wiki/StrategyChoice</a>
+   * for protocol documentation. Ensure the forwarding face is on the local
+   * machine (management requests are to /localhost/...) and that command
+   * signing has been set up (e.g. forwarder.setCommandSigningInfo()).
+   *
+   * @param forwarder only a localhost Face
+   * @param prefix
+   * @param strategy
+   * @throws Exception
+   */
+  public static void setStrategy(Face forwarder, Name prefix, Name strategy) throws Exception {
+    // build command name
+    Name command = new Name("/localhost/nfd/strategy-choice/set");
+    ControlParameters parameters = new ControlParameters();
+    parameters.setName(prefix);
+    parameters.setStrategy(strategy);
+    command.append(parameters.wireEncode());
+
+    // send the interest
+    sendCommand(forwarder, new Interest(command));
+  }
+
+  /**
+   * Build an interest to retrieve a segmented data set from the NFD; for
+   * details on the DataSet, see
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/StatusDataset">http://redmine.named-data.net/projects/nfd/wiki/StatusDataset</a>
+   *
+   * @param forwarder
+   * @param datasetName
+   * @return
+   * @throws IOException
+   * @throws ManagementException
+   */
+  public static Data retrieveDataSet(Face forwarder, Name datasetName) throws IOException, ManagementException {
+    // build management Interest packet; see <a href="http://redmine.named-data.net/projects/nfd/wiki/StatusDataset">http://redmine.named-data.net/projects/nfd/wiki/StatusDataset</a>
+    Interest interest = new Interest(datasetName);
+    interest.setMustBeFresh(true);
+    interest.setChildSelector(Interest.CHILD_SELECTOR_RIGHT);
+    interest.setInterestLifetimeMilliseconds(DEFAULT_TIMEOUT);
+
+    // send packet
+    Data data = SegmentedClient.getDefault().getSync(forwarder, interest);
+
+    // check for failed request
+    if (data.getContent().buf().get(0) == ControlResponse.TLV_CONTROL_RESPONSE) {
+      throw ManagementException.fromResponse(data.getContent());
+    }
+
+    return data;
+  }
+
+  /**
+   * Send an interest as a command to the forwarder; this method will convert
+   * the interest to a command interest and block until a response is received
+   * from the forwarder. Ensure the forwarding face is on the local machine
+   * (management requests are to /localhost/...) and that command signing has
+   * been set up (e.g. forwarder.setCommandSigningInfo()).
+   *
+   * @param forwarder only a localhost Face, command signing info must be set
+   * @param interest As described at
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/ControlCommand,">http://redmine.named-data.net/projects/nfd/wiki/ControlCommand,</a>
+   * the requested interest must have encoded ControlParameters appended to the
+   * interest name
+   * @return
+   * @throws java.io.IOException
+   * @throws net.named_data.jndn.encoding.EncodingException
+   * @throws com.intel.jndn.management.ManagementException
+   */
+  public static ControlResponse sendCommand(Face forwarder, Interest interest) throws IOException, EncodingException, ManagementException {
+    // forwarder must have command signing info set
+    try {
+      forwarder.makeCommandInterest(interest);
+    } catch (SecurityException e) {
+      throw new IllegalArgumentException("Failed to make command interest; ensure command signing info is set on the face.", e);
+    }
+
+    // send command packet
+    Data data = SimpleClient.getDefault().getSync(forwarder, interest);
+
+    // decode response
+    ControlResponse response = new ControlResponse();
+    response.wireDecode(data.getContent().buf());
+
+    // check response for success
+    if (response.getStatusCode() != OK_STATUS) {
+      throw ManagementException.fromResponse(response);
+    }
+
+    return response;
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/ControlResponse.java b/app/src/main/java/com/intel/jndn/management/types/ControlResponse.java
new file mode 100644
index 0000000..5514244
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/ControlResponse.java
@@ -0,0 +1,170 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import com.intel.jndn.management.EncodingHelper;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import net.named_data.jndn.ControlParameters;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.Tlv;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a ControlResponse, a Data packet sent in response to a
+ * ControlCommand to the NFD, see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/ControlCommand">http://redmine.named-data.net/projects/nfd/wiki/ControlCommand</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ControlResponse {
+
+  /**
+   * Use TLV codes from jndn.encoding.tlv.Tlv.java See
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/ControlCommand">http://redmine.named-data.net/projects/nfd/wiki/ControlCommand</a>
+   */
+  public final static int TLV_CONTROL_RESPONSE = 101;
+  public final static int TLV_CONTROL_RESPONSE_STATUS_CODE = 102;
+  public final static int TLV_CONTROL_RESPONSE_STATUS_TEXT = 103;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    for (ControlParameters parameters : body) {
+      EncodingHelper.encodeControlParameters(parameters, encoder);
+    }
+    encoder.writeBlobTlv(TLV_CONTROL_RESPONSE_STATUS_TEXT, new Blob(statusText).buf());
+    encoder.writeNonNegativeIntegerTlv(TLV_CONTROL_RESPONSE_STATUS_CODE, statusCode);
+    encoder.writeTypeAndLength(TLV_CONTROL_RESPONSE, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws net.named_data.jndn.encoding.EncodingException
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder, input);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @param input the WireFormat version that decodes ControlParameters does not
+   * allow passing a TlvDecoder, so we must pass the buffer itself
+   * @throws EncodingException
+   */
+  public void wireDecode(TlvDecoder decoder, ByteBuffer input) throws EncodingException {
+    int endOffset = decoder.readNestedTlvsStart(TLV_CONTROL_RESPONSE);
+
+    // parse known TLVs
+    this.statusCode = (int) decoder.readNonNegativeIntegerTlv(TLV_CONTROL_RESPONSE_STATUS_CODE);
+    Blob statusText_ = new Blob(decoder.readBlobTlv(TLV_CONTROL_RESPONSE_STATUS_TEXT), true); // copy because buffer is immutable
+    this.statusText = statusText_.toString();
+
+    // use the already-written decoder for ControlParameters (but we have to copy the buffer)
+    while (decoder.peekType(Tlv.ControlParameters_ControlParameters, endOffset)) {
+      ByteBuffer copyInput = input.duplicate();
+      copyInput.position(decoder.getOffset());
+      int internalEndOffset = decoder.readNestedTlvsStart(Tlv.ControlParameters_ControlParameters);
+      ControlParameters copyParameters = new ControlParameters();
+      copyParameters.wireDecode(copyInput);
+      this.body.add(copyParameters);
+      decoder.seek(internalEndOffset);
+      decoder.finishNestedTlvs(internalEndOffset);
+    }
+
+    decoder.finishNestedTlvs(endOffset);
+  }
+
+  /**
+   * Get status code
+   *
+   * @return
+   */
+  public int getStatusCode() {
+    return statusCode;
+  }
+
+  /**
+   * Set status code
+   *
+   * @param statusCode
+   */
+  public void setStatusCode(int statusCode) {
+    this.statusCode = statusCode;
+  }
+
+  /**
+   * Get status text
+   *
+   * @return
+   */
+  public String getStatusText() {
+    return statusText;
+  }
+
+  /**
+   * Set status text
+   *
+   * @param statusText
+   */
+  public void setStatusText(String statusText) {
+    this.statusText = statusText;
+  }
+
+  /**
+   * Get body
+   *
+   * @return
+   */
+  public List<ControlParameters> getBody() {
+    return body;
+  }
+
+  /**
+   * Set body
+   *
+   * @param body
+   */
+  public void setBody(List<ControlParameters> body) {
+    this.body = body;
+  }
+
+  private int statusCode = -1;
+  private String statusText = "";
+  private List<ControlParameters> body = new ArrayList<>();
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/Decodable.java b/app/src/main/java/com/intel/jndn/management/types/Decodable.java
new file mode 100644
index 0000000..aec50dd
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/Decodable.java
@@ -0,0 +1,28 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+
+/**
+ * Interface used by StatusDataset to decode generic message types; if they are
+ * Decodable, then StatusDataset will instantiate and decode them.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface Decodable {
+
+  public void wireDecode(TlvDecoder decoder) throws EncodingException;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/FacePersistency.java b/app/src/main/java/com/intel/jndn/management/types/FacePersistency.java
new file mode 100644
index 0000000..d294fcb
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/FacePersistency.java
@@ -0,0 +1,36 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+/**
+ * Indicate whether the face is persistent; used by FaceStatus. See
+ * <a href="http://redmine.named-data.net/projects/nfd/widi/FaceMgmt">http://redmine.named-data.net/projects/nfd/widi/FaceMgmt</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public enum FacePersistency {
+
+  PERSISTENT(0),
+  ON_DEMAND(1),
+  PERMANENT(2);
+
+  FacePersistency(int value) {
+    value_ = value;
+  }
+
+  public final int getNumericValue() {
+    return value_;
+  }
+  private final int value_;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/FaceScope.java b/app/src/main/java/com/intel/jndn/management/types/FaceScope.java
new file mode 100644
index 0000000..bcba855
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/FaceScope.java
@@ -0,0 +1,36 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+/**
+ * Indicate whether the face is local for scope control purposes; used by
+ * FaceStatus See
+ * <a href="http://redmine.named-data.net/projects/nfd/widi/FaceMgmt">http://redmine.named-data.net/projects/nfd/widi/FaceMgmt</a>
+ *
+ * @author andrew
+ */
+public enum FaceScope {
+
+  LOCAL(0),
+  NON_LOCAL(1);
+
+  FaceScope(int value) {
+    value_ = value;
+  }
+
+  public final int getNumericValue() {
+    return value_;
+  }
+  private final int value_;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/FaceStatus.java b/app/src/main/java/com/intel/jndn/management/types/FaceStatus.java
new file mode 100644
index 0000000..6a101a0
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/FaceStatus.java
@@ -0,0 +1,401 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import java.nio.ByteBuffer;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a FaceStatus object from /localhost/nfd/faces/list; see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt">http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt</a>
+ * for details
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class FaceStatus implements Decodable {
+
+  /**
+   * Spec from
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/ControlCommand">http://redmine.named-data.net/projects/nfd/wiki/ControlCommand</a>
+   */
+  public static final int TLV_FACE_ID = 105;
+  public static final int TLV_URI = 114;
+  public static final int TLV_EXPIRATION_PERIOD = 109;
+
+  /**
+   * Spec from
+   * <a href="http://redmine.named-data.net/projects/nfd/widi/FaceMgmt">http://redmine.named-data.net/projects/nfd/widi/FaceMgmt</a>
+   */
+  public static final int TLV_FACE_STATUS = 128;
+  public static final int TLV_LOCAL_URI = 129;
+  public static final int TLV_CHANNEL_STATUS = 130;
+  public static final int TLV_FACE_SCOPE = 132;
+  public static final int TLV_FACE_PERSISTENCY = 133;
+  public static final int TLV_LINK_TYPE = 134;
+  public static final int TLV_N_IN_INTERESTS = 144;
+  public static final int TLV_N_IN_DATAS = 145;
+  public static final int TLV_N_OUT_INTERESTS = 146;
+  public static final int TLV_N_OUT_DATAS = 147;
+  public static final int TLV_N_IN_BYTES = 148;
+  public static final int TLV_N_OUT_BYTES = 149;
+  public static final int TLV_NUM_IN_NACKS = 151;
+  public static final int TLV_NUM_OUT_NACKS = 152;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    encoder.writeNonNegativeIntegerTlv(TLV_N_OUT_BYTES, outBytes);
+    encoder.writeNonNegativeIntegerTlv(TLV_N_IN_BYTES, inBytes);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_OUT_NACKS, numOutNacks);
+    encoder.writeNonNegativeIntegerTlv(TLV_N_OUT_DATAS, outDatas);
+    encoder.writeNonNegativeIntegerTlv(TLV_N_OUT_INTERESTS, outInterests);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_IN_NACKS, numInNacks);
+    encoder.writeNonNegativeIntegerTlv(TLV_N_IN_DATAS, inDatas);
+    encoder.writeNonNegativeIntegerTlv(TLV_N_IN_INTERESTS, inInterests);
+    encoder.writeNonNegativeIntegerTlv(TLV_LINK_TYPE, linkType.getNumericValue());
+    encoder.writeNonNegativeIntegerTlv(TLV_FACE_PERSISTENCY, facePersistency.getNumericValue());
+    encoder.writeNonNegativeIntegerTlv(TLV_FACE_SCOPE, faceScope.getNumericValue());
+    encoder.writeOptionalNonNegativeIntegerTlv(TLV_EXPIRATION_PERIOD, expirationPeriod);
+    encoder.writeBlobTlv(TLV_LOCAL_URI, new Blob(localUri).buf());
+    encoder.writeBlobTlv(TLV_URI, new Blob(uri).buf());
+    encoder.writeNonNegativeIntegerTlv(TLV_FACE_ID, faceId);
+    encoder.writeTypeAndLength(TLV_FACE_STATUS, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws net.named_data.jndn.encoding.EncodingException
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @throws EncodingException
+   */
+  @Override
+  public void wireDecode(TlvDecoder decoder) throws EncodingException {
+    int endOffset = decoder.readNestedTlvsStart(TLV_FACE_STATUS);
+    // parse
+    this.faceId = (int) decoder.readNonNegativeIntegerTlv(TLV_FACE_ID);
+    Blob uri_ = new Blob(decoder.readBlobTlv(TLV_URI), true); // copy because buffer is immutable
+    this.uri = uri_.toString();
+    Blob localUri_ = new Blob(decoder.readBlobTlv(TLV_LOCAL_URI), true); // copy because buffer is immutable
+    this.localUri = localUri_.toString();
+    this.expirationPeriod = (int) decoder.readOptionalNonNegativeIntegerTlv(TLV_EXPIRATION_PERIOD, endOffset);
+    this.faceScope = FaceScope.values()[(int) decoder.readNonNegativeIntegerTlv(TLV_FACE_SCOPE)];
+    this.facePersistency = FacePersistency.values()[(int) decoder.readNonNegativeIntegerTlv(TLV_FACE_PERSISTENCY)];
+    this.linkType = LinkType.values()[(int) decoder.readNonNegativeIntegerTlv(TLV_LINK_TYPE)];
+    this.inInterests = (int) decoder.readNonNegativeIntegerTlv(TLV_N_IN_INTERESTS);
+    this.inDatas = (int) decoder.readNonNegativeIntegerTlv(TLV_N_IN_DATAS);
+    this.numInNacks = decoder.readNonNegativeIntegerTlv(TLV_NUM_IN_NACKS);
+    this.outInterests = (int) decoder.readNonNegativeIntegerTlv(TLV_N_OUT_INTERESTS);
+    this.outDatas = (int) decoder.readNonNegativeIntegerTlv(TLV_N_OUT_DATAS);
+    this.numOutNacks = decoder.readNonNegativeIntegerTlv(TLV_NUM_OUT_NACKS);
+    this.inBytes = (int) decoder.readNonNegativeIntegerTlv(TLV_N_IN_BYTES);
+    this.outBytes = (int) decoder.readNonNegativeIntegerTlv(TLV_N_OUT_BYTES);
+    decoder.finishNestedTlvs(endOffset);
+  }
+
+  /**
+   * Get face ID
+   *
+   * @return
+   */
+  public int getFaceId() {
+    return faceId;
+  }
+
+  /**
+   * Set face ID
+   *
+   * @param faceId
+   */
+  public void setFaceId(int faceId) {
+    this.faceId = faceId;
+  }
+
+  /**
+   * Get face ID
+   *
+   * @return
+   */
+  public String getUri() {
+    return uri;
+  }
+
+  /**
+   * Set URI
+   *
+   * @param uri
+   */
+  public void setUri(String uri) {
+    this.uri = uri;
+  }
+
+  /**
+   * Get face ID
+   *
+   * @return
+   */
+  public String getLocalUri() {
+    return localUri;
+  }
+
+  /**
+   * Set local URI
+   *
+   * @param localUri
+   */
+  public void setLocalUri(String localUri) {
+    this.localUri = localUri;
+  }
+
+  /**
+   * Get expiration period
+   *
+   * @return
+   */
+  public int getExpirationPeriod() {
+    return expirationPeriod;
+  }
+
+  /**
+   * Set expiration period
+   *
+   * @param expirationPeriod
+   */
+  public void setExpirationPeriod(int expirationPeriod) {
+    this.expirationPeriod = expirationPeriod;
+  }
+
+  /**
+   * Get face scope value
+   *
+   * @return
+   */
+  public FaceScope getFaceScope() {
+    return faceScope;
+  }
+
+  /**
+   * Set face scope value
+   *
+   * @param faceScope
+   */
+  public void setFaceScope(FaceScope faceScope) {
+    this.faceScope = faceScope;
+  }
+
+  /**
+   * Get face persistency value
+   *
+   * @return
+   */
+  public FacePersistency getFacePersistency() {
+    return facePersistency;
+  }
+
+  /**
+   * Set face persistency value
+   *
+   * @param facePersistency
+   */
+  public void setFacePersistency(FacePersistency facePersistency) {
+    this.facePersistency = facePersistency;
+  }
+
+  /**
+   * Get link type
+   *
+   * @return
+   */
+  public LinkType getLinkType() {
+    return linkType;
+  }
+
+  /**
+   * Set link type
+   *
+   * @param linkType
+   */
+  public void setLinkType(LinkType linkType) {
+    this.linkType = linkType;
+  }
+
+  /**
+   * Get number of received Interest packets
+   *
+   * @return
+   */
+  public int getInInterests() {
+    return inInterests;
+  }
+
+  /**
+   * Set number of received Interest packets
+   *
+   * @param inInterests
+   */
+  public void setInInterests(int inInterests) {
+    this.inInterests = inInterests;
+  }
+
+  /**
+   * Get number of sent Interest packets
+   *
+   * @return
+   */
+  public int getOutInterests() {
+    return outInterests;
+  }
+
+  /**
+   * Set number of sent Interest packets
+   *
+   * @param outInterests
+   */
+  public void setOutInterests(int outInterests) {
+    this.outInterests = outInterests;
+  }
+
+  /**
+   * Get number of received Data packets
+   *
+   * @return
+   */
+  public int getInDatas() {
+    return inDatas;
+  }
+
+  /**
+   * Set number of received Data packets
+   *
+   * @param inDatas
+   */
+  public void setInDatas(int inDatas) {
+    this.inDatas = inDatas;
+  }
+
+  /**
+   * Get number of sent Data packets
+   *
+   * @return
+   */
+  public int getOutDatas() {
+    return outDatas;
+  }
+
+  /**
+   * Set number of sent Data packets
+   *
+   * @param outDatas
+   */
+  public void setOutDatas(int outDatas) {
+    this.outDatas = outDatas;
+  }
+
+  /**
+   * Get number of input bytes
+   *
+   * @return
+   */
+  public int getInBytes() {
+    return inBytes;
+  }
+
+  /**
+   * Set number of input bytes
+   *
+   * @param inBytes
+   */
+  public void setInBytes(int inBytes) {
+    this.inBytes = inBytes;
+  }
+
+  /**
+   * Get number of output bytes
+   *
+   * @return
+   */
+  public int getOutBytes() {
+    return outBytes;
+  }
+
+  /**
+   * Set number of output bytes
+   *
+   * @param outBytes
+   */
+  public void setOutBytes(int outBytes) {
+    this.outBytes = outBytes;
+  }
+
+  public long getNumInNacks() {
+    return numInNacks;
+  }
+
+  public void setNumInNacks(long numInNacks) {
+    this.numInNacks = numInNacks;
+  }
+
+  public long getNumOutNacks() {
+    return numOutNacks;
+  }
+
+  public void setNumOutNacks(long numOutNacks) {
+    this.numOutNacks = numOutNacks;
+  }
+
+  private int faceId = -1;
+  private String uri = ""; // can't use URI because some are invalid syntax
+  private String localUri = ""; // can't use URI because some are invalid syntax
+  private int expirationPeriod = 0;
+  private FaceScope faceScope = FaceScope.LOCAL;
+  private FacePersistency facePersistency = FacePersistency.ON_DEMAND;
+  private LinkType linkType = LinkType.POINT_TO_POINT;
+  private int inInterests = 0;
+  private int outInterests = 0;
+  private int inDatas = 0;
+  private int outDatas = 0;
+  private int inBytes = 0;
+  private int outBytes = 0;
+  private long numInNacks = 0;
+  private long numOutNacks = 0;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/FibEntry.java b/app/src/main/java/com/intel/jndn/management/types/FibEntry.java
new file mode 100644
index 0000000..8c74636
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/FibEntry.java
@@ -0,0 +1,129 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import com.intel.jndn.management.EncodingHelper;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a FibEntry returned from /localhost/nfd/fib/list; see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#FIB-Dataset">http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#FIB-Dataset</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class FibEntry implements Decodable {
+
+  public final static int TLV_FIB_ENTRY = 128;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    for (NextHopRecord record : records) {
+      record.wireEncode(encoder);
+    }
+    EncodingHelper.encodeName(name, encoder);
+    encoder.writeTypeAndLength(TLV_FIB_ENTRY, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws EncodingException For invalid encoding.
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @throws EncodingException
+   */
+  @Override
+  public final void wireDecode(TlvDecoder decoder) throws EncodingException {
+    int endOffset = decoder.readNestedTlvsStart(TLV_FIB_ENTRY);
+    name = EncodingHelper.decodeName(decoder);
+    while (decoder.getOffset() < endOffset) {
+      NextHopRecord record = new NextHopRecord();
+      record.wireDecode(decoder);
+      records.add(record);
+    }
+    decoder.finishNestedTlvs(endOffset);
+  }
+
+  /**
+   * Get name
+   *
+   * @return
+   */
+  public Name getName() {
+    return name;
+  }
+
+  /**
+   * Set name
+   *
+   * @param name
+   */
+  public void setName(Name name) {
+    this.name = name;
+  }
+
+  /**
+   * Get records
+   *
+   * @return
+   */
+  public List<NextHopRecord> getRecords() {
+    return records;
+  }
+
+  /**
+   * Set records
+   *
+   * @param records
+   */
+  public void setRecords(List<NextHopRecord> records) {
+    this.records = records;
+  }
+
+  private Name name = new Name();
+  private List<NextHopRecord> records = new ArrayList<>();
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/ForwarderStatus.java b/app/src/main/java/com/intel/jndn/management/types/ForwarderStatus.java
new file mode 100644
index 0000000..6240bbd
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/ForwarderStatus.java
@@ -0,0 +1,240 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import java.nio.ByteBuffer;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a ForwarderStatus object from
+ * http://redmine.named-data.net/projects/nfd/wiki/ForwarderStatus.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ForwarderStatus implements Decodable {
+
+  public static final int TLV_NFD_VERSION = 0x80;
+  public static final int TLV_START_TIMESTAMP = 0x81;
+  public static final int TLV_CURRENT_TIMESTAMP = 0x82;
+  public static final int TLV_NUM_NAME_TREE_ENTRIES = 0x83;
+  public static final int TLV_NUM_FIB_ENTRIES = 0x84;
+  public static final int TLV_NUM_PIT_ENTRIES = 0x85;
+  public static final int TLV_NUM_MEASUREMENT_ENTRIES = 0x86;
+  public static final int TLV_NUM_CS_ENTRIES = 0x87;
+  public static final int TLV_NUM_IN_INTERESTS = 0x90;
+  public static final int TLV_NUM_IN_DATAS = 0x91;
+  public static final int TLV_NUM_OUT_INTERESTS = 0x92;
+  public static final int TLV_NUM_OUT_DATAS = 0x93;
+  public static final int TLV_NUM_IN_NACKS = 0x97;
+  public static final int TLV_NUM_OUT_NACKS = 0x98;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_OUT_NACKS, numOutNacks);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_OUT_DATAS, numOutDatas);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_OUT_INTERESTS, numOutInterests);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_IN_NACKS, numInNacks);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_IN_DATAS, numInDatas);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_IN_INTERESTS, numInInterests);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_CS_ENTRIES, numCsEntries);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_MEASUREMENT_ENTRIES, numMeasurementEntries);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_PIT_ENTRIES, numPitEntries);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_FIB_ENTRIES, numFibEntries);
+    encoder.writeNonNegativeIntegerTlv(TLV_NUM_NAME_TREE_ENTRIES, numNameTreeEntries);
+    encoder.writeNonNegativeIntegerTlv(TLV_CURRENT_TIMESTAMP, currentTimestamp);
+    encoder.writeNonNegativeIntegerTlv(TLV_START_TIMESTAMP, startTimestamp);
+    encoder.writeBlobTlv(TLV_NFD_VERSION, new Blob(nfdVersion).buf());
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws EncodingException For invalid encoding.
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @throws EncodingException
+   */
+  @Override
+  public void wireDecode(TlvDecoder decoder) throws EncodingException {
+    this.nfdVersion = new Blob(decoder.readBlobTlv(TLV_NFD_VERSION), true).toString();
+    this.startTimestamp = decoder.readNonNegativeIntegerTlv(TLV_START_TIMESTAMP);
+    this.currentTimestamp = decoder.readNonNegativeIntegerTlv(TLV_CURRENT_TIMESTAMP);
+    this.numNameTreeEntries = decoder.readNonNegativeIntegerTlv(TLV_NUM_NAME_TREE_ENTRIES);
+    this.numFibEntries = decoder.readNonNegativeIntegerTlv(TLV_NUM_FIB_ENTRIES);
+    this.numPitEntries = decoder.readNonNegativeIntegerTlv(TLV_NUM_PIT_ENTRIES);
+    this.numMeasurementEntries = decoder.readNonNegativeIntegerTlv(TLV_NUM_MEASUREMENT_ENTRIES);
+    this.numCsEntries = decoder.readNonNegativeIntegerTlv(TLV_NUM_CS_ENTRIES);
+    this.numInInterests = decoder.readNonNegativeIntegerTlv(TLV_NUM_IN_INTERESTS);
+    this.numInDatas = decoder.readNonNegativeIntegerTlv(TLV_NUM_IN_DATAS);
+    this.numInNacks = decoder.readNonNegativeIntegerTlv(TLV_NUM_IN_NACKS);
+    this.numOutInterests = decoder.readNonNegativeIntegerTlv(TLV_NUM_OUT_INTERESTS);
+    this.numOutDatas = decoder.readNonNegativeIntegerTlv(TLV_NUM_OUT_DATAS);
+    this.numOutNacks = decoder.readNonNegativeIntegerTlv(TLV_NUM_OUT_NACKS);
+  }
+
+  public String getNfdVersion() {
+    return nfdVersion;
+  }
+
+  public void setNfdVersion(String nfdVersion) {
+    this.nfdVersion = nfdVersion;
+  }
+
+  public long getStartTimestamp() {
+    return startTimestamp;
+  }
+
+  public void setStartTimestamp(long startTimestamp) {
+    this.startTimestamp = startTimestamp;
+  }
+
+  public long getCurrentTimestamp() {
+    return currentTimestamp;
+  }
+
+  public void setCurrentTimestamp(long currentTimestamp) {
+    this.currentTimestamp = currentTimestamp;
+  }
+
+  public long getNumNameTreeEntries() {
+    return numNameTreeEntries;
+  }
+
+  public void setNumNameTreeEntries(long numNameTreeEntries) {
+    this.numNameTreeEntries = numNameTreeEntries;
+  }
+
+  public long getNumFibEntries() {
+    return numFibEntries;
+  }
+
+  public void setNumFibEntries(long numFibEntries) {
+    this.numFibEntries = numFibEntries;
+  }
+
+  public long getNumPitEntries() {
+    return numPitEntries;
+  }
+
+  public void setNumPitEntries(long numPitEntries) {
+    this.numPitEntries = numPitEntries;
+  }
+
+  public long getNumMeasurementEntries() {
+    return numMeasurementEntries;
+  }
+
+  public void setNumMeasurementEntries(long numMeasurementEntries) {
+    this.numMeasurementEntries = numMeasurementEntries;
+  }
+
+  public long getNumCsEntries() {
+    return numCsEntries;
+  }
+
+  public void setNumCsEntries(long numCsEntries) {
+    this.numCsEntries = numCsEntries;
+  }
+
+  public long getNumInInterests() {
+    return numInInterests;
+  }
+
+  public void setNumInInterests(long numInInterests) {
+    this.numInInterests = numInInterests;
+  }
+
+  public long getNumInDatas() {
+    return numInDatas;
+  }
+
+  public void setNumInDatas(long numInDatas) {
+    this.numInDatas = numInDatas;
+  }
+
+  public long getNumInNacks() {
+    return numInNacks;
+  }
+
+  public void setNumInNacks(long numInNacks) {
+    this.numInNacks = numInNacks;
+  }
+
+  public long getNumOutInterests() {
+    return numOutInterests;
+  }
+
+  public void setNumOutInterests(long numOutInterests) {
+    this.numOutInterests = numOutInterests;
+  }
+
+  public long getNumOutDatas() {
+    return numOutDatas;
+  }
+
+  public void setNumOutDatas(long numOutDatas) {
+    this.numOutDatas = numOutDatas;
+  }
+
+  public long getNumOutNacks() {
+    return numOutNacks;
+  }
+
+  public void setNumOutNacks(long numOutNacks) {
+    this.numOutNacks = numOutNacks;
+  }
+
+  private String nfdVersion = "";
+  private long startTimestamp;
+  private long currentTimestamp;
+  private long numNameTreeEntries;
+  private long numFibEntries;
+  private long numPitEntries;
+  private long numMeasurementEntries;
+  private long numCsEntries;
+  private long numInInterests;
+  private long numInDatas;
+  private long numInNacks;
+  private long numOutInterests;
+  private long numOutDatas;
+  private long numOutNacks;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/LinkType.java b/app/src/main/java/com/intel/jndn/management/types/LinkType.java
new file mode 100644
index 0000000..a0f517c
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/LinkType.java
@@ -0,0 +1,35 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+/**
+ * Indicate the type of communication link; used by FaceStatus See
+ * <a href="http://redmine.named-data.net/projects/nfd/widi/FaceMgmt">http://redmine.named-data.net/projects/nfd/widi/FaceMgmt</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public enum LinkType {
+
+  POINT_TO_POINT(0),
+  MULTI_ACCESS(1);
+
+  LinkType(int value) {
+    value_ = value;
+  }
+
+  public final int getNumericValue() {
+    return value_;
+  }
+  private final int value_;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/LocalControlHeader.java b/app/src/main/java/com/intel/jndn/management/types/LocalControlHeader.java
new file mode 100644
index 0000000..5ca393f
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/LocalControlHeader.java
@@ -0,0 +1,36 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+/**
+ * Define constants for local control header options. See
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Enable-a-LocalControlHeader-feature">http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Enable-a-LocalControlHeader-feature</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public enum LocalControlHeader {
+
+  INCOMING_FACE_ID(1),
+  NEXT_HOP_FACE_ID(2),
+  CACHING_POLICY(3);
+
+  LocalControlHeader(int value) {
+    value_ = value;
+  }
+
+  public final int getNumericValue() {
+    return value_;
+  }
+  private final int value_;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/NextHopRecord.java b/app/src/main/java/com/intel/jndn/management/types/NextHopRecord.java
new file mode 100644
index 0000000..3b40104
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/NextHopRecord.java
@@ -0,0 +1,119 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import java.nio.ByteBuffer;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.Tlv;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a NextHopRecord in a FibEntry; see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#FIB-Dataset">http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#FIB-Dataset</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NextHopRecord {
+
+  public final static int TLV_NEXT_HOP_RECORD = 129;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_Cost, cost);
+    encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_FaceId, faceId);
+    encoder.writeTypeAndLength(TLV_NEXT_HOP_RECORD, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws EncodingException For invalid encoding.
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @throws EncodingException
+   */
+  public final void wireDecode(TlvDecoder decoder) throws EncodingException {
+    int endOffset = decoder.readNestedTlvsStart(TLV_NEXT_HOP_RECORD);
+    this.faceId = (int) decoder.readNonNegativeIntegerTlv(Tlv.ControlParameters_FaceId);
+    this.cost = (int) decoder.readNonNegativeIntegerTlv(Tlv.ControlParameters_Cost);
+    decoder.finishNestedTlvs(endOffset);
+  }
+
+  /**
+   * Get face ID
+   *
+   * @return
+   */
+  public int getFaceId() {
+    return faceId;
+  }
+
+  /**
+   * Set face ID
+   *
+   * @param faceId
+   */
+  public void setFaceId(int faceId) {
+    this.faceId = faceId;
+  }
+
+  /**
+   * Get cost
+   *
+   * @return
+   */
+  public int getCost() {
+    return cost;
+  }
+
+  /**
+   * Set cost
+   *
+   * @param cost
+   */
+  public void setCost(int cost) {
+    this.cost = cost;
+  }
+
+  private int faceId;
+  private int cost;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/RibEntry.java b/app/src/main/java/com/intel/jndn/management/types/RibEntry.java
new file mode 100644
index 0000000..705352c
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/RibEntry.java
@@ -0,0 +1,134 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import com.intel.jndn.management.EncodingHelper;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a entry in the RIB; see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#RIB-Dataset">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#RIB-Dataset</a>
+ * for details
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class RibEntry implements Decodable {
+
+  /**
+   * TLV type, see
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#TLV-TYPE-assignments">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#TLV-TYPE-assignments</a>
+   */
+  public final static int TLV_RIB_ENTRY = 128;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    for (Route route : routes) {
+      route.wireEncode(encoder);
+    }
+    EncodingHelper.encodeName(name, encoder);
+    encoder.writeTypeAndLength(TLV_RIB_ENTRY, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws EncodingException For invalid encoding.
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @throws EncodingException
+   */
+  @Override
+  public final void wireDecode(TlvDecoder decoder) throws EncodingException {
+    int endOffset = decoder.readNestedTlvsStart(TLV_RIB_ENTRY);
+    name = EncodingHelper.decodeName(decoder);
+    while (decoder.getOffset() < endOffset) {
+      Route route = new Route();
+      route.wireDecode(decoder);
+      routes.add(route);
+    }
+    decoder.finishNestedTlvs(endOffset);
+  }
+
+  /**
+   * Get name
+   *
+   * @return
+   */
+  public Name getName() {
+    return name;
+  }
+
+  /**
+   * Set name
+   *
+   * @param name
+   */
+  public void setName(Name name) {
+    this.name = name;
+  }
+
+  /**
+   * Get routes
+   *
+   * @return
+   */
+  public List<Route> getRoutes() {
+    return routes;
+  }
+
+  /**
+   * Set routes
+   *
+   * @param routes
+   */
+  public void setRoutes(List<Route> routes) {
+    this.routes = routes;
+  }
+
+  private Name name = new Name();
+  private List<Route> routes = new ArrayList<>();
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/Route.java b/app/src/main/java/com/intel/jndn/management/types/Route.java
new file mode 100644
index 0000000..b4badf0
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/Route.java
@@ -0,0 +1,188 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import net.named_data.jndn.encoding.tlv.Tlv;
+import java.nio.ByteBuffer;
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.encoding.tlv.TlvEncoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represent a Route object from /localhost/nfd/rib/list; see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#RIB-Dataset">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#RIB-Dataset</a>
+ * for details.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class Route {
+
+  /**
+   * TLV type, see
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#TLV-TYPE-assignments">http://redmine.named-data.net/projects/nfd/wiki/RibMgmt#TLV-TYPE-assignments</a>
+   */
+  public final static int TLV_ROUTE = 129;
+
+  /**
+   * Encode using a new TLV encoder.
+   *
+   * @return The encoded buffer.
+   */
+  public final Blob wireEncode() {
+    TlvEncoder encoder = new TlvEncoder();
+    wireEncode(encoder);
+    return new Blob(encoder.getOutput(), false);
+  }
+
+  /**
+   * Encode as part of an existing encode context.
+   *
+   * @param encoder
+   */
+  public final void wireEncode(TlvEncoder encoder) {
+    int saveLength = encoder.getLength();
+    encoder.writeOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_ExpirationPeriod, faceId);
+    encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_Flags, flags.getForwardingEntryFlags());
+    encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_Cost, cost);
+    encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_Origin, origin);
+    encoder.writeNonNegativeIntegerTlv(Tlv.ControlParameters_FaceId, faceId);
+    encoder.writeTypeAndLength(TLV_ROUTE, encoder.getLength() - saveLength);
+  }
+
+  /**
+   * Decode the input from its TLV format.
+   *
+   * @param input The input buffer to decode. This reads from position() to
+   * limit(), but does not change the position.
+   * @throws net.named_data.jndn.encoding.EncodingException
+   */
+  public final void wireDecode(ByteBuffer input) throws EncodingException {
+    TlvDecoder decoder = new TlvDecoder(input);
+    wireDecode(decoder);
+  }
+
+  /**
+   * Decode as part of an existing decode context.
+   *
+   * @param decoder
+   * @throws EncodingException
+   */
+  public final void wireDecode(TlvDecoder decoder) throws EncodingException {
+    int endOffset = decoder.readNestedTlvsStart(TLV_ROUTE);
+    this.faceId = (int) decoder.readNonNegativeIntegerTlv(Tlv.ControlParameters_FaceId);
+    this.origin = (int) decoder.readNonNegativeIntegerTlv(Tlv.ControlParameters_Origin);
+    this.cost = (int) decoder.readNonNegativeIntegerTlv(Tlv.ControlParameters_Cost);
+    this.flags.setForwardingEntryFlags((int) decoder.readNonNegativeIntegerTlv(Tlv.ControlParameters_Flags));
+    this.expirationPeriod = (int) decoder.readOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_ExpirationPeriod, endOffset);
+    decoder.finishNestedTlvs(endOffset);
+  }
+
+  /**
+   * Get Face ID
+   *
+   * @return
+   */
+  public int getFaceId() {
+    return faceId;
+  }
+
+  /**
+   * Set Face ID
+   *
+   * @param faceId
+   */
+  public void setFaceId(int faceId) {
+    this.faceId = faceId;
+  }
+
+  /**
+   * Get origin
+   *
+   * @return
+   */
+  public int getOrigin() {
+    return origin;
+  }
+
+  /**
+   * Set origin
+   *
+   * @param origin
+   */
+  public void setOrigin(int origin) {
+    this.origin = origin;
+  }
+
+  /**
+   * Get cost
+   *
+   * @return
+   */
+  public int getCost() {
+    return cost;
+  }
+
+  /**
+   * Set cost
+   *
+   * @param cost
+   */
+  public void setCost(int cost) {
+    this.cost = cost;
+  }
+
+  /**
+   * Get flags
+   *
+   * @return
+   */
+  public ForwardingFlags getFlags() {
+    return flags;
+  }
+
+  /**
+   * Set flags
+   *
+   * @param flags
+   */
+  public void setFlags(ForwardingFlags flags) {
+    this.flags = flags;
+  }
+
+  /**
+   * Get expiration period
+   *
+   * @return
+   */
+  public double getExpirationPeriod() {
+    return expirationPeriod;
+  }
+
+  /**
+   * Set expiration period
+   *
+   * @param expirationPeriod
+   */
+  public void setExpirationPeriod(double expirationPeriod) {
+    this.expirationPeriod = expirationPeriod;
+  }
+
+  private int faceId = -1;
+  private int origin = -1;
+  private int cost = -1;
+  private ForwardingFlags flags = new ForwardingFlags();
+  private double expirationPeriod = -1.0;
+}
diff --git a/app/src/main/java/com/intel/jndn/management/types/StatusDataset.java b/app/src/main/java/com/intel/jndn/management/types/StatusDataset.java
new file mode 100644
index 0000000..0cc42c8
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/management/types/StatusDataset.java
@@ -0,0 +1,56 @@
+/*
+ * jndn-management
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.management.types;
+
+import com.intel.jndn.management.ManagementException;
+import java.util.ArrayList;
+import java.util.List;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Helper class to handle StatusDatasets, see
+ * <a href="http://redmine.named-data.net/projects/nfd/wiki/StatusDataset">http://redmine.named-data.net/projects/nfd/wiki/StatusDataset</a>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class StatusDataset {
+
+  /**
+   * Decode multiple status entries as part of a StatusDataset, see
+   * <a href="http://redmine.named-data.net/projects/nfd/wiki/StatusDataset">http://redmine.named-data.net/projects/nfd/wiki/StatusDataset</a>
+   *
+   * @param <T>
+   * @param statusDataset
+   * @param type
+   * @return
+   * @throws com.intel.jndn.management.ManagementException
+   */
+  public static final <T extends Decodable> List<T> wireDecode(Blob statusDataset, Class<T> type) throws ManagementException {
+    List<T> entries = new ArrayList<>();
+    int endOffset = statusDataset.size();
+    TlvDecoder decoder = new TlvDecoder(statusDataset.buf());
+    while (decoder.getOffset() < endOffset) {
+      try {
+        T entry = type.newInstance();
+        entry.wireDecode(decoder);
+        entries.add(entry);
+      } catch (EncodingException | IllegalAccessException | InstantiationException e) {
+        throw new ManagementException("Failed to read status dataset.", e);
+      }
+    }
+    return entries;
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/Client.java b/app/src/main/java/com/intel/jndn/utils/Client.java
new file mode 100644
index 0000000..6b09f9f
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/Client.java
@@ -0,0 +1,50 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+
+/**
+ * Base functionality provided by all NDN clients in this package.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface Client {
+
+  /**
+   * Asynchronously request the Data for an Interest. This will send the
+   * Interest and return immediately; use futureData.get() to block until the
+   * Data returns (see Future) or manage the event processing independently.
+   *
+   * @param face
+   * @param interest
+   * @return
+   */
+  public Future<Data> getAsync(Face face, Interest interest);
+
+  /**
+   * 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
+   * @throws java.io.IOException
+   */
+  public Data getSync(Face face, Interest interest) throws IOException;
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/DynamicServer.java b/app/src/main/java/com/intel/jndn/utils/DynamicServer.java
new file mode 100644
index 0000000..8206e27
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/DynamicServer.java
@@ -0,0 +1,40 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.server.RespondWithData;
+import com.intel.jndn.utils.server.Server;
+import java.io.IOException;
+
+/**
+ * Defines the API for a {@link Server} producing {@link Data} packets
+ * dynamically; in other words, when an {@link Interest} arrives, this server
+ * will run a callback to determine what packet to send back. As good practice,
+ * keep callback methods as short as possible.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface DynamicServer extends Server {
+
+  /**
+   * Set the callback method to run when an {@link Interest} packet is passed to
+   * this server. This method should either return a {@link Data} packet that
+   * satisfies the Interest or throw an Exception to avoid sending. Calling this
+   * method a second time should replace the callback.
+   *
+   * @param callback the callback instance
+   * @throws java.io.IOException if the server fails to register a prefix
+   */
+  public void respondUsing(RespondWithData callback) throws IOException;
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/InternalFace.java b/app/src/main/java/com/intel/jndn/utils/InternalFace.java
new file mode 100644
index 0000000..2850363
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/InternalFace.java
@@ -0,0 +1,23 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+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/app/src/main/java/com/intel/jndn/utils/RepositoryServer.java b/app/src/main/java/com/intel/jndn/utils/RepositoryServer.java
new file mode 100644
index 0000000..0364eb7
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/RepositoryServer.java
@@ -0,0 +1,38 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.server.Server;
+import java.io.IOException;
+import net.named_data.jndn.Data;
+
+/**
+ * Defines the API for a {@link Server} producing {@link Data} packets and
+ * storing them until they are requested; this server corresponds closely to use
+ * cases such as: cache, file system, web server.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface RepositoryServer extends Server {
+
+  /**
+   * Store a {@link Data} packet in the server's repository until requested. The
+   * task of removing (or retaining) stale packets is not specified here but
+   * left to the implementation.
+   *
+   * @param data the {@link Data} packet to store and serve
+   * @throws IOException if the underlying server fails to store the packet
+   */
+  public void serve(Data data) throws IOException;
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/SegmentedClient.java b/app/src/main/java/com/intel/jndn/utils/SegmentedClient.java
new file mode 100644
index 0000000..2879a29
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/SegmentedClient.java
@@ -0,0 +1,211 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.client.SegmentedFutureData;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+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.encoding.EncodingException;
+
+import net.named_data.nfd.utils.G;
+
+/**
+ * Provide a client to simplify retrieving segmented Data packets over the NDN
+ * network. This class expects the Data producer to follow the NDN naming
+ * conventions (see http://named-data.net/doc/tech-memos/naming-conventions.pdf)
+ * and produce Data packets with a valid segment as the last component of their
+ * name; additionally, at least the first packet should set the FinalBlockId of
+ * the packet's MetaInfo (see
+ * http://named-data.net/doc/ndn-tlv/data.html#finalblockid).
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SegmentedClient implements Client {
+
+  private static SegmentedClient defaultInstance;
+  private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
+
+  /**
+   * Singleton access for simpler client use.
+   *
+   * @return
+   */
+  public static SegmentedClient getDefault() {
+    if (defaultInstance == null) {
+      defaultInstance = new SegmentedClient();
+    }
+    return defaultInstance;
+  }
+
+  /**
+   * Asynchronously send Interest packets for a segmented result; will block
+   * until the first packet is received and then send remaining interests until
+   * the specified FinalBlockId.
+   *
+   * @param face
+   * @param interest should include either a ChildSelector or an initial segment
+   * number; the initial segment number will be cut off in the de-segmented
+   * packet.
+   * @return a list of FutureData packets; if the first segment fails, the list
+   * will contain one FutureData with the failure exception
+   */
+  @Override
+  public Future<Data> getAsync(Face face, Interest interest) {
+    List<Future<Data>> segments = getAsyncList(face, interest);
+    Name name = hasSegment(interest.getName()) ? interest.getName().getPrefix(-1) : interest.getName();
+    return new SegmentedFutureData(name, segments);
+  }
+
+  /**
+   * Asynchronously send Interest packets for a segmented result; will block
+   * until the first packet is received and then send remaining interests until
+   * the specified FinalBlockId.
+   *
+   * @param face
+   * @param name the {@link Name} of the packet to retrieve using a default
+   * interest
+   * @return an aggregated data packet from all received segments
+   */
+  public Future<Data> getAsync(Face face, Name name) {
+    return getAsync(face, SimpleClient.getDefaultInterest(name));
+  }
+
+  /**
+   * Asynchronously send Interest packets for a segmented result; will block
+   * until the first packet is received and then send remaining interests until
+   * the specified FinalBlockId.
+   *
+   * @param face
+   * @param interest should include either a ChildSelector or an initial segment
+   * number
+   * @return a list of FutureData packets; if the first segment fails, the list
+   * will contain one FutureData with the failure exception
+   */
+  public List<Future<Data>> getAsyncList(Face face, Interest interest) {
+    // get first segment; default 0 or use a specified start segment
+    long firstSegment = 0;
+    boolean specifiedSegment = false;
+    try {
+      firstSegment = interest.getName().get(-1).toSegment();
+      specifiedSegment = true;
+    } catch (EncodingException e) {
+      // check for interest selector if no initial segment found
+      if (interest.getChildSelector() == -1) {
+        logger.log(Level.WARNING, "No child selector set for a segmented Interest; this may result in incorrect retrieval.");
+        // allow this interest to pass without a segment marker since it may still succeed
+      }
+    }
+
+    // setup segments
+    final List<Future<Data>> segments = new ArrayList<>();
+    segments.add(SimpleClient.getDefault().getAsync(face, interest));
+
+    // retrieve first packet to find last segment value
+    long lastSegment;
+    try {
+      G.Log("+++++++ " + segments.get(0).get().getMetaInfo().getFinalBlockId().toEscapedString());
+      lastSegment = segments.get(0).get().getMetaInfo().getFinalBlockId().toSegment();
+    } catch (ExecutionException | InterruptedException | EncodingException e) {
+      logger.log(Level.SEVERE, "Failed to retrieve first segment: ", e);
+      return segments;
+    }
+
+    // cut interest segment off
+    if (specifiedSegment) {
+      interest.setName(interest.getName().getPrefix(-1));
+    }
+
+    // send interests in remaining segments
+    for (long i = firstSegment + 1; i <= lastSegment; i++) {
+      Interest segmentedInterest = new Interest(interest);
+      segmentedInterest.getName().appendSegment(i);
+      Future<Data> futureData = SimpleClient.getDefault().getAsync(face, segmentedInterest);
+      segments.add((int) i, futureData);
+    }
+
+    return segments;
+  }
+
+  /**
+   * Asynchronously send Interests for a segmented Data packet 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). See getAsync(Face face)
+   * for more information.
+   *
+   * @param face
+   * @param name
+   * @return
+   */
+  public List<Future<Data>> getAsyncList(Face face, Name name) {
+    return getAsyncList(face, SimpleClient.getDefaultInterest(name));
+  }
+
+  /**
+   * Retrieve a segmented Data packet; will block until all segments are
+   * received and will re-assemble these.
+   *
+   * @param face
+   * @param interest should include either a ChildSelector or an initial segment
+   * number
+   * @return a Data packet; the name will inherit from the sent Interest, not
+   * the returned packets and the content will be a concatenation of all of the
+   * packet contents.
+   * @throws java.io.IOException
+   */
+  @Override
+  public Data getSync(Face face, Interest interest) throws IOException {
+    try {
+      return getAsync(face, interest).get();
+    } catch (ExecutionException | InterruptedException e) {
+      logger.log(Level.WARNING, "Failed to retrieve data.", e);
+      throw new IOException("Failed to retrieve data.", e);
+    }
+  }
+
+  /**
+   * 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). See getSync(Face face) for more
+   * information.
+   *
+   * @param face
+   * @param name
+   * @return
+   * @throws java.io.IOException
+   */
+  public Data getSync(Face face, Name name) throws IOException {
+    return getSync(face, SimpleClient.getDefaultInterest(name));
+  }
+
+  /**
+   * Check if a name ends in a segment component; uses marker value found in the
+   * NDN naming conventions (see
+   * http://named-data.net/doc/tech-memos/naming-conventions.pdf).
+   *
+   * @param name
+   * @return
+   */
+  public static boolean hasSegment(Name name) {
+    return name.get(-1).getValue().buf().get(0) == 0x00;
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/SegmentedServer.java b/app/src/main/java/com/intel/jndn/utils/SegmentedServer.java
new file mode 100644
index 0000000..0daf0af
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/SegmentedServer.java
@@ -0,0 +1,91 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.repository.ForLoopRepository;
+import com.intel.jndn.utils.repository.Repository;
+import com.intel.jndn.utils.server.SegmentedServerHelper;
+import com.intel.jndn.utils.server.ServerBaseImpl;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+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.encoding.EncodingException;
+import net.named_data.jndn.transport.Transport;
+
+/**
+ * Implementation of a {@link RepositoryServer} that segments packets stored in
+ * its repository.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SegmentedServer extends ServerBaseImpl implements RepositoryServer {
+
+  private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
+  private final Repository repository = new ForLoopRepository();
+
+  /**
+   * {@inheritDoc}
+   */
+  public SegmentedServer(Face face, Name prefix) {
+    super(face, prefix);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void serve(Data data) throws IOException {
+    if (!isRegistered()) {
+      register();
+    }
+
+    InputStream stream = new ByteArrayInputStream(data.getContent().getImmutableArray());
+    List<Data> segments = SegmentedServerHelper.segment(data, stream);
+    for (Data segment : segments) {
+      logger.info("Added segment: " + segment.getName().toUri());
+      repository.put(segment);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+    if (interest.getChildSelector() == -1) {
+      try {
+        interest.getName().get(-1).toSegment();
+      } catch (EncodingException e) {
+        interest.setChildSelector(Interest.CHILD_SELECTOR_LEFT);
+      }
+    }
+
+    try {
+      Data data = repository.get(interest);
+      data = processPipeline(data);
+      ByteBuffer buffer = data.wireEncode().buf();
+      transport.send(buffer);
+    } catch (Exception e) {
+      logger.log(Level.SEVERE, "Failed to send data for: " + interest.toUri(), e);
+    }
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/SimpleClient.java b/app/src/main/java/com/intel/jndn/utils/SimpleClient.java
new file mode 100644
index 0000000..a751f8e
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/SimpleClient.java
@@ -0,0 +1,145 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.client.FutureData;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+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 java.util.logging.Logger;
+
+/**
+ * Provide a client to simplify information retrieval over the NDN network.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SimpleClient implements Client {
+
+  public static final long DEFAULT_SLEEP_TIME = 20;
+  public static final long DEFAULT_TIMEOUT = 2000;
+  private static final Logger logger = Logger.getLogger(SimpleClient.class.getName());
+  private static SimpleClient defaultInstance;
+
+  /**
+   * Singleton access for simpler client use
+   *
+   * @return
+   */
+  public static SimpleClient getDefault() {
+    if (defaultInstance == null) {
+      defaultInstance = new SimpleClient();
+    }
+    return defaultInstance;
+  }
+
+  /**
+   * Asynchronously request the Data for an Interest. This will send the
+   * Interest and return immediately; use futureData.get() to block until the
+   * Data returns (see FutureData) or manage the event processing independently.
+   *
+   * @param face
+   * @param interest
+   * @return
+   */
+  @Override
+  public Future<Data> getAsync(Face face, Interest interest) {
+    final FutureData futureData = new FutureData(face, interest.getName());
+
+    // send interest
+    try {
+      face.expressInterest(interest, new OnData() {
+        @Override
+        public void onData(Interest interest, Data data) {
+          futureData.resolve(data);
+        }
+      }, new OnTimeout() {
+        @Override
+        public void onTimeout(Interest interest) {
+          futureData.reject(new TimeoutException());
+        }
+      });
+    } catch (IOException e) {
+      logger.log(Level.WARNING, "IO failure while sending interest: ", e);
+      futureData.reject(e);
+    }
+
+    return futureData;
+  }
+
+  /**
+   * 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 Future<Data> getAsync(Face face, Name name) {
+    return getAsync(face, getDefaultInterest(name));
+  }
+
+  /**
+   * 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
+   * @throws java.io.IOException
+   */
+  @Override
+  public Data getSync(Face face, Interest interest) throws IOException {
+    try {
+      return getAsync(face, interest).get();
+    } catch (ExecutionException | InterruptedException e) {
+      logger.log(Level.WARNING, "Failed to retrieve data.", e);
+      throw new IOException("Failed to retrieve data.", e);
+    }
+  }
+
+  /**
+   * 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
+   * @throws java.io.IOException
+   */
+  public Data getSync(Face face, Name name) throws IOException {
+    return getSync(face, getDefaultInterest(name));
+  }
+
+  /**
+   * Create a default interest for a given Name using some common settings: -
+   * lifetime: 2 seconds
+   *
+   * @param name
+   * @return
+   */
+  public static Interest getDefaultInterest(Name name) {
+    Interest interest = new Interest(name, DEFAULT_TIMEOUT);
+    return interest;
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/SimpleServer.java b/app/src/main/java/com/intel/jndn/utils/SimpleServer.java
new file mode 100644
index 0000000..0bd4667
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/SimpleServer.java
@@ -0,0 +1,96 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.server.RespondWithData;
+import com.intel.jndn.utils.server.RespondWithBlob;
+import com.intel.jndn.utils.server.ServerBaseImpl;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+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.transport.Transport;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Implementation of a {@link DynamicServer} that wraps the {@link OnInterest}
+ * callback with some encoding and pipeline support.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SimpleServer extends ServerBaseImpl implements DynamicServer {
+
+  private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
+  private RespondWithData callback;
+
+  /**
+   * {@inheritDoc}
+   */
+  public SimpleServer(Face face, Name prefix) {
+    super(face, prefix);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void respondUsing(RespondWithData callback) throws IOException {
+    if (!isRegistered()) {
+      register();
+    }
+    this.callback = callback;
+  }
+
+  /**
+   * Convenience method for responding to an {@link Interest} by returning the
+   * {@link Blob} content only; when an Interest arrives, this method wraps the
+   * returned Blob with a {@link Data} using the exact {@link Name} of the
+   * incoming Interest.
+   *
+   * @param callback the callback function to retrieve content when an
+   * {@link Interest} arrives
+   * @throws java.io.IOException if the server fails to register a prefix
+   */
+  public void respondUsing(final RespondWithBlob callback) throws IOException {
+    RespondWithData dataCallback = new RespondWithData() {
+      @Override
+      public Data onInterest(Name prefix, Interest interest) throws Exception {
+        Data data = new Data(interest.getName());
+        Blob content = callback.onInterest(prefix, interest);
+        data.setContent(content);
+        return data;
+      }
+    };
+    respondUsing(dataCallback);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+    try {
+      Data data = callback.onInterest(prefix, interest);
+      data = processPipeline(data);
+      ByteBuffer buffer = data.wireEncode().buf();
+      transport.send(buffer);
+    } catch (Exception e) {
+      logger.log(Level.SEVERE, "Failed to send data for: " + interest.toUri(), e);
+    }
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/client/FutureData.java b/app/src/main/java/com/intel/jndn/utils/client/FutureData.java
new file mode 100644
index 0000000..dc6b393
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/client/FutureData.java
@@ -0,0 +1,197 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.client;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.encoding.EncodingException;
+
+/**
+ * Reference to a Packet that has yet to be returned from the network. Usage:
+ *
+ * <pre><code>
+ * FutureData futureData = new FutureData(face, interest.getName());
+ * face.expressInterest(interest, new OnData(){
+ *	... futureData.resolve(data); ...
+ * }, new OnTimeout(){
+ *  ... futureData.reject(new TimeoutException());
+ * });
+ * Data resolvedData = futureData.get(); // will block and call face.processEvents() until complete
+ * </code></pre>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class FutureData implements Future<Data> {
+
+  protected final Face face;
+  private final Name name;
+  private Data data;
+  private boolean cancelled = false;
+  private Throwable error;
+
+  /**
+   * Constructor
+   *
+   * @param face
+   * @param name
+   */
+  public FutureData(Face face, Name name) {
+    this.face = face;
+    this.name = new Name(name);
+  }
+
+  /**
+   * Get the Interest name.
+   *
+   * @return
+   */
+  public Name getName() {
+    return name;
+  }
+
+  /**
+   * Cancel the current request.
+   *
+   * @param mayInterruptIfRunning
+   * @return
+   */
+  @Override
+  public boolean cancel(boolean mayInterruptIfRunning) {
+    cancelled = true;
+    return cancelled;
+  }
+
+  /**
+   * Determine if this request is cancelled.
+   *
+   * @return
+   */
+  @Override
+  public boolean isCancelled() {
+    return cancelled;
+  }
+
+  /**
+   * Determine if the request has completed (successfully or not).
+   *
+   * @return
+   */
+  @Override
+  public boolean isDone() {
+    return data != null || error != null || isCancelled();
+  }
+
+  /**
+   * Set the packet when successfully retrieved; unblocks get().
+   *
+   * @param d
+   */
+  public void resolve(Data d) {
+    data = d;
+  }
+
+  /**
+   * Set the exception when request failed; unblocks get().
+   *
+   * @param e
+   */
+  public void reject(Throwable e) {
+    error = e;
+  }
+
+  /**
+   * Block until packet is retrieved.
+   *
+   * @return
+   * @throws InterruptedException
+   * @throws ExecutionException
+   */
+  @Override
+  public Data get() throws InterruptedException, ExecutionException {
+    while (!isDone()) {
+      // process face events
+      try {
+        synchronized (face) {
+          face.processEvents();
+        }
+      } catch (EncodingException | IOException e) {
+        throw new ExecutionException("Failed to retrieve packet.", e);
+      }
+    }
+
+    // case: cancelled
+    if (cancelled) {
+      throw new InterruptedException("Interrupted by user.");
+    }
+
+    // case: error
+    if (error != null) {
+      throw new ExecutionException("Future rejected with error.", error);
+    }
+
+    return data;
+  }
+
+  /**
+   * Block until packet is retrieved or timeout is reached.
+   *
+   * @param timeout
+   * @param unit
+   * @return
+   * @throws InterruptedException
+   * @throws ExecutionException
+   * @throws TimeoutException
+   */
+  @Override
+  public Data get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+    long interval = TimeUnit.MILLISECONDS.convert(timeout, unit);
+    long endTime = System.currentTimeMillis() + interval;
+    long currentTime = System.currentTimeMillis();
+    while (!isDone() && !isCancelled() && currentTime <= endTime) {
+      // process face events
+      try {
+        synchronized (face) {
+          face.processEvents();
+        }
+      } catch (EncodingException | IOException e) {
+        throw new ExecutionException("Failed to retrieve packet.", e);
+      }
+
+      currentTime = System.currentTimeMillis();
+    }
+
+    // case: cancelled
+    if (cancelled) {
+      throw new InterruptedException("Interrupted by user.");
+    }
+
+    // case: error
+    if (error != null) {
+      throw new ExecutionException("Future rejected with error.", error);
+    }
+
+    // case: timed out
+    if (currentTime > endTime) {
+      throw new TimeoutException("Timed out.");
+    }
+
+    return data;
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/client/SegmentedFutureData.java b/app/src/main/java/com/intel/jndn/utils/client/SegmentedFutureData.java
new file mode 100644
index 0000000..dec33df
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/client/SegmentedFutureData.java
@@ -0,0 +1,172 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Represents a list of Packets that have been requested asynchronously and have
+ * yet to be returned from the network. Usage:
+ *
+ * <pre><code>
+ * SegmentedFutureData segmentedFutureData = new SegmentedFutureData(face, name, futureDataList);
+ * Data data = segmentedFutureData.get(); // will block until complete
+ * </code></pre>
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SegmentedFutureData implements Future<Data> {
+
+  private final Name name;
+  List<Future<Data>> segments;
+  private boolean cancelled = false;
+
+  /**
+   * Constructor
+   *
+   * @param name this will be the name of the returned Data packet, regardless
+   * of suffixes (e.g. segment components) on each segment packet
+   * @param segments
+   */
+  public SegmentedFutureData(Name name, List<Future<Data>> segments) {
+    this.name = name;
+    this.segments = segments;
+  }
+
+  /**
+   * Get the Interest name; this will also be the name of the Data packet
+   * returned from get().
+   *
+   * @return
+   */
+  public Name getName() {
+    return name;
+  }
+
+  /**
+   * Cancel the current request.
+   *
+   * @param mayInterruptIfRunning
+   * @return
+   */
+  @Override
+  public boolean cancel(boolean mayInterruptIfRunning) {
+    cancelled = true;
+    return cancelled;
+  }
+
+  /**
+   * Determine if this request is cancelled.
+   *
+   * @return
+   */
+  @Override
+  public boolean isCancelled() {
+    return cancelled;
+  }
+
+  /**
+   * Determine if the request has completed (successfully or not).
+   *
+   * @return
+   */
+  @Override
+  public boolean isDone() {
+    // check for errors, cancellation
+    if (isCancelled()) {
+      return true;
+    }
+
+    // check each segment for completion
+    for (Future<Data> futureData : segments) {
+      if (!futureData.isDone()) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Block until packet is retrieved.
+   *
+   * @return
+   * @throws InterruptedException
+   * @throws ExecutionException
+   */
+  @Override
+  public Data get() throws InterruptedException, ExecutionException {
+    // aggregate bytes
+    ByteArrayOutputStream content = new ByteArrayOutputStream();
+    for (Future<Data> futureData : segments) {
+      try {
+        content.write(futureData.get().getContent().getImmutableArray());
+      } catch (ExecutionException | IOException | InterruptedException e) {
+        throw new ExecutionException("Failed while aggregating retrieved packets.", e);
+      }
+    }
+
+    // build aggregated packet (copy first packet)
+    Data data = new Data(segments.get(0).get());
+    data.setName(getName());
+    data.setContent(new Blob(content.toByteArray()));
+    return data;
+  }
+
+  /**
+   * Block until packet is retrieved or timeout is reached.
+   *
+   * @param timeout
+   * @param unit
+   * @return
+   * @throws InterruptedException
+   * @throws ExecutionException
+   * @throws TimeoutException
+   */
+  @Override
+  public Data get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+    long interval = TimeUnit.MILLISECONDS.convert(timeout, unit);
+    long endTime = System.currentTimeMillis() + interval;
+
+    // aggregate bytes
+    ByteArrayOutputStream content = new ByteArrayOutputStream();
+    for (Future<Data> futureData : segments) {
+      try {
+        content.write(futureData.get().getContent().getImmutableArray());
+      } catch (ExecutionException | IOException | InterruptedException e) {
+        throw new ExecutionException("Failed while aggregating retrieved packets.", e);
+      }
+
+      // check for timeout
+      if (System.currentTimeMillis() > endTime) {
+        throw new TimeoutException("Timed out.");
+      }
+    }
+
+    // build aggregated packet (copy first packet)
+    Data data = new Data(segments.get(0).get());
+    data.setName(getName());
+    data.setContent(new Blob(content.toByteArray()));
+    return data;
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/repository/DataNotFoundException.java b/app/src/main/java/com/intel/jndn/utils/repository/DataNotFoundException.java
new file mode 100644
index 0000000..732cc9c
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/repository/DataNotFoundException.java
@@ -0,0 +1,23 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.repository;
+
+/**
+ * Thrown when a {@link Repository} cannot retrieve a stored packet.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class DataNotFoundException extends Exception {
+
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/repository/ForLoopRepository.java b/app/src/main/java/com/intel/jndn/utils/repository/ForLoopRepository.java
new file mode 100644
index 0000000..cb43655
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/repository/ForLoopRepository.java
@@ -0,0 +1,110 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.repository;
+
+import java.util.ArrayList;
+import java.util.List;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+
+/**
+ * Store {@link Data} packets in a linked list and iterate over the list to find
+ * the best match; this is a subset of the functionality provided in
+ * {@link net.named_data.jndn.util.MemoryContentCache} and borrows the matching
+ * logic from there. Code for removing stale packets is not yet implemented.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ForLoopRepository implements Repository {
+
+  private List<Data> storage = new ArrayList<>();
+
+  /**
+   * Helper data structure
+   */
+  private class Record {
+
+    public Name name;
+    public Data data;
+
+    public Record(Name name, Data data) {
+      this.name = name;
+      this.data = data;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void put(Data data) {
+    storage.add(data);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Data get(Interest interest) throws DataNotFoundException {
+    Name.Component selectedComponent = null;
+    Data selectedData = null;
+    for (Data content : storage) {
+      if (interest.matchesName(content.getName())) {
+        if (interest.getChildSelector() < 0) {
+          // No child selector, so send the first match that we have found.
+          return content;
+        } else {
+          // Update selectedEncoding based on the child selector.
+          Name.Component component;
+          if (content.getName().size() > interest.getName().size()) {
+            component = content.getName().get(interest.getName().size());
+          } else {
+            component = new Name.Component();
+          }
+
+          boolean gotBetterMatch = false;
+          if (selectedData == null) {
+            // Save the first match.
+            gotBetterMatch = true;
+          } else {
+            if (interest.getChildSelector() == 0) {
+              // Leftmost child.
+              if (component.compare(selectedComponent) < 0) {
+                gotBetterMatch = true;
+              }
+            } else {
+              // Rightmost child.
+              if (component.compare(selectedComponent) > 0) {
+                gotBetterMatch = true;
+              }
+            }
+          }
+
+          if (gotBetterMatch) {
+            selectedComponent = component;
+            selectedData = content;
+          }
+        }
+      }
+    }
+
+    if (selectedData != null) {
+      // We found the leftmost or rightmost child.
+      return selectedData;
+    } else {
+      throw new DataNotFoundException();
+    }
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/repository/NavigableTreeRepository.java b/app/src/main/java/com/intel/jndn/utils/repository/NavigableTreeRepository.java
new file mode 100644
index 0000000..ec47190
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/repository/NavigableTreeRepository.java
@@ -0,0 +1,146 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.repository;
+
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+
+/**
+ * Store {@link Data} packets in a {@link TreeMap}; this is an initial concept
+ * class and should not be used in production. In tests, see
+ * RepositoryTest.java, this class is faster on retrieval but not enough to make
+ * up for its slow put().
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NavigableTreeRepository implements Repository {
+
+  private NavigableMap<Name, PossibleData> storage = new TreeMap<>();
+
+  /**
+   * Helper data structure
+   */
+  private class PossibleData {
+
+    public PossibleData() {
+      // no data provided
+    }
+
+    public PossibleData(Data data) {
+      this.data = data;
+    }
+    public Data data;
+
+    public boolean hasData() {
+      return data != null;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void put(Data data) {
+    storage.put(data.getName(), new PossibleData(data));
+
+    Name name = data.getName();
+    do {
+      name = name.getPrefix(-1);
+      if (storage.get(name) == null) {
+        storage.put(name, new PossibleData());
+      }
+    } while (name.size() > 0);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Data get(Interest interest) throws DataNotFoundException {
+    Data data;
+    if (interest.getChildSelector() == Interest.CHILD_SELECTOR_LEFT) {
+      data = getLowest(interest);
+    } else if (interest.getChildSelector() == Interest.CHILD_SELECTOR_RIGHT) {
+      data = getHighest(interest);
+    } else {
+      data = getFirstMatching(interest);
+    }
+    checkForNull(data);
+    return data;
+  }
+
+  /**
+   * @param interest the {@link Interest} to search with
+   * @return the lowest matching {@link Data} packet
+   */
+  private Data getLowest(Interest interest) {
+    PossibleData found = storage.get(interest.getName());
+
+    Name name = interest.getName();
+    while (found != null && interest.matchesName(name)) {
+      name = storage.lowerKey(name);
+      found = (name != null) ? storage.get(name) : null;
+    }
+
+    return found == null ? null : found.data;
+  }
+
+  /**
+   * @param interest the {@link Interest} to search with
+   * @return the highest matching {@link Data} packet
+   */
+  private Data getHighest(Interest interest) {
+    PossibleData found = storage.get(interest.getName());
+
+    if (found != null) {
+      Name name = interest.getName();
+      while (name != null && interest.matchesName(name)) {
+        found = storage.get(name);
+        name = storage.higherKey(name);
+      }
+    }
+
+    return found == null ? null : found.data;
+  }
+
+  /**
+   * @param interest the {@link Interest} to search with
+   * @return the first matching {@link Data} packet
+   */
+  private Data getFirstMatching(Interest interest) {
+    PossibleData found = storage.get(interest.getName());
+
+    Name name = interest.getName();
+    while (found != null && !found.hasData() && interest.matchesName(name)) {
+      name = storage.higherKey(name);
+      found = (name != null) ? storage.get(name) : null;
+    }
+
+    return found == null ? null : found.data;
+  }
+
+  /**
+   * @param data the {@link Data} packet to check
+   * @throws DataNotFoundException if data is null
+   */
+  private void checkForNull(Data data) throws DataNotFoundException {
+    if (data == null) {
+      throw new DataNotFoundException();
+    }
+  }
+
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/repository/Repository.java b/app/src/main/java/com/intel/jndn/utils/repository/Repository.java
new file mode 100644
index 0000000..9444e24
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/repository/Repository.java
@@ -0,0 +1,43 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.repository;
+
+import com.intel.jndn.utils.repository.DataNotFoundException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+
+/**
+ * Define API for storing and retrieving NDN packets
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface Repository {
+
+	/**
+	 * Put a {@link Data} packet in the repository.
+	 *
+	 * @param data a {@link Data} packet
+	 */
+	public void put(Data data);
+
+	/**
+	 * Retrieve a {@link Data} packet in the repository; this method should
+	 * respect child selectors, exclude selectors, etc.
+	 *
+	 * @param interest the {@link Interest}
+	 * @return a {@link Data} packet
+	 * @throws DataNotFoundException if the packet is not found
+	 */
+	public Data get(Interest interest) throws DataNotFoundException;
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/PipelineStage.java b/app/src/main/java/com/intel/jndn/utils/server/PipelineStage.java
new file mode 100644
index 0000000..15d23f0
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/PipelineStage.java
@@ -0,0 +1,24 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server;
+
+/**
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface PipelineStage<T, Y> {
+  public Y process(T context) throws Exception;
+//  public void setNextStage(PipelineStage<Y, ?> nextStage);
+//  public PipelineStage<Y, ?> getNextStage();
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/RespondWithBlob.java b/app/src/main/java/com/intel/jndn/utils/server/RespondWithBlob.java
new file mode 100644
index 0000000..fe7f74a
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/RespondWithBlob.java
@@ -0,0 +1,28 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server;
+
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Functional interface for serving data from Server.on()
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface RespondWithBlob {
+
+  public Blob onInterest(Name prefix, Interest interest) throws Exception;
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/RespondWithData.java b/app/src/main/java/com/intel/jndn/utils/server/RespondWithData.java
new file mode 100644
index 0000000..95d0e6a
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/RespondWithData.java
@@ -0,0 +1,28 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server;
+
+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 RespondWithData {
+
+  public Data onInterest(Name prefix, Interest interest) throws Exception;
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/SegmentedServerHelper.java b/app/src/main/java/com/intel/jndn/utils/server/SegmentedServerHelper.java
new file mode 100644
index 0000000..2e1f92c
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/SegmentedServerHelper.java
@@ -0,0 +1,102 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Helper for segmenting an input stream into a list of Data packets. Current
+ * use of the default segment size of 4096 (only for
+ * {@link #segment(net.named_data.jndn.Data, java.io.InputStream)} is based on
+ * several assumptions: NDN packet size was limited to 8000 at the time this was
+ * written and signature size is unknown.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SegmentedServerHelper {
+
+  public static final int DEFAULT_SEGMENT_SIZE = 4096;
+
+  /**
+   * Segment a stream of bytes into a list of Data packets; this must read all
+   * the bytes first in order to determine the end segment for FinalBlockId.
+   *
+   * @param template the {@link Data} packet to use for the segment {@link Name},
+   * {@link net.named_data.jndn.MetaInfo}, etc.
+   * @param bytes an {@link InputStream} to the bytes to segment
+   * @return a list of segmented {@link Data} packets
+   * @throws IOException if the stream fails
+   */
+  public static List<Data> segment(Data template, InputStream bytes) throws IOException {
+    return segment(template, bytes, DEFAULT_SEGMENT_SIZE);
+  }
+
+  /**
+   * Segment a stream of bytes into a list of Data packets; this must read all
+   * the bytes first in order to determine the end segment for FinalBlockId.
+   *
+   * @param template the {@link Data} packet to use for the segment {@link Name},
+   * {@link net.named_data.jndn.MetaInfo}, etc.
+   * @param bytes an {@link InputStream} to the bytes to segment
+   * @return a list of segmented {@link Data} packets
+   * @throws IOException if the stream fails
+   */
+  public static List<Data> segment(Data template, InputStream bytes, int segmentSize) throws IOException {
+    List<Data> segments = new ArrayList<>();
+    byte[] buffer_ = readAll(bytes);
+    int numBytes = buffer_.length;
+    int numPackets = (int) Math.ceil((double) numBytes / segmentSize);
+    ByteBuffer buffer = ByteBuffer.wrap(buffer_, 0, numBytes);
+    Name.Component lastSegment = Name.Component.fromNumberWithMarker(numPackets - 1, 0x00);
+
+    for (int i = 0; i < numPackets; i++) {
+      Data segment = new Data(template);
+      segment.getName().appendSegment(i);
+      segment.getMetaInfo().setFinalBlockId(lastSegment);
+      byte[] content = new byte[Math.min(segmentSize, buffer.remaining())];
+      buffer.get(content);
+      segment.setContent(new Blob(content));
+      segments.add(segment);
+    }
+
+    return segments;
+  }
+
+  /**
+   * Read all of the bytes in an input stream.
+   *
+   * @param bytes the {@link InputStream} of bytes to read
+   * @return an array of all bytes retrieved from the stream
+   * @throws IOException if the stream fails
+   */
+  public static byte[] readAll(InputStream bytes) throws IOException {
+    ByteArrayOutputStream builder = new ByteArrayOutputStream();
+    int read = bytes.read();
+    while (read != -1) {
+      builder.write(read);
+      read = bytes.read();
+    }
+    builder.flush();
+    bytes.close();
+    return builder.toByteArray();
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/Server.java b/app/src/main/java/com/intel/jndn/utils/server/Server.java
new file mode 100644
index 0000000..ba4ff1c
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/Server.java
@@ -0,0 +1,46 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnInterest;
+
+/**
+ * Base interface for defining a server; see descendant interfaces for different
+ * modes of serving packets. This class extends {@link Runnable} expecting
+ * implementing classes to do any necessary event processing in the
+ * {@link Runnable#run()} block, thus allowing different ways to manage servers
+ * (e.g. single-thread vs {@link ScheduledThreadPoolExecutor}.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public interface Server extends Runnable, OnInterest {
+
+  /**
+   * @return the {@link Name} prefix this server is serving on.
+   */
+  public Name getPrefix();
+
+  /**
+   * Add a stage to the server pipeline. Each stage should be processed once the
+   * server has the {@link Data} packet available to send (e.g. after a callback
+   * has produced a packet); also, stages should be processed in the order they
+   * are added.
+   *
+   * @param pipelineStage a Data-to-Data processing stage
+   */
+  public void addPipelineStage(PipelineStage<Data, Data> pipelineStage);
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/ServerBaseImpl.java b/app/src/main/java/com/intel/jndn/utils/server/ServerBaseImpl.java
new file mode 100644
index 0000000..0d150d6
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/ServerBaseImpl.java
@@ -0,0 +1,140 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnRegisterFailed;
+import net.named_data.jndn.encoding.EncodingException;
+
+/**
+ * Base implementation for a {@link Server}.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public abstract class ServerBaseImpl implements Server {
+
+  private static final Logger logger = Logger.getLogger(ServerBaseImpl.class.getName());
+  private final Face face;
+  private final Name prefix;
+  private final List<PipelineStage> pipeline = new ArrayList<>();
+  private boolean registered = false;
+
+  /**
+   * Build the base server; register() must run separately and is run
+   * automatically on {@link #run()}.
+   *
+   * @param face a {@link Face} allowing prefix registration (see
+   * {@link Face#setCommandSigningInfo(net.named_data.jndn.security.KeyChain, net.named_data.jndn.Name)}
+   * @param prefix the {@link Name} to register
+   */
+  public ServerBaseImpl(Face face, Name prefix) {
+    this.face = face;
+    this.prefix = prefix;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Name getPrefix() {
+    return prefix;
+  }
+
+  /**
+   * @return true if the server has registered a prefix on the face
+   */
+  public boolean isRegistered() {
+    return registered;
+  }
+
+  /**
+   * Register a prefix for responding to interests.
+   *
+   * @throws java.io.IOException if IO fails
+   */
+  public void register() throws IOException {
+    registered = true;
+    try {
+      face.registerPrefix(prefix, this, new OnRegisterFailed() {
+        @Override
+        public void onRegisterFailed(Name prefix) {
+          registered = false;
+          logger.log(Level.SEVERE, "Failed to register prefix: " + prefix.toUri());
+        }
+      }, new ForwardingFlags());
+    } catch (net.named_data.jndn.security.SecurityException e) {
+      registered = false;
+      throw new IOException("Failed to communicate to face due to security error", e);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void addPipelineStage(PipelineStage<Data, Data> pipelineStage) {
+    pipeline.add(pipelineStage);
+  }
+
+  /**
+   * Process the {@link Data} before sending it; this runs the packet through
+   * each registered {@link PipelineStage} in order.
+   *
+   * @param data the {@link Data} to process
+   * @return a processed {@link Data} packet; no guarantee as to whether it is
+   * the same instance as passed in as a parameter (and likely not).
+   * @throws Exception if a pipeline stage fails
+   */
+  public Data processPipeline(Data data) throws Exception {
+    for (PipelineStage<Data, Data> stage : pipeline) {
+      data = stage.process(data);
+    }
+    return data;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void run() {
+    if (!isRegistered()) {
+      try {
+        register();
+      } catch (IOException ex) {
+        throw new RuntimeException("Failed to register prefix, aborting.", ex);
+      }
+    }
+
+    // continuously serve packets
+    while (true) {
+      try {
+        synchronized (face) {
+          face.processEvents();
+        }
+      } catch (IOException ex) {
+        logger.log(Level.SEVERE, "Failed to process events.", ex);
+      } catch (EncodingException ex) {
+        logger.log(Level.SEVERE, "Failed to parse bytes.", ex);
+      }
+    }
+  }
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/pipeline/CompressionStage.java b/app/src/main/java/com/intel/jndn/utils/server/pipeline/CompressionStage.java
new file mode 100644
index 0000000..687bde0
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/pipeline/CompressionStage.java
@@ -0,0 +1,49 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server.pipeline;
+
+import com.intel.jndn.utils.server.PipelineStage;
+import java.io.ByteArrayOutputStream;
+import java.util.zip.GZIPOutputStream;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * Sample stage for compressing {@link Data} content using GZIP
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class CompressionStage implements PipelineStage<Data, Data> {
+
+  /**
+   * Compress and replace the {@link Data} content. Note: this stage will return
+   * the same {@link Data} instance and will modify only its content.
+   *
+   * @param context the {@link Data} packet
+   * @return the same packet but with GZIP-compressed content
+   * @throws Exception if compression fails
+   */
+  @Override
+  public Data process(Data context) throws Exception {
+    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    try (GZIPOutputStream stream = new GZIPOutputStream(buffer)) {
+      stream.write(context.getContent().getImmutableArray(), 0, context.getContent().size());
+      stream.close();
+    }
+
+    context.setContent(new Blob(buffer.toByteArray()));
+    return context;
+  }
+
+}
diff --git a/app/src/main/java/com/intel/jndn/utils/server/pipeline/SigningStage.java b/app/src/main/java/com/intel/jndn/utils/server/pipeline/SigningStage.java
new file mode 100644
index 0000000..5296d89
--- /dev/null
+++ b/app/src/main/java/com/intel/jndn/utils/server/pipeline/SigningStage.java
@@ -0,0 +1,68 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils.server.pipeline;
+
+import com.intel.jndn.utils.server.PipelineStage;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.security.KeyChain;
+import net.named_data.jndn.security.SecurityException;
+
+/**
+ * As a part of a {@link com.intel.jndn.utils.ServerIn} pipeline, this stage
+ * will sign a {@link Data} packet.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SigningStage implements PipelineStage<Data, Data> {
+
+  private final KeyChain keyChain;
+  private final Name certificateName;
+
+  /**
+   * Default constructor.
+   *
+   * @param keyChain the {@link KeyChain} to use for signing
+   * @param certificateName the certificate to sign with
+   */
+  public SigningStage(KeyChain keyChain, Name certificateName) {
+    this.keyChain = keyChain;
+    this.certificateName = certificateName;
+  }
+
+  /**
+   * Build the stage using the default certificate name defined on the
+   * {@link KeyChain}.
+   *
+   * @param keyChain the {@link KeyChain} to use for signing
+   * @throws SecurityException if no default certificate is found
+   */
+  public SigningStage(KeyChain keyChain) throws SecurityException {
+    this.keyChain = keyChain;
+    this.certificateName = keyChain.getDefaultCertificateName();
+  }
+
+  /**
+   * Sign a {@link Data} packet.
+   *
+   * @param context the data packet to sign
+   * @return the signed data packet
+   * @throws Exception if signing fails
+   */
+  @Override
+  public Data process(Data context) throws Exception {
+    keyChain.sign(context, certificateName);
+    return context;
+  }
+}