blob: d07690629c4baf542adf298e601d7c011ac40fc2 [file] [log] [blame]
/*
* jndn-utils
* Copyright (c) 2015, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 3, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*/
package com.intel.jndn.utils;
import com.intel.jndn.utils.client.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.encoding.EncodingException;
/**
* Provide a client to simplify retrieving segmented Data packets over the NDN
* network. This class expects the Data producer to follow the NDN naming
* conventions (see http://named-data.net/doc/tech-memos/naming-conventions.pdf)
* and produce Data packets with a valid segment as the last component of their
* name; additionally, at least the first packet should set the FinalBlockId of
* the packet's MetaInfo (see
* http://named-data.net/doc/ndn-tlv/data.html#finalblockid).
*
* @author Andrew Brown <andrew.brown@intel.com>
*/
public class SegmentedClient extends SimpleClient {
private static SegmentedClient defaultInstance;
private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
/**
* Singleton access for simpler client use.
*
* @return
*/
public static SegmentedClient getDefault() {
if (defaultInstance == null) {
defaultInstance = new SegmentedClient();
}
return defaultInstance;
}
/**
* 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
* non-segmented packets as well.
*
* @param face the {@link Face} on which to retrieve the packets
* @param interest should include either a ChildSelector or an initial segment
* number; the initial segment number will be cut off in the de-segmented
* packet.
* @return a list of FutureData packets; if the first segment fails, the list
* will contain one FutureData with the failure exception
*/
@Override
public CompletableFuture<Data> getAsync(final Face face, Interest interest) {
final long firstSegmentId = parseFirstSegmentId(interest);
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
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);
}
}
});
return allData;
}
/**
* @param interest the request {@link Interest}
* @return the first segment the interest is requesting, or 0 if none found
*/
private long parseFirstSegmentId(Interest interest) {
try {
return interest.getName().get(-1).toSegment();
} catch (EncodingException e) {
if (interest.getChildSelector() == -1) {
logger.log(Level.WARNING, "No child selector set for a segmented Interest; this may result in incorrect retrieval.");
// allow this interest to pass without a segment marker since it may still succeed
}
return 0;
}
}
/**
* @param firstData the first returned {@link Data}
* @return the last segment number available as specified in the FinalBlockId
* @throws EncodingException
*/
private long parseLastSegmentId(Data firstData) throws EncodingException {
return firstData.getMetaInfo().getFinalBlockId().toSegment();
}
/**
* Send interests for remaining segments; adding them to the segmented future
*
* @param face
* @param segmentedData
* @param template
* @param fromSegment
* @param toSegment
*/
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);
CompletableFuture<Data> futureData = super.getAsync(face, segmentedInterest);
segments.add(futureData);
}
return new SegmentedDataReassembler(template.getName(), segments).reassemble();
}
/**
* Check if a name ends in a segment component; uses marker value found in the
* NDN naming conventions (see
* http://named-data.net/doc/tech-memos/naming-conventions.pdf).
*
* @param name 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;
}
/**
* @param name the {@link Name} to check
* @return a new instance of {@link Name} with no segment component appended
*/
public static Name removeSegment(Name name) {
return hasSegment(name) ? name.getPrefix(-1) : new Name(name);
}
}