Move to Java 8, with CompletableFutures, etc.
diff --git a/src/main/java/com/intel/jndn/utils/Client.java b/src/main/java/com/intel/jndn/utils/Client.java
index 6b09f9f..711d24c 100644
--- a/src/main/java/com/intel/jndn/utils/Client.java
+++ b/src/main/java/com/intel/jndn/utils/Client.java
@@ -14,10 +14,11 @@
 package com.intel.jndn.utils;
 
 import java.io.IOException;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
 import net.named_data.jndn.Data;
 import net.named_data.jndn.Face;
 import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
 
 /**
  * Base functionality provided by all NDN clients in this package.
@@ -28,23 +29,53 @@
 
   /**
    * 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.
+   * Interest and return immediately; with this method, the user is responsible
+   * for calling {@link Face#processEvents()} in order for the
+   * {@link CompletableFuture} to complete.
    *
-   * @param face
-   * @param interest
-   * @return
+   * @param face the {@link Face} on which to make the request; call
+   * {@link Face#processEvents()} separately to complete the request
+   * @param interest the {@link Interest} to send over the network
+   * @return a future {@link Data} packet
    */
-  public Future<Data> getAsync(Face face, Interest interest);
+  public CompletableFuture<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).
+   * Convenience method for calling
+   * {@link #getAsync(net.named_data.jndn.Face, net.named_data.jndn.Interest)}
+   * with a default {@link Interest} packet.
    *
-   * @param face
-   * @param interest
-   * @return
-   * @throws java.io.IOException
+   * @param face the {@link Face} on which to make the request; call
+   * {@link Face#processEvents()} separately to complete the request
+   * @param name the {@link Name} to wrap inside a default {@link Interest}
+   * @return a future {@link Data} packet
+   */
+  public CompletableFuture<Data> getAsync(Face face, Name name);
+
+  /**
+   * Synchronously retrieve the {@link Data} for an {@link Interest}; this will
+   * block until complete (i.e. either the data is received or the interest
+   * times out).
+   *
+   * @param face the {@link Face} on which to make the request; this method will
+   * call {@link Face#processEvents()} at a configurable interval until complete
+   * or timeout
+   * @param interest the {@link Interest} to send over the network
+   * @return a {@link Data} packet
+   * @throws java.io.IOException if the request fails
    */
   public Data getSync(Face face, Interest interest) throws IOException;
+  
+  /**
+   * Convenience method for calling
+   * {@link #getSync(net.named_data.jndn.Face, net.named_data.jndn.Interest)}
+   * with a default {@link Interest} packet.
+   *
+   * @param face the {@link Face} on which to make the request; call
+   * {@link Face#processEvents()} separately to complete the request
+   * @param name the {@link Name} to wrap inside a default {@link Interest}
+   * @return a {@link Data} packet
+   * @throws java.io.IOException if the request fails
+   */
+  public Data getSync(Face face, Name name) throws IOException;
 }
diff --git a/src/main/java/com/intel/jndn/utils/InternalFace.java b/src/main/java/com/intel/jndn/utils/InternalFace.java
deleted file mode 100644
index 2850363..0000000
--- a/src/main/java/com/intel/jndn/utils/InternalFace.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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/src/main/java/com/intel/jndn/utils/SegmentedClient.java b/src/main/java/com/intel/jndn/utils/SegmentedClient.java
index 28ac66b..d076906 100644
--- a/src/main/java/com/intel/jndn/utils/SegmentedClient.java
+++ b/src/main/java/com/intel/jndn/utils/SegmentedClient.java
@@ -13,20 +13,18 @@
  */
 package com.intel.jndn.utils;
 
-import com.intel.jndn.utils.client.FutureData;
-import com.intel.jndn.utils.client.SegmentedFutureData;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeoutException;
+import com.intel.jndn.utils.client.SegmentedDataReassembler;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Function;
 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.OnData;
-import net.named_data.jndn.OnTimeout;
 import net.named_data.jndn.encoding.EncodingException;
 
 /**
@@ -40,7 +38,7 @@
  *
  * @author Andrew Brown <andrew.brown@intel.com>
  */
-public class SegmentedClient implements Client {
+public class SegmentedClient extends SimpleClient {
 
   private static SegmentedClient defaultInstance;
   private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
@@ -58,6 +56,23 @@
   }
 
   /**
+   * See {@link SimpleClient#SimpleClient(long, long)}
+   *
+   * @param sleepTime
+   * @param interestLifetime
+   */
+  public SegmentedClient(long sleepTime, long interestLifetime) {
+    super(sleepTime, interestLifetime);
+  }
+
+  /**
+   * Build a client with default parameters
+   */
+  public SegmentedClient() {
+    super();
+  }
+
+  /**
    * Asynchronously send Interest packets for a segmented result; will not
    * block, but will wait for the first packet to return before sending
    * remaining interests until using the specified FinalBlockId. Will retrieve
@@ -71,43 +86,29 @@
    * will contain one FutureData with the failure exception
    */
   @Override
-  public Future<Data> getAsync(final Face face, Interest interest) {
+  public CompletableFuture<Data> getAsync(final Face face, Interest interest) {
     final long firstSegmentId = parseFirstSegmentId(interest);
-    final SegmentedFutureData segmentedData = new SegmentedFutureData(face, interest.getName());
-    final FutureData firstData = new FutureData(face, interest.getName());
-    segmentedData.add(0, firstData);
 
-    // send first interest
-    logger.log(Level.FINER, "Sending first interest for: " + interest.getName().toUri());
-    try {
-      face.expressInterest(interest, new OnData() {
-        @Override
-        public void onData(Interest interest, Data data) {
+    logger.log(Level.FINER, "Requesting segmented data packets: " + interest.getName().toUri());
+    CompletableFuture<Data> allData = super.getAsync(face, interest).thenCompose(new Function<Data, CompletionStage<Data>>() {
+      @Override
+      public CompletionStage<Data> apply(Data firstSegment) {
+        try {
+          logger.log(Level.FINER, "Received first data packet: " + interest.getName().toUri());
+          long lastSegmentId = parseLastSegmentId(firstSegment);
+          Interest template = new Interest(interest);
+          template.setName(removeSegment(firstSegment.getName()));
+
           // request subsequent segments using FinalBlockId and the Interest template
-          try {
-            long lastSegmentId = parseLastSegmentId(data);
-            Interest template = new Interest(interest);
-            template.setName(removeSegment(data.getName()));
-            requestRemainingSegments(face, segmentedData, template, firstSegmentId + 1, lastSegmentId);
-          } catch (EncodingException ex) {
-            logger.log(Level.FINER, "No segment ID found in FinalBlockId, assuming first packet is only packet.");
-          }
-          
-          // resolve the first data
-          firstData.resolve(data);
+          return requestRemainingSegments(face, template, firstSegment, firstSegmentId + 1, lastSegmentId);
+        } catch (EncodingException ex) {
+          logger.log(Level.FINER, "No segment ID found in FinalBlockId, assuming first packet is only packet.");
+          return CompletableFuture.completedFuture(firstSegment);
         }
-      }, new OnTimeout() {
-        @Override
-        public void onTimeout(Interest interest) {
-          segmentedData.reject(new TimeoutException());
-        }
-      });
-    } catch (IOException e) {
-      logger.log(Level.FINE, "IO failure while sending interest: ", e);
-      segmentedData.reject(e);
-    }
+      }
+    });
 
-    return segmentedData;
+    return allData;
   }
 
   /**
@@ -144,68 +145,18 @@
    * @param fromSegment
    * @param toSegment
    */
-  private void requestRemainingSegments(Face face, SegmentedFutureData segmentedData, Interest template, long fromSegment, long toSegment) {
-    // send interests in remaining segments
+  private CompletableFuture<Data> requestRemainingSegments(Face face, Interest template, Data firstSegment, long fromSegment, long toSegment) {
+    List<CompletableFuture<Data>> segments = new ArrayList<>();
+    segments.add(CompletableFuture.completedFuture(firstSegment));
+
     for (long i = fromSegment; i <= toSegment; i++) {
       Interest segmentedInterest = new Interest(template);
       segmentedInterest.getName().appendSegment(i);
-      Future<Data> futureData = SimpleClient.getDefault().getAsync(face, segmentedInterest);
-      segmentedData.add((int) i, futureData);
+      CompletableFuture<Data> futureData = super.getAsync(face, segmentedInterest);
+      segments.add(futureData);
     }
-  }
 
-  /**
-   * Asynchronously send Interest packets for a segmented result; see {@link #getAsync(net.named_data.jndn.Face, net.named_data.jndn.Interest)
-   * } for more information.
-   *
-   * @param face the {@link Face} on which to retrieve the packets
-   * @param name the {@link Name} of the packet to retrieve using a default
-   * interest; may optionally end with the segment component of the first
-   * segment to retrieve
-   * @return an aggregated data packet from all received segments
-   */
-  public Future<Data> getAsync(Face face, Name name) {
-    return getAsync(face, SimpleClient.getDefaultInterest(name));
-  }
-
-  /**
-   * Retrieve a segmented Data packet; see {@link #getAsync(net.named_data.jndn.Face, net.named_data.jndn.Interest)
-   * } for more information. This method will block and call
-   * {@link Face#processEvents()} until the sent interests timeout or the
-   * corresponding data packets are retrieved.
-   *
-   * @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) {
-      String message = "Failed to retrieve data: " + interest.toUri();
-      logger.log(Level.FINE, message, e);
-      throw new IOException(message, e);
-    }
-  }
-
-  /**
-   * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
-   * second timeout). This method will block and call
-   * {@link Face#processEvents()} until the sent interests timeout or the
-   * corresponding data packets are retrieved.
-   *
-   * @param face
-   * @param name
-   * @return
-   * @throws java.io.IOException
-   */
-  public Data getSync(Face face, Name name) throws IOException {
-    return getSync(face, SimpleClient.getDefaultInterest(name));
+    return new SegmentedDataReassembler(template.getName(), segments).reassemble();
   }
 
   /**
@@ -213,8 +164,8 @@
    * NDN naming conventions (see
    * http://named-data.net/doc/tech-memos/naming-conventions.pdf).
    *
-   * @param name
-   * @return
+   * @param name the {@link Name} to check
+   * @return true if the {@link Name} ends in a segment component
    */
   public static boolean hasSegment(Name name) {
     return name.get(-1).getValue().buf().get(0) == 0x00;
diff --git a/src/main/java/com/intel/jndn/utils/SimpleClient.java b/src/main/java/com/intel/jndn/utils/SimpleClient.java
index 7c2bf98..1bf8519 100644
--- a/src/main/java/com/intel/jndn/utils/SimpleClient.java
+++ b/src/main/java/com/intel/jndn/utils/SimpleClient.java
@@ -13,10 +13,9 @@
  */
 package com.intel.jndn.utils;
 
-import com.intel.jndn.utils.client.FutureData;
 import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
 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;
@@ -26,6 +25,7 @@
 import net.named_data.jndn.OnData;
 import net.named_data.jndn.OnTimeout;
 import java.util.logging.Logger;
+import net.named_data.jndn.encoding.EncodingException;
 
 /**
  * Provide a client to simplify information retrieval over the NDN network.
@@ -38,11 +38,13 @@
   public static final long DEFAULT_TIMEOUT = 2000;
   private static final Logger logger = Logger.getLogger(SimpleClient.class.getName());
   private static SimpleClient defaultInstance;
+  private final long sleepTime;
+  private final long interestLifetime;
 
   /**
    * Singleton access for simpler client use
    *
-   * @return
+   * @return a default client
    */
   public static SimpleClient getDefault() {
     if (defaultInstance == null) {
@@ -52,17 +54,32 @@
   }
 
   /**
-   * 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.
+   * Build a simple client
    *
-   * @param face
-   * @param interest
-   * @return
+   * @param sleepTime for synchronous processing, the time to sleep the thread
+   * between {@link Face#processEvents()}
+   * @param interestLifetime the {@link Interest} lifetime for default
+   * Interests; see
+   * {@link #getAsync(net.named_data.jndn.Face, net.named_data.jndn.Name)}
+   */
+  public SimpleClient(long sleepTime, long interestLifetime) {
+    this.sleepTime = sleepTime;
+    this.interestLifetime = interestLifetime;
+  }
+
+  /**
+   * Build a simple client using default parameters
+   */
+  public SimpleClient() {
+    this(DEFAULT_SLEEP_TIME, DEFAULT_TIMEOUT);
+  }
+
+  /**
+   * {@inheritDoc}
    */
   @Override
-  public Future<Data> getAsync(Face face, Interest interest) {
-    final FutureData futureData = new FutureData(face, interest.getName());
+  public CompletableFuture<Data> getAsync(Face face, Interest interest) {
+    final CompletableFuture futureData = new CompletableFuture<>();
 
     // send interest
     logger.log(Level.FINER, "Sending interest for: " + interest.getName().toUri());
@@ -70,64 +87,64 @@
       face.expressInterest(interest, new OnData() {
         @Override
         public void onData(Interest interest, Data data) {
-          futureData.resolve(data);
+          futureData.complete(data);
         }
       }, new OnTimeout() {
         @Override
         public void onTimeout(Interest interest) {
-          futureData.reject(new TimeoutException());
+          String message = interest.getInterestLifetimeMilliseconds() + "ms timeout exceeded";
+          futureData.completeExceptionally(new TimeoutException(message));
         }
       });
     } catch (IOException e) {
       logger.log(Level.FINE, "IO failure while sending interest: ", e);
-      futureData.reject(e);
+      futureData.completeExceptionally(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
+   * {@inheritDoc}
    */
-  public Future<Data> getAsync(Face face, Name name) {
+  @Override
+  public CompletableFuture<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
+   * {@inheritDoc}
    */
   @Override
   public Data getSync(Face face, Interest interest) throws IOException {
+    CompletableFuture<Data> future = getAsync(face, interest);
     try {
-      return getAsync(face, interest).get();
-    } catch (ExecutionException | InterruptedException e) {
+      // process events until complete
+      while (!future.isDone()) {
+        synchronized (face) {
+          face.processEvents();
+        }
+        
+        if (sleepTime > 0) {
+          try{
+            Thread.sleep(sleepTime);
+          }
+          catch(InterruptedException e){
+            Thread.currentThread().interrupt();
+          }
+        }
+      }
+      return future.get();
+    } catch (InterruptedException | ExecutionException | EncodingException e) {
       logger.log(Level.FINE, "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
+   * {@inheritDoc}
    */
+  @Override
   public Data getSync(Face face, Name name) throws IOException {
     return getSync(face, getDefaultInterest(name));
   }
@@ -139,8 +156,8 @@
    * @param name
    * @return
    */
-  public static Interest getDefaultInterest(Name name) {
-    Interest interest = new Interest(name, DEFAULT_TIMEOUT);
+  public Interest getDefaultInterest(Name name) {
+    Interest interest = new Interest(name, interestLifetime);
     return interest;
   }
 }
diff --git a/src/main/java/com/intel/jndn/utils/client/FutureData.java b/src/main/java/com/intel/jndn/utils/client/FutureData.java
deleted file mode 100644
index 663db81..0000000
--- a/src/main/java/com/intel/jndn/utils/client/FutureData.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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 net.named_data.jndn.Data;
-import net.named_data.jndn.Face;
-import net.named_data.jndn.Name;
-import net.named_data.jndn.OnData;
-
-/**
- * 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 extends FutureDataBase {
-
-  private Data data;
-
-  /**
-   * Constructor
-   *
-   * @param face the {@link Face} to use for processing events
-   * @param name the {@link Name} of the interest sent
-   */
-  public FutureData(Face face, Name name) {
-    super(face, name);
-  }
-
-  /**
-   * @return true if the request has completed (successfully or not)
-   */
-  @Override
-  public boolean isDone() {
-    return isResolved() || isRejected() || isCancelled();
-  }
-
-  /**
-   * Set the packet when successfully retrieved; unblocks {@link #get()}. Use
-   * this method inside an {@link OnData} callback to resolve this future.
-   *
-   * @param returnedData the {@link Data} returned from the network
-   */
-  public void resolve(Data returnedData) {
-    data = returnedData;
-  }
-
-  /**
-   * @return true if the {@link Data} has returned and been resolved with
-   * {@link #resolve(net.named_data.jndn.Data)}.
-   */
-  public boolean isResolved() {
-    return data != null;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public Data getData() {
-    return data;
-  }
-}
diff --git a/src/main/java/com/intel/jndn/utils/client/FutureDataBase.java b/src/main/java/com/intel/jndn/utils/client/FutureDataBase.java
deleted file mode 100644
index a398cb3..0000000
--- a/src/main/java/com/intel/jndn/utils/client/FutureDataBase.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.OnTimeout;
-import net.named_data.jndn.encoding.EncodingException;
-
-/**
- * Implement common functionality for re-use in NDN futures
- *
- * @author Andrew Brown <andrew.brown@intel.com>
- */
-public abstract class FutureDataBase implements Future<Data> {
-
-  private final Face face;
-  private final Name name;
-  private boolean cancelled = false;
-  private Throwable error;
-
-  /**
-   * Constructor
-   *
-   * @param face the {@link Face} to use for processing events
-   * @param name the {@link Name} of the interest sent
-   */
-  public FutureDataBase(Face face, Name name) {
-    this.face = face;
-    this.name = new Name(name);
-  }
-
-  /**
-   * Block until packet is retrieved; will call face.processEvents() until the
-   * future is resolved or rejected.
-   *
-   * @return the {@link Data} when the packet is retrieved
-   * @throws InterruptedException
-   * @throws ExecutionException
-   */
-  @Override
-  public final 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 while processing events: " + name.toUri(), e);
-      }
-    }
-
-    // case: cancelled
-    if (isCancelled()) {
-      throw new InterruptedException("Interrupted by user while retrieving packet: " + name.toUri());
-    }
-
-    // case: error
-    if (isRejected()) {
-      throw new ExecutionException("Error while retrieving packet: " + name.toUri(), error);
-    }
-
-    return getData();
-  }
-
-  /**
-   * Block until packet is retrieved or timeout is reached.
-   *
-   * @param timeout
-   * @param unit
-   * @return
-   * @throws InterruptedException
-   * @throws ExecutionException
-   * @throws TimeoutException
-   */
-  @Override
-  public final 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() && currentTime <= endTime) {
-      // process face events
-      try {
-        synchronized (face) {
-          face.processEvents();
-        }
-      } catch (EncodingException | IOException e) {
-        throw new ExecutionException("Failed to retrieve packet while processing events: " + name.toUri(), e);
-      }
-
-      currentTime = System.currentTimeMillis();
-    }
-
-    // case: cancelled
-    if (isCancelled()) {
-      throw new InterruptedException("Interrupted by user while retrieving packet: " + name.toUri());
-    }
-
-    // case: error
-    if (isRejected()) {
-      throw new ExecutionException("Error while retrieving packet: " + name.toUri(), error);
-    }
-
-    // case: timed out
-    if (currentTime > endTime) {
-      throw new TimeoutException("Timed out while retrieving packet: " + name.toUri());
-    }
-
-    return getData();
-  }
-
-  /**
-   * @return true if the request has completed (successfully or not)
-   */
-  @Override
-  public boolean isDone() {
-    return isRejected() || isCancelled();
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public final boolean cancel(boolean mayInterruptIfRunning) {
-    cancelled = true;
-    return cancelled;
-  }
-
-  /**
-   * @return true if this request is cancelled
-   */
-  @Override
-  public final boolean isCancelled() {
-    return cancelled;
-  }
-
-  /**
-   * @return true if the future has been rejected with an error
-   */
-  public final boolean isRejected() {
-    return error != null;
-  }
-
-  /**
-   * Set the exception when request failed; unblocks {@link #get()}. Use this
-   * method inside an {@link OnTimeout} callback to resolve this future.
-   *
-   * @param failureCause the error that causes this future to fail
-   */
-  public final void reject(Throwable failureCause) {
-    error = failureCause;
-  }
-
-  /**
-   * @return the {@link Data} retrieved by this future
-   */
-  public abstract Data getData() throws InterruptedException, ExecutionException;
-
-  /**
-   * @return the {@link Name} used for this future; this is currently only used
-   * for debugging purposes, not retrieval
-   */
-  public Name getName() {
-    return name;
-  }
-}
diff --git a/src/main/java/com/intel/jndn/utils/client/SegmentedDataReassembler.java b/src/main/java/com/intel/jndn/utils/client/SegmentedDataReassembler.java
new file mode 100644
index 0000000..0dfa2a7
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/client/SegmentedDataReassembler.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.intel.jndn.utils.SegmentedClient;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+
+/**
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SegmentedDataReassembler {
+  private static final Logger logger = Logger.getLogger(SegmentedDataReassembler.class.getName());
+  private final Name interestName;
+  private final List<CompletableFuture<Data>> segments;
+
+  public SegmentedDataReassembler(Name interestName, List<CompletableFuture<Data>> segments) {
+    this.interestName = interestName;
+    this.segments = segments;
+  }
+
+  public CompletableFuture<Data> reassemble(){ 
+    CompletableFuture[] segmentArray = segments.toArray(new CompletableFuture[]{});
+    CompletableFuture<Void> allComplete = CompletableFuture.allOf(segmentArray);
+    return allComplete.thenApply(new Function<Void, Data>() {
+      @Override
+      public Data apply(Void t) {
+        try {
+          logger.finer("Re-assembling data for request: " + interestName.toUri());
+          byte[] content = aggregateBytes();
+          Data data = buildAggregatePacket();
+          data.setContent(new Blob(content));
+          return data;
+        } catch (ExecutionException | InterruptedException ex) {
+          logger.log(Level.FINER, "Failed to re-assemble packet for request: " + interestName.toUri(), ex);
+          throw new RuntimeException(ex);
+        }
+      }
+    });
+  }
+
+  /**
+   * @return the array of aggregated bytes for all of the segments retrieved
+   * @throws ExecutionException
+   */
+  private byte[] aggregateBytes() throws 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: " + interestName.toUri(), e);
+      }
+    }
+    return content.toByteArray();
+  }
+
+  /**
+   * @return an aggregated {@link Data} packet with no content set
+   * @throws InterruptedException
+   * @throws ExecutionException
+   */
+  private Data buildAggregatePacket() throws InterruptedException, ExecutionException {
+    if (segments.isEmpty()) {
+      throw new IllegalStateException("Unable to re-assemble packets; no segments added: " + interestName.toUri());
+    }
+    Data firstData = segments.get(0).get();
+    Data aggregatedData = new Data(firstData);
+    Name shortenedName = SegmentedClient.removeSegment(firstData.getName());
+    aggregatedData.setName(shortenedName);
+    return aggregatedData;
+  }
+}
diff --git a/src/main/java/com/intel/jndn/utils/client/SegmentedFutureData.java b/src/main/java/com/intel/jndn/utils/client/SegmentedFutureData.java
deleted file mode 100644
index 4d58ee4..0000000
--- a/src/main/java/com/intel/jndn/utils/client/SegmentedFutureData.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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 com.intel.jndn.utils.SegmentedClient;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import net.named_data.jndn.Data;
-import net.named_data.jndn.Face;
-import net.named_data.jndn.Name;
-import net.named_data.jndn.util.Blob;
-
-/**
- * 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 extends FutureDataBase {
-
-  List<Future<Data>> segments = new ArrayList<>();
-
-  public SegmentedFutureData(Face face, Name name) {
-    super(face, name);
-  }
-
-  /**
-   * Add a future data to the list of segments
-   *
-   * @param index the numeric index in the list, see
-   * {@link List#add(int, java.lang.Object)} for more details.
-   * @param futureData the {@link Future} to add
-   */
-  public void add(int index, Future<Data> futureData) {
-    segments.add(index, futureData);
-  }
-
-  /**
-   * Add a future data to the end of the list of segments
-   *
-   * @param futureData the {@link Future} to add
-   */
-  public void add(FutureData futureData) {
-    segments.add(futureData);
-  }
-
-  /**
-   * @return true if the request has completed (successfully or not)
-   */
-  @Override
-  public boolean isDone() {
-    return isRejected() || isCancelled() || allSegmentsDone();
-  }
-  
-  /**
-   * @return true if all segments are done
-   */
-  private boolean allSegmentsDone(){
-    for (Future<Data> futureData : segments) {
-      if (!futureData.isDone()) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public Data getData() throws ExecutionException, InterruptedException {
-    byte[] content = aggregateBytes();
-    Data data = buildAggregatePacket();
-    data.setContent(new Blob(content));
-    return data;
-  }
-  
-  /**
-   * @return the array of aggregated bytes for all of the segments retrieved
-   * @throws ExecutionException
-   */
-  private byte[] aggregateBytes() throws 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: " + getName().toUri(), e);
-      }
-    }
-    return content.toByteArray();
-  }
-
-  /**
-   * @return an aggregated {@link Data} packet with no content set
-   * @throws InterruptedException
-   * @throws ExecutionException
-   */
-  private Data buildAggregatePacket() throws InterruptedException, ExecutionException {
-    if (segments.isEmpty()) {
-      throw new IllegalStateException("Unable to aggregate packets; no segments added with SegmentedFutureData.add().");
-    }
-    Data firstData = segments.get(0).get();
-    Data aggregatedData = new Data(firstData);
-    aggregatedData.setName(parseName(firstData));
-    return aggregatedData;
-  }
-
-  /**
-   * @return parse the name of the segmented packet from the first packet; this
-   * will remove the segment number if it is the last name component
-   * @throws InterruptedException
-   * @throws ExecutionException
-   */
-  private Name parseName(Data data) throws InterruptedException, ExecutionException {
-    Name firstPacketName = data.getName();
-    if (SegmentedClient.hasSegment(firstPacketName)) {
-      firstPacketName = firstPacketName.getPrefix(-1);
-    }
-    return firstPacketName;
-  }
-}
diff --git a/src/test/java/com/intel/jndn/utils/SegmentedClientTest.java b/src/test/java/com/intel/jndn/utils/SegmentedClientTest.java
index 823223d..2ee0ae4 100644
--- a/src/test/java/com/intel/jndn/utils/SegmentedClientTest.java
+++ b/src/test/java/com/intel/jndn/utils/SegmentedClientTest.java
@@ -14,19 +14,17 @@
 package com.intel.jndn.utils;
 
 import com.intel.jndn.mock.MockFace;
-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.Logger;
 import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
 import net.named_data.jndn.Interest;
+import net.named_data.jndn.InterestFilter;
 import net.named_data.jndn.Name;
 import net.named_data.jndn.Name.Component;
-import net.named_data.jndn.OnInterest;
-import net.named_data.jndn.transport.Transport;
+import net.named_data.jndn.OnInterestCallback;
 import net.named_data.jndn.util.Blob;
 import org.junit.Test;
 import static org.junit.Assert.*;
@@ -40,26 +38,26 @@
 public class SegmentedClientTest {
 
   private static final Logger logger = Logger.getLogger(SimpleClient.class.getName());
+  private SegmentedClient instance;
   private MockFace face;
 
   @Before
   public void beforeTest() {
     face = new MockFace();
+    instance = new SegmentedClient(1, 100); // warning: setting an interest 
+    // lifetime that is too low will cause the getSync() tests to fail due to
+    // Thread.sleep()
   }
 
-  /**
-   * Test of getSync method, of class SegmentedClient.
-   *
-   * @throws java.lang.Exception
-   */
   @Test
   public void testGetSync() throws Exception {
-    face.registerPrefix(new Name("/segmented/data"), new OnInterest() {
+    Name name = new Name("/segmented/data");
+    face.registerPrefix(name, new OnInterestCallback() {
       private int count = 0;
       private int max = 9;
 
       @Override
-      public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+      public void onInterest(Name prefix, Interest interest, Face face, long interestFilterId, InterestFilter filter) {
         Data data = new Data(interest.getName());
         if (!SegmentedClient.hasSegment(data.getName())) {
           data.getName().appendSegment(0);
@@ -67,14 +65,15 @@
         data.getMetaInfo().setFinalBlockId(Component.fromNumberWithMarker(max, 0x00));
         data.setContent(new Blob("."));
         try {
-          transport.send(data.wireEncode().buf());
+          face.putData(data);
         } catch (IOException e) {
           fail(e.getMessage());
         }
       }
     }, null);
 
-    Data data = SegmentedClient.getDefault().getSync(face, new Name("/segmented/data").appendSegment(0));
+    logger.info("Client retrieving segments synchronously: " + name.toUri());
+    Data data = instance.getSync(face, new Name(name).appendSegment(0));
     assertEquals(10, data.getContent().size());
   }
 
@@ -83,40 +82,23 @@
    *
    * @throws java.lang.Exception
    */
-  @Test(expected = ExecutionException.class)
-  public void testFailureToRetrieve() throws Exception {
-    // retrieve non-existent data, should timeout
-    logger.info("Client expressing interest asynchronously: /test/no-data");
-    Future<Data> futureData = SegmentedClient.getDefault().getAsync(face, new Name("/test/no-data"));
-    futureData.get();
-  }
+//  @Test(expected = ExecutionException.class)
+//  public void testFailureToRetrieve() throws Exception {
+//    // retrieve non-existent data, should timeout
+//    logger.info("Client retrieving segments asynchronously: /test/no-data");
+//    Future<Data> futureData = instance.getAsync(face, new Name("/test/no-data"));
+//    face.processEvents();
+//    futureData.get();
+//  }
 
-  /**
-   * Test that a sync failed request fails with an exception.
-   */
-  @Test(expected = IOException.class)
-  public void testSyncFailureToRetrieve() throws IOException {
-    SegmentedClient.getDefault().getSync(face, new Name("/test/no-data"));
-  }
-
-  /**
-   * Identifies bug where the last Name.Component was always cut off.
-   *
-   * @throws InterruptedException
-   * @throws ExecutionException
-   */
-  @Test
-  public void testNameShorteningLogic() throws InterruptedException, ExecutionException {
-    final List<Data> segments = buildSegmentedData("/test/name", 10);
-    for (Data segment : segments) {
-      face.addResponse(segment.getName(), segment);
-    }
-
-    Name name = new Name("/test/name").appendSegment(0);
-
-    SegmentedFutureData future = (SegmentedFutureData) SegmentedClient.getDefault().getAsync(face, name);
-    assertEquals(name.getPrefix(-1).toUri(), future.get().getName().toUri());
-  }
+//  /**
+//   * Test that a sync failed request fails with an exception.
+//   */
+//  @Test(expected = IOException.class)
+//  public void testSyncFailureToRetrieve() throws IOException {
+//    logger.info("Client retrieving segments synchronously: /test/no-data");
+//    instance.getSync(face, new Name("/test/no-data"));
+//  }
 
   /**
    * Verify that Data returned with a different Name than the Interest is still
@@ -126,7 +108,7 @@
    */
   @Test
   public void testWhenDataNameIsLongerThanInterestName() throws Exception {
-    final List<Data> segments = buildSegmentedData("/a/b/c/d", 10);
+    final List<Data> segments = TestHelper.buildSegments(new Name("/a/b/c/d"), 0, 10);
     for (Data segment : segments) {
       face.addResponse(segment.getName(), segment);
     }
@@ -134,7 +116,8 @@
     Name name = new Name("/a/b");
     face.addResponse(name, segments.get(0));
 
-    Data data = SegmentedClient.getDefault().getSync(face, name);
+    logger.info("Client retrieving segments synchronously: " + name.toUri());
+    Data data = instance.getSync(face, name);
     assertNotNull(data);
     assertEquals("/a/b/c/d", data.getName().toUri());
   }
@@ -148,10 +131,12 @@
   @Test
   public void testNoContent() throws Exception {
     Name name = new Name("/test/no-content").appendSegment(0);
-    Data data = buildSegmentedData(name);
+    Data data = TestHelper.buildData(name, "", 0);
     face.addResponse(name, data);
 
-    Future<Data> result = SegmentedClient.getDefault().getAsync(face, name);
+    logger.info("Client retrieving segments asynchronously: /test/no-content");
+    Future<Data> result = instance.getAsync(face, name);
+    face.processEvents();
     assertEquals("/test/no-content", result.get().getName().toUri());
     assertEquals("", result.get().getContent().toString());
   }
@@ -174,7 +159,10 @@
     data1.getMetaInfo().setFinalBlockId(Name.Component.fromNumberWithMarker(1, 0x00));
     face.addResponse(data2.getName(), data2);
 
-    Future<Data> result = SegmentedClient.getDefault().getAsync(face, new Name("/test/content-length").appendSegment(0));
+    logger.info("Client retrieving segments asynchronously: /test/content-length");
+    Future<Data> result = instance.getAsync(face, new Name("/test/content-length").appendSegment(0));
+    face.processEvents();
+    face.processEvents();
     assertEquals(20, result.get().getContent().size());
   }
 
@@ -191,35 +179,10 @@
     data.setContent(new Blob("1"));
     face.addResponse(name, data);
 
-    Future<Data> result = SegmentedClient.getDefault().getAsync(face, name);
+    logger.info("Client retrieving segments asynchronously: /test/no-final-block-id");
+    Future<Data> result = instance.getAsync(face, name);
+    face.processEvents();
     assertEquals("/test/no-final-block-id", result.get().getName().toUri());
     assertEquals("1", result.get().getContent().toString());
   }
-
-  /**
-   * Helper method, sets FinalBlockId from last Name component
-   *
-   * @param name
-   * @return
-   */
-  private Data buildSegmentedData(Name name) {
-    Data data = new Data(name);
-    data.getMetaInfo().setFinalBlockId(name.get(-1));
-    return data;
-  }
-
-  private List<Data> buildSegmentedData(String name, int numSegments) {
-    Name.Component finalBlockId = Name.Component.fromNumberWithMarker(numSegments - 1, 0x00);
-    List<Data> segments = new ArrayList<>(numSegments);
-
-    for (int i = 0; i < numSegments; i++) {
-      Data data = new Data(new Name(name).appendSegment(i));
-      data.setContent(new Blob("0123456789"));
-      data.getMetaInfo().setFinalBlockId(finalBlockId);
-      segments.add(data);
-    }
-
-    return segments;
-  }
-
 }
diff --git a/src/test/java/com/intel/jndn/utils/SimpleClientTest.java b/src/test/java/com/intel/jndn/utils/SimpleClientTest.java
index 3cae974..35883a9 100644
--- a/src/test/java/com/intel/jndn/utils/SimpleClientTest.java
+++ b/src/test/java/com/intel/jndn/utils/SimpleClientTest.java
@@ -13,11 +13,12 @@
  */
 package com.intel.jndn.utils;
 
+import com.intel.jndn.mock.MockFace;
 import org.junit.Test;
 import static org.junit.Assert.*;
 import com.intel.jndn.mock.MockTransport;
-import com.intel.jndn.utils.client.FutureData;
 import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -25,12 +26,14 @@
 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.util.Blob;
 import org.junit.rules.ExpectedException;
 
 /**
- * Test Client.java
+ * Test SimpleClient.java
  *
  * @author Andrew Brown <andrew.brown@intel.com>
  */
@@ -39,11 +42,6 @@
   private static final Logger logger = Logger.getLogger(SimpleClient.class.getName());
   public ExpectedException thrown = ExpectedException.none();
 
-  /**
-   * Test retrieving data synchronously
-   *
-   * @throws java.io.IOException
-   */
   @Test
   public void testGetSync() throws IOException {
     // setup face
@@ -62,13 +60,8 @@
     assertEquals(new Blob("...").buf(), data.getContent().buf());
   }
 
-  /**
-   * Test retrieving data asynchronously
-   *
-   * @throws InterruptedException
-   */
   @Test
-  public void testGetAsync() throws InterruptedException, ExecutionException {
+  public void testGetAsync() throws InterruptedException, ExecutionException, IOException, EncodingException {
     // setup face
     MockTransport transport = new MockTransport();
     Face face = new Face(transport, null);
@@ -82,47 +75,56 @@
     logger.info("Client expressing interest asynchronously: /test/async");
     SimpleClient client = new SimpleClient();
     Future<Data> futureData = client.getAsync(face, new Name("/test/async"));
-
     assertTrue(!futureData.isDone());
-    futureData.get();
+    
+    // process events to retrieve data
+    face.processEvents();
     assertTrue(futureData.isDone());
     assertEquals(new Blob("...").toString(), futureData.get().getContent().toString());
   }
 
-  /**
-   * Test that asynchronous client times out correctly
-   *
-   * @throws InterruptedException
-   */
-  @Test(expected = TimeoutException.class)
-  public void testTimeout() throws InterruptedException, ExecutionException, TimeoutException {
+  @Test
+  public void testTimeout() throws Exception {
     // setup face
     MockTransport transport = new MockTransport();
     Face face = new Face(transport, null);
 
     // retrieve non-existent data, should timeout
     logger.info("Client expressing interest asynchronously: /test/timeout");
-    Future<Data> futureData = SimpleClient.getDefault().getAsync(face, new Name("/test/timeout"));
+    Interest interest = new Interest(new Name("/test/timeout"), 1);
+    CompletableFuture<Data> futureData = SimpleClient.getDefault().getAsync(face, interest);
 
-    // expect an exception
-    futureData.get(50, TimeUnit.MILLISECONDS);
+    // wait for NDN timeout
+    Thread.sleep(2);
+    face.processEvents();
+    
+    // verify that the client is completing the future with a TimeoutException
+    assertTrue(futureData.isDone());
+    assertTrue(futureData.isCompletedExceptionally());
+    try{
+      futureData.get();
+    }
+    catch(ExecutionException e){
+      assertTrue(e.getCause() instanceof TimeoutException);
+    }
   }
 
-  /**
-   * Test that a sync failed request fails with an exception.
-   */
-  @Test(expected = ExecutionException.class)
-  public void testAsyncFailureToRetrieve() throws InterruptedException, ExecutionException {
-    Future future = SimpleClient.getDefault().getAsync(new Face(), new Name("/test/no-data"));
-    future.get();
-    assertTrue(future.isDone());
+  @Test(expected = Exception.class)
+  public void testAsyncFailureToRetrieve() throws Exception {
+    Face face = new MockFace();
+    
+    logger.info("Client expressing interest asynchronously: /test/no-data");
+    Interest interest = new Interest(new Name("/test/no-data"), 10);
+    Future future = SimpleClient.getDefault().getAsync(face, interest);
+    
+    face.processEvents();
+    future.get(15, TimeUnit.MILLISECONDS);
   }
 
-  /**
-   * Test that a sync failed request fails with an exception.
-   */
   @Test(expected = IOException.class)
   public void testSyncFailureToRetrieve() throws IOException {
-    SimpleClient.getDefault().getSync(new Face(), new Name("/test/no-data"));
+    logger.info("Client expressing interest synchronously: /test/no-data");
+    Interest interest = new Interest(new Name("/test/no-data"), 10);
+    SimpleClient.getDefault().getSync(new Face(), interest);
   }
 }
diff --git a/src/test/java/com/intel/jndn/utils/SimpleClientTestIT.java b/src/test/java/com/intel/jndn/utils/SimpleClientTestIT.java
new file mode 100644
index 0000000..b60eed8
--- /dev/null
+++ b/src/test/java/com/intel/jndn/utils/SimpleClientTestIT.java
@@ -0,0 +1,103 @@
+/*
+ * 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.mock.MockKeyChain;
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+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.InterestFilter;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnInterestCallback;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.security.KeyChain;
+import net.named_data.jndn.security.SecurityException;
+import net.named_data.jndn.util.Blob;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test SimpleClient.java; requires a hostname to an NFD accepting a generated
+ * key to register prefixes, e.g. mvn test -Dnfd.ip=10.10.10.1
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SimpleClientTestIT {
+
+  private static final Logger logger = Logger.getLogger(SegmentedServerTestIT.class.getName());
+  private static final Name PREFIX = new Name("/test/for/simple-client");
+
+  Face face;
+  SimpleClient instance;
+  String ip;
+  ScheduledExecutorService pool;
+
+  public SimpleClientTestIT() throws SecurityException {
+    this.ip = System.getProperty("nfd.ip");
+    this.face = new Face(ip);
+    this.instance = SimpleClient.getDefault();
+    this.pool = Executors.newScheduledThreadPool(2);
+
+    KeyChain mockKeyChain = MockKeyChain.configure(new Name("/test/server"));
+    face.setCommandSigningInfo(mockKeyChain, mockKeyChain.getDefaultCertificateName());
+    pool.scheduleAtFixedRate(new EventProcessor(face), 0, 10, TimeUnit.MILLISECONDS);
+  }
+
+  @Test
+  public void testCompletableFuture() throws Exception {
+    Data servedData = new Data();
+    servedData.setContent(new Blob("....."));
+    face.registerPrefix(PREFIX, new OnInterestCallback() {
+      @Override
+      public void onInterest(Name prefix, Interest interest, Face face, long interestFilterId, InterestFilter filter) {
+        servedData.setName(interest.getName());
+        try {
+          face.putData(servedData);
+        } catch (IOException ex) {
+          logger.log(Level.SEVERE, "Failed to put data.", ex);
+        }
+      }
+    }, null);
+
+    CompletableFuture<Data> future = instance.getAsync(face, PREFIX);
+    Data retrievedData = future.get(200, TimeUnit.MILLISECONDS);
+
+    Assert.assertEquals(servedData.getContent().toString(), retrievedData.getContent().toString());
+  }
+
+  private class EventProcessor implements Runnable {
+
+    private final Face face;
+
+    public EventProcessor(Face face) {
+      this.face = face;
+    }
+
+    @Override
+    public void run() {
+      try {
+        face.processEvents();
+      } catch (IOException | EncodingException ex) {
+        logger.log(Level.SEVERE, null, ex);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/intel/jndn/utils/TestHelper.java b/src/test/java/com/intel/jndn/utils/TestHelper.java
new file mode 100644
index 0000000..8784a8d
--- /dev/null
+++ b/src/test/java/com/intel/jndn/utils/TestHelper.java
@@ -0,0 +1,54 @@
+/*
+ * 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.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+
+/**
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class TestHelper {
+  
+  public static List<CompletableFuture<Data>> buildFutureSegments(Name name, int from, int to) {
+    return buildSegments(name, from, to).stream()
+            .map((d) -> CompletableFuture.completedFuture(d))
+            .collect(Collectors.toList());
+  }
+
+  public static List<Data> buildSegments(Name name, int from, int to) {
+    return IntStream.range(0, 10).boxed()
+            .map((i) -> buildData(new Name(name).appendSegment(i), i.toString(), to - 1))
+            .collect(Collectors.toList());
+  }
+
+  public static Data buildData(Name name, String content) {
+    Data data = new Data(name);
+    data.setContent(new Blob(content));
+
+    return data;
+  }
+  
+  public static Data buildData(Name name, String content, int finalBlockId){
+    Data data = buildData(name, content);
+    data.getMetaInfo().setFinalBlockId(Name.Component.fromNumberWithMarker(finalBlockId, 0x00));
+    return data;
+  }
+}
diff --git a/src/test/java/com/intel/jndn/utils/client/FutureDataTest.java b/src/test/java/com/intel/jndn/utils/client/FutureDataTest.java
deleted file mode 100644
index e632cae..0000000
--- a/src/test/java/com/intel/jndn/utils/client/FutureDataTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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 com.intel.jndn.mock.MockFace;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import net.named_data.jndn.Data;
-import net.named_data.jndn.Name;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-/**
- * Test {@link FutureData}
- * @author Andrew Brown <andrew.brown@intel.com>
- */
-public class FutureDataTest {
-  
-  FutureData instance;
-  
-  public FutureDataTest(){
-    instance = new FutureData(new MockFace(), new Name("/test/future"));
-  }
-
-  /**
-   * Test of getName method, of class FutureData.
-   */
-  @Test
-  public void testGetName() {
-    assertNotNull(instance.getName());
-  }
-
-  /**
-   * Test of cancel method, of class FutureData.
-   */
-  @Test(expected = InterruptedException.class)
-  public void testCancellation() throws InterruptedException, ExecutionException {
-    instance.cancel(true);
-    assertTrue(instance.isCancelled());
-    instance.get();
-  }
-
-  /**
-   * Test of isDone method, of class FutureData.
-   */
-  @Test
-  public void testIsDone() {
-    assertFalse(instance.isDone());
-  }
-
-  /**
-   * Test of resolve method, of class FutureData.
-   */
-  @Test
-  public void testResolve() {
-    instance.resolve(new Data());
-    assertTrue(instance.isDone());
-  }
-
-  /**
-   * Test of reject method, of class FutureData.
-   */
-  @Test
-  public void testReject() {
-    instance.reject(new Error());
-    assertTrue(instance.isDone());
-  }
-
-  /**
-   * Test of get method, of class FutureData.
-   */
-  @Test
-  public void testGet_0args() throws Exception {
-    instance.resolve(new Data(new Name("/test/packet")));
-    Data result = instance.get();
-    assertEquals("/test/packet", result.getName().toUri());
-  }
-
-  /**
-   * Test of get method, of class FutureData.
-   */
-  @Test(expected = TimeoutException.class)
-  public void testGet_long_TimeUnit() throws Exception {
-    instance.get(10, TimeUnit.MILLISECONDS);
-  }
-}
diff --git a/src/test/java/com/intel/jndn/utils/client/SegmentedDataReassemblerTest.java b/src/test/java/com/intel/jndn/utils/client/SegmentedDataReassemblerTest.java
new file mode 100644
index 0000000..881b6af
--- /dev/null
+++ b/src/test/java/com/intel/jndn/utils/client/SegmentedDataReassemblerTest.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.client;
+
+import com.intel.jndn.utils.TestHelper;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Name;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ * Test that segment data packets are re-assembled correctly.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SegmentedDataReassemblerTest {
+
+  @Test
+  public void testReassemble() throws InterruptedException, ExecutionException {
+    Name name = new Name("/data/re-assembly");
+    List<CompletableFuture<Data>> segments = TestHelper.buildFutureSegments(name, 0, 10);
+    SegmentedDataReassembler instance = new SegmentedDataReassembler(name, segments);
+
+    CompletableFuture<Data> future = instance.reassemble();
+    assertTrue(future.isDone());
+    assertEquals(name.toUri(), future.get().getName().toUri()); // tests name-shortening logic
+    assertEquals("0123456789", future.get().getContent().toString());
+  }
+}
diff --git a/src/test/java/com/intel/jndn/utils/client/SegmentedFutureDataTest.java b/src/test/java/com/intel/jndn/utils/client/SegmentedFutureDataTest.java
deleted file mode 100644
index e030083..0000000
--- a/src/test/java/com/intel/jndn/utils/client/SegmentedFutureDataTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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 com.intel.jndn.mock.MockFace;
-import java.util.ArrayList;
-import java.util.List;
-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.util.Blob;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-/**
- *
- * @author Andrew Brown <andrew.brown@intel.com>
- */
-public class SegmentedFutureDataTest {
-
-  SegmentedFutureData instance;
-
-  public SegmentedFutureDataTest() {
-    Face face = new MockFace();
-    instance = new SegmentedFutureData(face, new Name("/test/packet"));
-    for (int i = 0; i < 10; i++) {
-      Data data = new Data(new Name("/test/packet").appendSegment(i));
-      data.setContent(new Blob("."));
-      FutureData future = new FutureData(face, data.getName());
-      future.resolve(data);
-      instance.add(future);
-    }
-  }
-
-  @Test
-  public void testIsDone() {
-    assertTrue(instance.isDone());
-  }
-  
-  @Test
-  public void testIsDoneWhenCancelled() {
-    instance.cancel(false);
-    assertTrue(instance.isDone());
-  }
-
-  /**
-   * Test of get method, of class SegmentedFutureData.
-   */
-  @Test
-  public void testGet_0args() throws Exception {
-    assertEquals(10, instance.get().getContent().size());
-  }
-
-  /**
-   * Test of get method, of class FutureData.
-   */
-  @Test(expected = TimeoutException.class)
-  public void testGet_long_TimeUnit() throws Exception {
-    Face face = new MockFace();
-    Data data = new Data(new Name("/test/packet").appendSegment(0));
-    FutureData future = new FutureData(face, data.getName());
-    
-    SegmentedFutureData segmentedFuture = new SegmentedFutureData(face, new Name("/test/packet"));
-    segmentedFuture.add(future);
-    
-    segmentedFuture.get(10, TimeUnit.MILLISECONDS);
-  }
-}