Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a6f89c2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2cdd548
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.intel</groupId>
+	<artifactId>jndn-management</artifactId>
+	<version>0.9</version>
+	<packaging>jar</packaging>
+	<description>Tools for managing an NDN forwarding daemon</description>
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<maven.compiler.source>1.7</maven.compiler.source>
+		<maven.compiler.target>1.7</maven.compiler.target>
+	</properties>
+	<dependencies>
+		<dependency>
+			<groupId>net.named_data.jndn</groupId>
+			<artifactId>jndn</artifactId>
+			<version>RELEASE</version>
+		</dependency>
+		<dependency>
+			<groupId>com.intel.jndn.utils</groupId>
+			<artifactId>jndn-utils</artifactId>
+			<version>RELEASE</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-api</artifactId>
+			<version>2.1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-core</artifactId>
+			<version>2.1</version>
+		</dependency>
+		<!-- Test dependencies -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.10</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.intel.jndn.mock</groupId>
+			<artifactId>jndn-mock</artifactId>
+			<version>RELEASE</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	<distributionManagement>
+		<repository>
+			<id>ubit-artifactory-or.intel.com</id>
+			<name>ubit-artifactory-or.intel.com-releases</name>
+			<url>https://ubit-artifactory-or.intel.com/artifactory/libs-release-local</url>
+		</repository>
+		<snapshotRepository>
+			<id>ubit-artifactory-or.intel.com</id>
+			<name>ubit-artifactory-or.intel.com-snapshots</name>
+			<url>https://ubit-artifactory-or.intel.com/artifactory/libs-snapshot-local</url>
+		</snapshotRepository>
+	</distributionManagement>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/intel/jndn/management/ControlResponse.java b/src/main/java/com/intel/jndn/management/ControlResponse.java
new file mode 100644
index 0000000..c221d7c
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/ControlResponse.java
@@ -0,0 +1,40 @@
+/*
+ * File name: ControlResponse.java
+ * 
+ * Purpose: Represent a ControlResponse, a Data packet sent in response to a 
+ * ControlCommand to the NFD, see http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+import java.util.List;
+import net.named_data.jndn.ControlParameters;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.encoding.EncodingException;
+
+/**
+ * 
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ControlResponse {
+	public int StatusCode;
+	public String StatusText;
+	public List<ControlParameters> Body;
+	
+	/**
+	 * Decode input as a ControlResponse in NDN-TLV and set the fields of the
+	 * new object
+	 * 
+	 * @param data
+	 * @return 
+	 */
+	public static ControlResponse decode(Data data) throws EncodingException{
+		ControlResponseDecoder decoder = new ControlResponseDecoder();
+		ControlResponse response = new ControlResponse();
+		decoder.decodeControlResponse(response, data.getContent().buf());
+		return response;
+	}
+}
diff --git a/src/main/java/com/intel/jndn/management/ControlResponseDecoder.java b/src/main/java/com/intel/jndn/management/ControlResponseDecoder.java
new file mode 100644
index 0000000..f52c35a
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/ControlResponseDecoder.java
@@ -0,0 +1,68 @@
+/*
+ * File name: ControlParametersDecoder.java
+ * 
+ * Purpose: See http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import net.named_data.jndn.ControlParameters;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.TlvWireFormat;
+import net.named_data.jndn.encoding.tlv.Tlv;
+import net.named_data.jndn.encoding.tlv.TlvDecoder;
+import net.named_data.jndn.util.Blob;
+
+/**
+ * 
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class ControlResponseDecoder extends TlvWireFormat {
+
+	/**
+	 * Use TLV codes from jndn.encoding.tlv.Tlv.java See
+	 * http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+	 */
+	public final static int ControlResponse = 101;
+	public final static int ControlResponse_StatusCode = 102;
+	public final static int ControlResponse_StatusText = 103;
+
+	/**
+	 * Decode a ControlResponse TLV object; see
+	 * http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+	 *
+	 * @param controlResponse
+	 * @param input
+	 * @throws EncodingException
+	 */
+	public void decodeControlResponse(ControlResponse controlResponse, ByteBuffer input) throws EncodingException {
+		TlvDecoder decoder = new TlvDecoder(input);
+		int endOffset = decoder.readNestedTlvsStart(ControlResponse);
+
+		// parse
+		controlResponse.StatusCode = (int) decoder.readNonNegativeIntegerTlv(ControlResponse_StatusCode);
+		Blob statusText = new Blob(decoder.readBlobTlv(ControlResponse_StatusText), true); // copy because buffer is immutable
+		controlResponse.StatusText = statusText.toString();
+		controlResponse.Body = new ArrayList<>();
+		while (decoder.peekType(Tlv.ControlParameters_ControlParameters, endOffset)) {
+			ByteBuffer copyInput = input.duplicate();
+			copyInput.position(decoder.getOffset());
+			int internalEndOffset = decoder.readNestedTlvsStart(Tlv.ControlParameters_ControlParameters);
+			// decode
+			ControlParameters copyParameters = new ControlParameters();
+			copyParameters.wireDecode(copyInput);
+			controlResponse.Body.add(copyParameters);
+			decoder.seek(internalEndOffset);
+			// 
+			decoder.finishNestedTlvs(internalEndOffset);
+		}
+
+		// etc...
+		decoder.finishNestedTlvs(endOffset);
+	}
+}
diff --git a/src/main/java/com/intel/jndn/management/FacePersistency.java b/src/main/java/com/intel/jndn/management/FacePersistency.java
new file mode 100644
index 0000000..2f2b5e6
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/FacePersistency.java
@@ -0,0 +1,32 @@
+/*
+ * File name: FacePersistency.java
+ * 
+ * Purpose: Indicate whether the face is persistent; used by FaceStatus
+ * See http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+/**
+ * Indicate whether the face is persistent; used by FaceStatus
+ * See http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+ * @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/src/main/java/com/intel/jndn/management/FaceScope.java b/src/main/java/com/intel/jndn/management/FaceScope.java
new file mode 100644
index 0000000..17028c5
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/FaceScope.java
@@ -0,0 +1,32 @@
+/*
+ * File name: FaceScope.java
+ * 
+ * Purpose: Indicate whether the face is local for scope control purposes; 
+ * used by FaceStatus. See http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+/**
+ * Indicate whether the face is local for scope control purposes; used by FaceStatus
+ * See http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+ *
+ * @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/src/main/java/com/intel/jndn/management/FaceStatus.java b/src/main/java/com/intel/jndn/management/FaceStatus.java
new file mode 100644
index 0000000..f0a992b
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/FaceStatus.java
@@ -0,0 +1,49 @@
+/*
+ * File name: FaceStatus.java
+ * 
+ * Purpose: Represent a FaceStatus object from /localhost/nfd/faces/list;
+ * see http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt for details
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+import java.util.List;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.encoding.EncodingException;
+
+/**
+ * Represent a FaceStatus object from /localhost/nfd/faces/list;
+ * see http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt for details
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class FaceStatus {
+
+	public int faceId;
+	public String uri; // can't use URI because some are invalid syntax
+	public String localUri; // can't use URI because some are invalid syntax
+	public int expirationPeriod;
+	public FaceScope faceScope;
+	public FacePersistency facePersistency;
+	public LinkType linkType;
+	public int inInterests;
+	public int outInterests;
+	public int inDatas;
+	public int outDatas;
+	public int inBytes;
+	public int outBytes;
+
+	/**
+	 * Helper method for decoding a list of face statuses from
+	 * /localhost/nfd/faces/list
+	 * @param data
+	 * @return
+	 * @throws EncodingException 
+	 */
+	public static List<FaceStatus> decode(Data data) throws EncodingException{
+		FaceStatusDecoder decoder = new FaceStatusDecoder();
+		return decoder.decodeFaces(data.getContent().buf());
+	}
+}
diff --git a/src/main/java/com/intel/jndn/management/FaceStatusDecoder.java b/src/main/java/com/intel/jndn/management/FaceStatusDecoder.java
new file mode 100644
index 0000000..346f2cb
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/FaceStatusDecoder.java
@@ -0,0 +1,97 @@
+/*
+ * File name: FaceStatusDecoder.java
+ * 
+ * Purpose: Decode lists of FaceStatus objects from /localhost/nfd/faces/list
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+import java.nio.ByteBuffer;
+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;
+
+/**
+ * Decode lists of FaceStatus objects from /localhost/nfd/faces/list
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class FaceStatusDecoder {
+	
+	/**
+	 * Spec from http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+	 */
+	public static final int FACE_ID = 105; 
+	public static final int URI = 114;
+	public static final int EXPIRATION_PERIOD = 109;
+	
+	/**
+	 * Spec from http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+	 */
+	public static final int FACE_STATUS = 128;
+	public static final int LOCAL_URI = 129;
+	public static final int CHANNEL_STATUS = 130;
+	public static final int FACE_SCOPE = 132;
+	public static final int FACE_PERSISTENCY = 133;
+	public static final int LINK_TYPE = 134;
+	public static final int N_IN_INTERESTS = 144;
+	public static final int N_IN_DATAS = 145;
+	public static final int N_OUT_INTERESTS = 146;
+	public static final int N_OUT_DATAS = 147;
+	public static final int N_IN_BYTES = 148;
+	public static final int N_OUT_BYTES = 149;
+	
+	/**
+	 * Decode a list of faces according to
+	 * http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt
+	 * @param buffer
+	 * @return
+	 * @throws EncodingException 
+	 */
+	public List<FaceStatus> decodeFaces(ByteBuffer buffer) throws EncodingException{
+		ArrayList<FaceStatus> faces = new ArrayList<>();
+		TlvDecoder decoder = new TlvDecoder(buffer);
+		int parentEndOffset;
+		do{
+			parentEndOffset = decoder.readNestedTlvsStart(FACE_STATUS);
+			faces.add(decodeFaceStatus(decoder, parentEndOffset));
+			decoder.finishNestedTlvs(parentEndOffset);
+		}
+		while(parentEndOffset < buffer.limit());
+		return faces;
+	}
+	
+	/**
+	 * Decode one face status using the current decoder
+	 * @param buffer
+	 * @return
+	 * @throws EncodingException 
+	 */
+	private static FaceStatus decodeFaceStatus(TlvDecoder decoder, int parentEndOffset) throws EncodingException{
+		FaceStatus status = new FaceStatus();
+		
+		// parse
+		status.faceId = (int) decoder.readNonNegativeIntegerTlv(FACE_ID);
+		Blob uri = new Blob(decoder.readBlobTlv(URI), true); // copy because buffer is immutable
+		status.uri = uri.toString();
+		Blob localUri = new Blob(decoder.readBlobTlv(LOCAL_URI), true); // copy because buffer is immutable
+		status.localUri = localUri.toString();
+		status.expirationPeriod = (int) decoder.readOptionalNonNegativeIntegerTlv(EXPIRATION_PERIOD, parentEndOffset);
+		status.faceScope = FaceScope.values()[(int) decoder.readNonNegativeIntegerTlv(FACE_SCOPE)];
+		status.facePersistency = FacePersistency.values()[(int) decoder.readNonNegativeIntegerTlv(FACE_PERSISTENCY)];
+		status.linkType = LinkType.values()[(int) decoder.readNonNegativeIntegerTlv(LINK_TYPE)];
+		status.inInterests = (int) decoder.readNonNegativeIntegerTlv(N_IN_INTERESTS);
+		status.inDatas = (int) decoder.readNonNegativeIntegerTlv(N_IN_DATAS);
+		status.outInterests = (int) decoder.readNonNegativeIntegerTlv(N_OUT_INTERESTS);
+		status.outDatas = (int) decoder.readNonNegativeIntegerTlv(N_OUT_DATAS);
+		status.inBytes = (int) decoder.readNonNegativeIntegerTlv(N_IN_BYTES);
+		status.outBytes = (int) decoder.readNonNegativeIntegerTlv(N_OUT_BYTES);
+		
+		return status;
+	}
+
+}
diff --git a/src/main/java/com/intel/jndn/management/LinkType.java b/src/main/java/com/intel/jndn/management/LinkType.java
new file mode 100644
index 0000000..e69513e
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/LinkType.java
@@ -0,0 +1,31 @@
+/*
+ * File name: LinkType.java
+ * 
+ * Purpose: Indicate the type of communication link; used by FaceStatus
+ * See http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+/**
+ * Indicate the type of communication link; used by FaceStatus
+ * See http://redmine.named-data.net/projects/nfd/widi/FaceMgmt
+ * @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/src/main/java/com/intel/jndn/management/NFD.java b/src/main/java/com/intel/jndn/management/NFD.java
new file mode 100644
index 0000000..e19f517
--- /dev/null
+++ b/src/main/java/com/intel/jndn/management/NFD.java
@@ -0,0 +1,303 @@
+/*
+ * File name: NFD.java
+ * 
+ * Purpose: Helper class for interacting with an NDN forwarder daemon;
+ * see http://redmine.named-data.net/projects/nfd/wiki/Management for 
+ * explanations of the various protocols used.
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+import com.intel.jndn.utils.Client;
+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 org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Helper class for interacting with an NDN forwarder daemon; see
+ * http://redmine.named-data.net/projects/nfd/wiki/Management for explanations
+ * of the various protocols used.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NFD {
+
+	public final static long DEFAULT_TIMEOUT = 2000;
+	private static final Logger logger = LogManager.getLogger();
+
+	/**
+	 * 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 
+	 * @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
+		Data data = Client.getDefault().getSync(face, interest);
+		return data != null;
+	}
+
+	/**
+	 * 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
+	 * @throws Exception
+	 */
+	public static List<FaceStatus> getFaceList(Face forwarder) throws Exception {
+		// build management Interest packet; see http://redmine.named-data.net/projects/nfd/wiki/StatusDataset
+		Interest interest = new Interest(new Name("/localhost/nfd/faces/list"));
+		interest.setMustBeFresh(true);
+		interest.setChildSelector(Interest.CHILD_SELECTOR_RIGHT);
+		interest.setInterestLifetimeMilliseconds(DEFAULT_TIMEOUT);
+
+		// TODO verify that all faces are being returned; right now they don't
+		// match up with the results from nfd-status-http-server but no 
+		// additional segments are present;  
+		// see http://redmine.named-data.net/projects/nfd/wiki/StatusDataset
+		// send packet
+		Data data = Client.getDefault().getSync(forwarder, interest);
+		if (data == null) {
+			throw new Exception("Failed to retrieve list of faces from the forwarder.");
+		}
+
+		// parse packet
+		return FaceStatus.decode(data);
+	}
+
+	/**
+	 * Helper method to register a new face on the forwarder; as mentioned at
+	 * http://named-data.net/doc/NFD/current/manpages/nfdc.html, this is more
+	 * for debugging; use 'register' instead
+	 *
+	 * @param forwarder Only a localhost Face
+	 * @param faceId
+	 * @param prefix
+	 * @return
+	 * @throws Exception
+	 */
+	public static boolean 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
+		return sendCommandAndErrorCheck(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));
+
+		// check for body
+		if (response.Body.isEmpty()) {
+			throw new Exception("Failed to create face: " + uri);
+		}
+
+		// return
+		return response.Body.get(0).getFaceId();
+	}
+
+	/**
+	 * Register a route on the forwarder; see
+	 * http://named-data.net/doc/NFD/current/manpages/nfdc.html for command-line
+	 * usage and http://redmine.named-data.net/projects/nfd/wiki/RibMgmt 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
+	 * @return
+	 * @throws Exception
+	 */
+	public static boolean 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
+		return sendCommandAndErrorCheck(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
+	 * @return true for successful registration
+	 * @throws java.lang.Exception
+	 */
+	public static boolean register(Face forwarder, String uri, Name route, int cost) throws Exception {
+		// create the new face
+		int faceId = createFace(forwarder, uri);
+
+		// run base method
+		return 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
+	 * @return true for successful registration
+	 * @throws java.lang.Exception
+	 */
+	public static boolean 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
+		return register(forwarder, parameters);
+	}
+
+	/**
+	 * Set a strategy on the forwarder; see
+	 * http://named-data.net/doc/NFD/current/manpages/nfdc.html for command-line
+	 * usage and http://redmine.named-data.net/projects/nfd/wiki/StrategyChoice
+	 * 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
+	 * @return true for successful command
+	 * @throws Exception
+	 */
+	public static boolean 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
+		return sendCommandAndErrorCheck(forwarder, new Interest(command));
+	}
+
+	/**
+	 * 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
+	 * @param interest As described at
+	 * http://redmine.named-data.net/projects/nfd/wiki/ControlCommand, the
+	 * requested interest must have encoded ControlParameters appended to the
+	 * interest name
+	 * @return
+	 * @throws net.named_data.jndn.security.SecurityException
+	 * @throws java.io.IOException
+	 * @throws net.named_data.jndn.encoding.EncodingException
+	 */
+	public static ControlResponse sendCommand(Face forwarder, Interest interest) throws SecurityException, IOException, EncodingException {
+		forwarder.makeCommandInterest(interest);
+
+		// send command packet
+		Data data = Client.getDefault().getSync(forwarder, interest);
+		if (data == null) {
+			throw new IOException("Failed to receive command response.");
+		}
+
+		// return response
+		return ControlResponse.decode(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.
+	 *
+	 * @param forwarder Only a localhost Face
+	 * @param interest As described at
+	 * http://redmine.named-data.net/projects/nfd/wiki/ControlCommand, the
+	 * requested interest must have encoded ControlParameters appended to the
+	 * interest name
+	 * @return
+	 * @throws net.named_data.jndn.security.SecurityException
+	 * @throws java.io.IOException
+	 * @throws net.named_data.jndn.encoding.EncodingException
+	 */
+	public static boolean sendCommandAndErrorCheck(Face forwarder, Interest interest) throws SecurityException, IOException, EncodingException {
+		ControlResponse response = sendCommand(forwarder, interest);
+		if (response.StatusCode < 400) {
+			return true;
+		} else {
+			logger.warn("Command sent but failed: " + response.StatusCode + " " + response.StatusText);
+			return false;
+		}
+	}
+}
diff --git a/src/test/java/com/intel/jndn/management/FaceStatusTest.java b/src/test/java/com/intel/jndn/management/FaceStatusTest.java
new file mode 100644
index 0000000..c4d5ba0
--- /dev/null
+++ b/src/test/java/com/intel/jndn/management/FaceStatusTest.java
@@ -0,0 +1,126 @@
+/*
+ * File name: FaceStatusTest.java
+ * 
+ * Purpose: Test whether the decoding for the face management service is 
+ * working correctly
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.management;
+
+import com.intel.jndn.utils.Client;
+import java.util.List;
+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.util.Blob;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Test whether the decoding for the face management service is working
+ * correctly
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class FaceStatusTest {
+	
+	private static final Logger logger = LogManager.getLogger();
+
+	/**
+	 * Test of decode method, of class FaceStatus.
+	 *
+	 * @throws java.lang.Exception
+	 */
+	@Test
+	public void testDecode() throws Exception {
+		Data data = getFaceData(true);
+		List<FaceStatus> results = FaceStatus.decode(data);
+		assertTrue(results.size() > 4);
+		for (FaceStatus f : results) {
+			// the first face (face 1) should always be the internal face
+			if (f.faceId == 1) {
+				assertEquals("internal://", f.uri);
+				assertEquals("internal://", f.localUri);
+			}
+		}
+	}
+
+	/**
+	 * Integration test to run on actual system
+	 *
+	 * @param args
+	 * @throws EncodingException
+	 */
+	public static void main(String[] args) throws EncodingException {
+		Data data = getFaceData(false);
+		List<FaceStatus> results = FaceStatus.decode(data);
+		assertTrue(results.size() > 4);
+		for (FaceStatus f : results) {
+			// the first face (face 1) should always be the internal face
+			if (f.faceId == 1) {
+				assertEquals("internal://", f.uri);
+				assertEquals("internal://", f.localUri);
+			}
+		}
+	}
+
+	/**
+	 * Retrieve a TLV encoded representation of the face list data
+	 *
+	 * @param usePreComputedData to avoid errors when local NFD is not present
+	 * @return
+	 */
+	private static Data getFaceData(boolean usePreComputedData) {
+		// use pre-computed data to avoid errors when local NFD is not present
+		if (usePreComputedData) {
+			Data data = new Data();
+			data.setContent(new Blob(hexStringToByteArray(DATA)));
+			return data;
+		} // alternately, query the actual localhost for current data
+		else {
+			Face forwarder = new Face("localhost");
+
+			// build management Interest packet; see http://redmine.named-data.net/projects/nfd/wiki/StatusDataset
+			Interest interest = new Interest(new Name("/localhost/nfd/faces/list"));
+			interest.setMustBeFresh(true);
+			interest.setChildSelector(Interest.CHILD_SELECTOR_RIGHT);
+			interest.setInterestLifetimeMilliseconds(2000.0);
+
+			// send packet
+			Data data = Client.getDefault().getSync(forwarder, interest);
+			String hex = data.getContent().toHex();
+			logger.info("Hex dump of face list: " + hex);
+			return data;
+		}
+	}
+
+	/**
+	 * Pre-computed face list from a vanilla NFD running on Ubuntu 14.04
+	 */
+	private static final String DATA = "803a690101720b696e7465726e616c3a2f2f810b696e7465726e616c3a2f2f840101850100860100900100910201429202063993010094010095010080406901fe720f636f6e74656e7473746f72653a2f2f810f636f6e74656e7473746f72653a2f2f84010185010086010090010091010092010093010094010095010080306901ff72076e756c6c3a2f2f81076e756c6c3a2f2f8401018501008601009001009101009201009301009401009501008053690201007219756470343a2f2f3232342e302e32332e3137303a35363336338117756470343a2f2f31302e35342e31322e373a35363336338401008501008601009001009101009201009301009401009501008056690201017219756470343a2f2f3232342e302e32332e3137303a3536333633811a756470343a2f2f3139322e3136382e35302e35373a3536333633840100850100860100900100910100920100930100940100950100804869020102721b65746865723a2f2f5b30313a30303a35653a30303a31373a61615d810a6465763a2f2f65746830840100850100860100900100910100920100930100940100950100804969020103721b65746865723a2f2f5b30313a30303a35653a30303a31373a61615d810b6465763a2f2f776c616e30840100850100860100900100910100920100930100940100950100804669020104720766643a2f2f32328114756e69783a2f2f2f72756e2f6e66642e736f636b840101850101860100900206349101019201019302012e940400014079950400041903804e690201197216746370343a2f2f3132372e302e302e313a35363336358115746370343a2f2f3132372e302e302e313a36333633840101850101860100900101910100920100930100940132950100";
+
+	/**
+	 * Convert hex string to bytes; special thanks to
+	 * http://stackoverflow.com/questions/140131
+	 *
+	 * @param s
+	 * @return
+	 */
+	private static byte[] hexStringToByteArray(String s) {
+		int len = s.length();
+		byte[] data = new byte[len / 2];
+		for (int i = 0; i < len; i += 2) {
+			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+					+ Character.digit(s.charAt(i + 1), 16));
+		}
+		return data;
+	}
+
+}