net.named_data.jndn.encrypt.Producer Maven / Gradle / Ivy
/**
* Copyright (C) 2015-2019 Regents of the University of California.
* @author: Jeff Thompson
* @author: From ndn-group-encrypt src/producer https://github.com/named-data/ndn-group-encrypt
* @author: excludeRange from ndn-cxx https://github.com/named-data/ndn-cxx/blob/master/ndn-cxx/exclude.cpp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
* A copy of the GNU Lesser General Public License is in the file COPYING.
*/
package net.named_data.jndn.encrypt;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import net.named_data.jndn.Data;
import net.named_data.jndn.Exclude;
import net.named_data.jndn.Face;
import net.named_data.jndn.Interest;
import net.named_data.jndn.Link;
import net.named_data.jndn.Name;
import net.named_data.jndn.NetworkNack;
import net.named_data.jndn.OnData;
import net.named_data.jndn.OnNetworkNack;
import net.named_data.jndn.OnTimeout;
import net.named_data.jndn.encoding.EncodingException;
import net.named_data.jndn.encrypt.EncryptError.ErrorCode;
import net.named_data.jndn.encrypt.EncryptError.OnError;
import net.named_data.jndn.encrypt.algo.AesAlgorithm;
import net.named_data.jndn.encrypt.algo.EncryptAlgorithmType;
import net.named_data.jndn.encrypt.algo.EncryptParams;
import net.named_data.jndn.encrypt.algo.Encryptor;
import net.named_data.jndn.security.AesKeyParams;
import net.named_data.jndn.security.KeyChain;
import net.named_data.jndn.security.SecurityException;
import net.named_data.jndn.security.pib.PibImpl;
import net.named_data.jndn.security.tpm.TpmBackEnd;
import net.named_data.jndn.util.Blob;
/**
* A Producer manages content keys used to encrypt a data packet in the
* group-based encryption protocol.
* @note This class is an experimental feature. The API may change.
*/
public class Producer {
public interface OnEncryptedKeys {
// keys is a list of Data packets with the content key encrypted by E-KEYS.
void onEncryptedKeys(List keys);
}
/**
* Create a Producer to use the given ProducerDb, Face and other values.
*
* A producer can produce data with a naming convention:
* /{prefix}/SAMPLE/{dataType}/[timestamp]
*
* The produced data packet is encrypted with a content key,
* which is stored in the ProducerDb database.
*
* A producer also needs to produce data containing a content key
* encrypted with E-KEYs. A producer can retrieve E-KEYs through the face,
* and will re-try for at most repeatAttemps times when E-KEY retrieval fails.
* @param prefix The producer name prefix. This makes a copy of the Name.
* @param dataType The dataType portion of the producer name. This makes a
* copy of the Name.
* @param face The face used to retrieve keys.
* @param keyChain The keyChain used to sign data packets.
* @param database The ProducerDb database for storing keys.
* @param repeatAttempts The maximum retry for retrieving keys.
* @param keyRetrievalLink The Link object to use in Interests for key
* retrieval. This makes a copy of the Link object. If the Link object's
* getDelegations().size() is zero, don't use it.
*/
public Producer
(Name prefix, Name dataType, Face face, KeyChain keyChain,
ProducerDb database, int repeatAttempts, Link keyRetrievalLink)
{
face_ = face;
keyChain_ = keyChain;
database_ = database;
maxRepeatAttempts_ = repeatAttempts;
// Copy the Link object.
keyRetrievalLink_ = new Link(keyRetrievalLink);
construct(prefix, dataType);
}
/**
* Create a Producer to use the given ProducerDb, Face and other values.
*
* A producer can produce data with a naming convention:
* /{prefix}/SAMPLE/{dataType}/[timestamp]
*
* The produced data packet is encrypted with a content key,
* which is stored in the ProducerDb database.
*
* A producer also needs to produce data containing a content key
* encrypted with E-KEYs. A producer can retrieve E-KEYs through the face,
* and will re-try for at most repeatAttemps times when E-KEY retrieval fails.
* @param prefix The producer name prefix. This makes a copy of the Name.
* @param dataType The dataType portion of the producer name. This makes a
* copy of the Name.
* @param face The face used to retrieve keys.
* @param keyChain The keyChain used to sign data packets.
* @param database The ProducerDb database for storing keys.
* @param repeatAttempts The maximum retry for retrieving keys.
*/
public Producer
(Name prefix, Name dataType, Face face, KeyChain keyChain,
ProducerDb database, int repeatAttempts)
{
face_ = face;
keyChain_ = keyChain;
database_ = database;
maxRepeatAttempts_ = repeatAttempts;
keyRetrievalLink_ = NO_LINK;
construct(prefix, dataType);
}
/**
* Create a Producer to use the given ProducerDb, Face and other values.
*
* A producer can produce data with a naming convention:
* /{prefix}/SAMPLE/{dataType}/[timestamp]
*
* The produced data packet is encrypted with a content key,
* which is stored in the ProducerDb database.
*
* A producer also needs to produce data containing a content key
* encrypted with E-KEYs. A producer can retrieve E-KEYs through the face,
* and will re-try for at most 3 times when E-KEY retrieval fails.
* @param prefix The producer name prefix.
* @param dataType The dataType portion of the producer name.
* @param face The face used to retrieve keys.
* @param keyChain The keyChain used to sign data packets.
* @param database The ProducerDb database for storing keys.
*/
public Producer
(Name prefix, Name dataType, Face face, KeyChain keyChain,
ProducerDb database)
{
face_ = face;
keyChain_ = keyChain;
database_ = database;
maxRepeatAttempts_ = 3;
keyRetrievalLink_ = NO_LINK;
construct(prefix, dataType);
}
private void
construct(Name prefix, Name dataType)
{
Name fixedPrefix = new Name(prefix);
Name fixedDataType = new Name(dataType);
// Fill ekeyInfo_ with all permutations of dataType, including the 'E-KEY'
// component of the name. This will be used in createContentKey to send
// interests without reconstructing names every time.
fixedPrefix.append(Encryptor.NAME_COMPONENT_READ);
while (fixedDataType.size() > 0) {
Name nodeName = new Name(fixedPrefix);
nodeName.append(fixedDataType);
nodeName.append(Encryptor.NAME_COMPONENT_E_KEY);
eKeyInfo_.put(nodeName, new KeyInfo());
fixedDataType = fixedDataType.getPrefix(-1);
}
fixedPrefix.append(dataType);
namespace_ = new Name(prefix);
namespace_.append(Encryptor.NAME_COMPONENT_SAMPLE);
namespace_.append(dataType);
}
/**
* Create the content key corresponding to the timeSlot. This first checks if
* the content key exists. For an existing content key, this returns the
* content key name directly. If the key does not exist, this creates one and
* encrypts it using the corresponding E-KEYs. The encrypted content keys are
* passed to the onEncryptedKeys callback.
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @param onEncryptedKeys If this creates a content key, then this calls
* onEncryptedKeys.onEncryptedKeys(keys) where keys is a list of encrypted
* content key Data packets. If onEncryptedKeys is null, this does not use it.
* NOTE: The library will log any exceptions thrown by this callback, but for
* better error handling the callback should catch and properly handle any
* exceptions.
* @param onError This calls onError.onError(errorCode, message) for an error.
* NOTE: The library will log any exceptions thrown by this callback, but for
* better error handling the callback should catch and properly handle any
* exceptions.
* @return The content key name.
*/
public final Name
createContentKey
(double timeSlot, OnEncryptedKeys onEncryptedKeys,
OnError onError)
throws ProducerDb.Error, IOException, SecurityException, TpmBackEnd.Error,
PibImpl.Error, KeyChain.Error
{
double hourSlot = getRoundedTimeSlot(timeSlot);
// Create the content key name.
Name contentKeyName = new Name(namespace_);
contentKeyName.append(Encryptor.NAME_COMPONENT_C_KEY);
contentKeyName.append(Schedule.toIsoString(hourSlot));
Blob contentKeyBits;
// Check if we have created the content key before.
if (database_.hasContentKey(timeSlot))
// We have created the content key. Return its name directly.
return contentKeyName;
// We haven't created the content key. Create one and add it into the database.
AesKeyParams aesParams = new AesKeyParams(128);
contentKeyBits = AesAlgorithm.generateKey(aesParams).getKeyBits();
database_.addContentKey(timeSlot, contentKeyBits);
// Now we need to retrieve the E-KEYs for content key encryption.
double timeCount = Math.round(timeSlot);
keyRequests_.put(timeCount, new KeyRequest(eKeyInfo_.size()));
KeyRequest keyRequest = (KeyRequest)keyRequests_.get(timeCount);
// Check if the current E-KEYs can cover the content key.
Exclude timeRange = new Exclude();
excludeAfter(timeRange, new Name.Component(Schedule.toIsoString(timeSlot)));
for (Object entryObj : eKeyInfo_.entrySet()) {
// For each current E-KEY.
Map.Entry entry = (Map.Entry)entryObj;
KeyInfo keyInfo = (KeyInfo)entry.getValue();
if (timeSlot < keyInfo.beginTimeSlot || timeSlot >= keyInfo.endTimeSlot) {
// The current E-KEY cannot cover the content key, so retrieve one.
keyRequest.repeatAttempts.put(entry.getKey(), 0);
sendKeyInterest
(new Interest((Name)entry.getKey()).setExclude(timeRange).setChildSelector(1),
timeSlot, onEncryptedKeys, onError);
}
else {
// The current E-KEY can cover the content key.
// Encrypt the content key directly.
Name eKeyName = new Name((Name)entry.getKey());
eKeyName.append(Schedule.toIsoString(keyInfo.beginTimeSlot));
eKeyName.append(Schedule.toIsoString(keyInfo.endTimeSlot));
encryptContentKey
(keyInfo.keyBits, eKeyName, timeSlot, onEncryptedKeys, onError);
}
}
return contentKeyName;
}
/**
* Call the main createContentKey method where onError is defaultOnError.
*/
public final Name
createContentKey(double timeSlot, OnEncryptedKeys onEncryptedKeys)
throws ProducerDb.Error, IOException, SecurityException, TpmBackEnd.Error,
PibImpl.Error, KeyChain.Error
{
return createContentKey(timeSlot, onEncryptedKeys, defaultOnError);
}
/**
* Encrypt the given content with the content key that covers timeSlot, and
* update the data packet with the encrypted content and an appropriate data
* name.
* @param data An empty Data object which is updated.
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @param content The content to encrypt.
* @param onError This calls onError.onError(errorCode, message) for an error.
* NOTE: The library will log any exceptions thrown by this callback, but for
* better error handling the callback should catch and properly handle any
* exceptions.
*/
public final void
produce(Data data, double timeSlot, Blob content, OnError onError)
throws ProducerDb.Error, IOException, SecurityException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException, InvalidKeySpecException,
TpmBackEnd.Error, PibImpl.Error, KeyChain.Error
{
// Get a content key.
Name contentKeyName = createContentKey(timeSlot, null, onError);
Blob contentKey = database_.getContentKey(timeSlot);
// Produce data.
Name dataName = new Name(namespace_);
dataName.append(Schedule.toIsoString(timeSlot));
data.setName(dataName);
EncryptParams params = new EncryptParams(EncryptAlgorithmType.AesCbc, 16);
Encryptor.encryptData(data, content, contentKeyName, contentKey, params);
keyChain_.sign(data);
}
/**
* Call the main produce method where onError is defaultOnError.
*/
public final void
produce(Data data, double timeSlot, Blob content)
throws ProducerDb.Error, IOException, SecurityException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException, InvalidKeySpecException,
TpmBackEnd.Error, PibImpl.Error, KeyChain.Error
{
produce(data, timeSlot, content, defaultOnError);
}
/**
* The default OnError callback which does nothing.
*/
public static final OnError
defaultOnError = new OnError() {
public void onError(ErrorCode errorCode, String message) {
// Do nothing.
}
};
private static class KeyInfo {
public double beginTimeSlot;
public double endTimeSlot;
public Blob keyBits;
}
private static class KeyRequest {
public KeyRequest(int interests)
{
interestCount = interests;
}
public int interestCount;
public final Map repeatAttempts =
new HashMap(); /**< The map key is the Name. The value is an int count. */
public final List encryptedKeys = new ArrayList(); // of Data.
}
/**
* Round timeSlot to the nearest whole hour, so that we can store content keys
* uniformly (by start of the hour).
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @return The start of the hour as milliseconds since Jan 1, 1970 UTC.
*/
private static double
getRoundedTimeSlot(double timeSlot)
{
return Math.round
(Math.floor(Math.round(timeSlot) / 3600000.0) * 3600000.0);
}
/**
* Send an interest with the given name through the face with callbacks to
* handleCoveringKey, handleTimeout and handleNetworkNack.
* @param interest The interest to send.
* @param timeSlot The time slot, passed to handleCoveringKey, handleTimeout
* and handleNetworkNack.
* @param onEncryptedKeys The OnEncryptedKeys callback, passed to
* handleCoveringKey, handleTimeout and handleNetworkNack.
*/
private void
sendKeyInterest
(Interest interest, final double timeSlot,
final OnEncryptedKeys onEncryptedKeys, final OnError onError)
throws IOException
{
OnData onKey = new OnData() {
public void onData(Interest interest, final Data data) {
try {
handleCoveringKey(interest, data, timeSlot, onEncryptedKeys, onError);
} catch (Exception ex) {
logger_.log(Level.SEVERE, null, ex);
}
}
};
OnTimeout onTimeout = new OnTimeout() {
public void onTimeout(Interest interest) {
try {
handleTimeout(interest, timeSlot, onEncryptedKeys, onError);
} catch (IOException ex) {
logger_.log(Level.SEVERE, null, ex);
}
}
};
OnNetworkNack onNetworkNack = new OnNetworkNack() {
public void onNetworkNack(Interest interest, NetworkNack networkNack) {
handleNetworkNack
(interest, networkNack, timeSlot, onEncryptedKeys, onError);
}
};
Interest request;
if (keyRetrievalLink_.getDelegations().size() == 0)
// We can use the supplied interest without copying.
request = interest;
else {
// Copy the supplied interest and add the Link.
request = new Interest(interest);
// This will use a cached encoding if available.
request.setLinkWireEncoding(keyRetrievalLink_.wireEncode());
}
face_.expressInterest(request, onKey, onTimeout, onNetworkNack);
}
/**
* This is called from an expressInterest timeout to update the state of
* keyRequest. Re-express the interest if the number of retrials is less than
* the max limit.
* @param interest The timed-out interest.
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @param onEncryptedKeys When there are no more interests to process, this
* calls onEncryptedKeys.onEncryptedKeys(keys) where keys is a list of
* encrypted content key Data packets. If onEncryptedKeys is null, this does
* not use it.
*/
private void
handleTimeout
(Interest interest, double timeSlot, OnEncryptedKeys onEncryptedKeys,
OnError onError)
throws IOException
{
double timeCount = Math.round(timeSlot);
KeyRequest keyRequest = (KeyRequest)keyRequests_.get(timeCount);
Name interestName = interest.getName();
if ((int)(Integer)keyRequest.repeatAttempts.get(interestName) < maxRepeatAttempts_) {
// Increase the retrial count.
keyRequest.repeatAttempts.put
(interestName, (int)(Integer)keyRequest.repeatAttempts.get(interestName) + 1);
sendKeyInterest(interest, timeSlot, onEncryptedKeys, onError);
}
else
// Treat an eventual timeout as a network Nack.
handleNetworkNack
(interest, new NetworkNack(), timeSlot, onEncryptedKeys, onError);
}
/**
* This is called from an expressInterest OnNetworkNack to handle a network
* Nack for the E-KEY requested through the Interest. Decrease the outstanding
* E-KEY interest count for the C-KEY corresponding to the timeSlot.
* @param interest The interest given to expressInterest.
* @param networkNack The returned NetworkNack (unused).
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @param onEncryptedKeys When there are no more interests to process, this
* calls onEncryptedKeys.onEncryptedKeys(keys) where keys is a list of
* encrypted content key Data packets. If onEncryptedKeys is null, this does
* not use it.
*/
private void
handleNetworkNack
(Interest interest, NetworkNack networkNack, double timeSlot,
OnEncryptedKeys onEncryptedKeys, OnError onError)
{
// We have run out of options....
double timeCount = Math.round(timeSlot);
updateKeyRequest
((KeyRequest)keyRequests_.get(timeCount), timeCount, onEncryptedKeys);
}
/**
* Decrease the count of outstanding E-KEY interests for the C-KEY for
* timeCount. If the count decreases to 0, invoke onEncryptedKeys.
* @param keyRequest The KeyRequest with the interestCount to update.
* @param timeCount The time count for indexing keyRequests_.
* @param onEncryptedKeys When there are no more interests to process, this
* calls onEncryptedKeys.onEncryptedKeys(keys) where keys is a list of
* encrypted content key Data packets. If onEncryptedKeys is null, this does
* not use it.
*/
private void
updateKeyRequest
(KeyRequest keyRequest, double timeCount, OnEncryptedKeys onEncryptedKeys)
{
--keyRequest.interestCount;
if (keyRequest.interestCount == 0 && onEncryptedKeys != null) {
try {
onEncryptedKeys.onEncryptedKeys(keyRequest.encryptedKeys);
} catch (Throwable exception) {
logger_.log(Level.SEVERE, "Error in onEncryptedKeys", exception);
}
keyRequests_.remove(timeCount);
}
}
/**
* This is called from an expressInterest OnData to check that the encryption
* key contained in data fits the timeSlot. This sends a refined interest if
* required.
* @param interest The interest given to expressInterest.
* @param data The fetched Data packet.
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @param onEncryptedKeys When there are no more interests to process, this
* calls onEncryptedKeys.onEncryptedKeys(keys) where keys is a list of
* encrypted content key Data packets. If onEncryptedKeys is null, this does
* not use it.
*/
private void
handleCoveringKey
(Interest interest, Data data, double timeSlot,
OnEncryptedKeys onEncryptedKeys, OnError onError)
throws EncodingException, ProducerDb.Error, SecurityException, IOException,
TpmBackEnd.Error, PibImpl.Error, KeyChain.Error
{
double timeCount = Math.round(timeSlot);
KeyRequest keyRequest = (KeyRequest)keyRequests_.get(timeCount);
Name interestName = interest.getName();
Name keyName = data.getName();
double begin = Schedule.fromIsoString
(keyName.get(START_TIME_STAMP_INDEX).getValue().toString());
double end = Schedule.fromIsoString
(keyName.get(END_TIME_STAMP_INDEX).getValue().toString());
if (timeSlot >= end) {
// If the received E-KEY covers some earlier period, try to retrieve an
// E-KEY covering a later one.
Exclude timeRange = new Exclude(interest.getExclude());
excludeBefore(timeRange, keyName.get(START_TIME_STAMP_INDEX));
keyRequest.repeatAttempts.put(interestName, 0);
sendKeyInterest
(new Interest(interestName).setExclude(timeRange).setChildSelector(1),
timeSlot, onEncryptedKeys, onError);
}
else {
// If the received E-KEY covers the content key, encrypt the content.
Blob encryptionKey = data.getContent();
// If everything is correct, save the E-KEY as the current key.
if (encryptContentKey
(encryptionKey, keyName, timeSlot, onEncryptedKeys, onError)) {
KeyInfo keyInfo = (KeyInfo)eKeyInfo_.get(interestName);
keyInfo.beginTimeSlot = begin;
keyInfo.endTimeSlot = end;
keyInfo.keyBits = encryptionKey;
}
}
}
/**
* Get the content key from the database_ and encrypt it for the timeSlot
* using encryptionKey.
* @param encryptionKey The encryption key value.
* @param eKeyName The key name for the EncryptedContent.
* @param timeSlot The time slot as milliseconds since Jan 1, 1970 UTC.
* @param onEncryptedKeys When there are no more interests to process, this
* calls onEncryptedKeys.onEncryptedKeys(keys) where keys is a list of
* encrypted content key Data packets. If onEncryptedKeys is null, this does
* not use it.
* @return True if encryption succeeds, otherwise false.
*/
private boolean
encryptContentKey
(Blob encryptionKey, Name eKeyName, double timeSlot,
OnEncryptedKeys onEncryptedKeys, OnError onError)
throws ProducerDb.Error, SecurityException, TpmBackEnd.Error, PibImpl.Error,
KeyChain.Error
{
double timeCount = Math.round(timeSlot);
KeyRequest keyRequest = (KeyRequest)keyRequests_.get(timeCount);
Name keyName = new Name(namespace_);
keyName.append(Encryptor.NAME_COMPONENT_C_KEY);
keyName.append(Schedule.toIsoString(getRoundedTimeSlot(timeSlot)));
Blob contentKey = database_.getContentKey(timeSlot);
Data cKeyData = new Data();
cKeyData.setName(keyName);
EncryptParams params = new EncryptParams(EncryptAlgorithmType.RsaOaep);
try {
Encryptor.encryptData
(cKeyData, contentKey, eKeyName, encryptionKey, params);
} catch (Exception ex) {
try {
onError.onError(ErrorCode.EncryptionFailure, ex.getMessage());
} catch (Exception exception) {
logger_.log(Level.SEVERE, "Error in onError", exception);
}
return false;
}
keyChain_.sign(cKeyData);
keyRequest.encryptedKeys.add(cKeyData);
updateKeyRequest(keyRequest, timeCount, onEncryptedKeys);
return true;
}
// TODO: Move this to be the main representation inside the Exclude object.
private static class ExcludeEntry {
public ExcludeEntry(Name.Component component, boolean anyFollowsComponent)
{
component_ = component;
anyFollowsComponent_ = anyFollowsComponent;
}
public Name.Component component_;
public boolean anyFollowsComponent_;
}
/**
* Create a list of ExcludeEntry from the Exclude object.
* @param exclude The Exclude object to read.
* @return A new list of ExcludeEntry.
*/
private static ArrayList
getExcludeEntries(Exclude exclude)
{
ArrayList entries = new ArrayList();
for (int i = 0; i < exclude.size(); ++i) {
if (exclude.get(i).getType() == Exclude.Type.ANY) {
if (entries.size() == 0)
// Add a "beginning ANY".
entries.add(new ExcludeEntry(new Name.Component(), true));
else
// Set anyFollowsComponent of the final component.
((ExcludeEntry)entries.get(entries.size() - 1)).anyFollowsComponent_ = true;
}
else
entries.add(new ExcludeEntry(exclude.get(i).getComponent(), false));
}
return entries;
}
/**
* Set the Exclude object from the list of ExcludeEntry.
* @param exclude The Exclude object to update.
* @param entries The list of ExcludeEntry.
*/
private static void
setExcludeEntries(Exclude exclude, ArrayList entries)
{
exclude.clear();
for (int i = 0; i < entries.size(); ++i) {
ExcludeEntry entry = (ExcludeEntry)entries.get(i);
if (i == 0 && entry.component_.getValue().size() == 0 &&
entry.anyFollowsComponent_)
// This is a "beginning ANY".
exclude.appendAny();
else {
exclude.appendComponent(entry.component_);
if (entry.anyFollowsComponent_)
exclude.appendAny();
}
}
}
/**
* Get the latest entry in the list whose component_ is less than or equal to
* component.
* @param entries The list of ExcludeEntry.
* @param component The component to compare.
* @return The index of the found entry, or -1 if not found.
*/
private static int
findEntryBeforeOrAt(ArrayList entries, Name.Component component)
{
int i = entries.size() - 1;
while (i >= 0) {
if (((ExcludeEntry)entries.get(i)).component_.compare(component) <= 0)
break;
--i;
}
return i;
}
/**
* Exclude all components in the range beginning at "from".
* @param exclude The Exclude object to update.
* @param from The first component in the exclude range.
*/
private static void
excludeAfter(Exclude exclude, Name.Component from)
{
ArrayList entries = getExcludeEntries(exclude);
int iNewFrom;
int iFoundFrom = findEntryBeforeOrAt(entries, from);
if (iFoundFrom < 0) {
// There is no entry before "from" so insert at the beginning.
entries.add(0, new ExcludeEntry(from, true));
iNewFrom = 0;
}
else {
ExcludeEntry foundFrom = (ExcludeEntry)entries.get(iFoundFrom);
if (!foundFrom.anyFollowsComponent_) {
if (foundFrom.component_.equals(from)) {
// There is already an entry with "from", so just set the "ANY" flag.
foundFrom.anyFollowsComponent_ = true;
iNewFrom = iFoundFrom;
}
else {
// Insert following the entry before "from".
entries.add(iFoundFrom + 1, new ExcludeEntry(from, true));
iNewFrom = iFoundFrom + 1;
}
}
else
// The entry before "from" already has an "ANY" flag, so do nothing.
iNewFrom = iFoundFrom;
}
// Remove entries after the new "from".
int iRemoveBegin = iNewFrom + 1;
int nRemoveNeeded = entries.size() - iRemoveBegin;
for (int i = 0; i < nRemoveNeeded; ++i)
entries.remove(iRemoveBegin);
setExcludeEntries(exclude, entries);
}
/**
* Exclude all components in the range ending at "to".
* @param exclude The Exclude object to update.
* @param to The last component in the exclude range.
*/
private static void
excludeBefore(Exclude exclude, Name.Component to)
{
excludeRange(exclude, new Name.Component(), to);
}
/**
* Exclude all components in the range beginning at "from" and ending at "to".
* @param exclude The Exclude object to update.
* @param from The first component in the exclude range.
* @param to The last component in the exclude range.
*/
private static void
excludeRange(Exclude exclude, Name.Component from, Name.Component to)
{
if (from.compare(to) >= 0) {
if (from.compare(to) == 0)
throw new Error
("excludeRange: from == to. To exclude a single component, sue excludeOne.");
else
throw new Error
("excludeRange: from must be less than to. Invalid range: [" +
from.toEscapedString() + ", " + to.toEscapedString() + "]");
}
ArrayList entries = getExcludeEntries(exclude);
int iNewFrom;
int iFoundFrom = findEntryBeforeOrAt(entries, from);
if (iFoundFrom < 0) {
// There is no entry before "from" so insert at the beginning.
entries.add(0, new ExcludeEntry(from, true));
iNewFrom = 0;
}
else {
ExcludeEntry foundFrom = (ExcludeEntry)entries.get(iFoundFrom);
if (!foundFrom.anyFollowsComponent_) {
if (foundFrom.component_.equals(from)) {
// There is already an entry with "from", so just set the "ANY" flag.
foundFrom.anyFollowsComponent_ = true;
iNewFrom = iFoundFrom;
}
else {
// Insert following the entry before "from".
entries.add(iFoundFrom + 1, new ExcludeEntry(from, true));
iNewFrom = iFoundFrom + 1;
}
}
else
// The entry before "from" already has an "ANY" flag, so do nothing.
iNewFrom = iFoundFrom;
}
// We have at least one "from" before "to", so we know this will find an entry.
int iFoundTo = findEntryBeforeOrAt(entries, to);
ExcludeEntry foundTo = (ExcludeEntry)entries.get(iFoundTo);
if (iFoundTo == iNewFrom)
// Insert the "to" immediately after the "from".
entries.add(iNewFrom + 1, new ExcludeEntry(to, false));
else {
int iRemoveEnd;
if (!foundTo.anyFollowsComponent_) {
if (foundTo.component_.equals(to))
// The "to" entry already exists. Remove up to it.
iRemoveEnd = iFoundTo;
else {
// Insert following the previous entry, which will be removed.
entries.add(iFoundTo + 1, new ExcludeEntry(to, false));
iRemoveEnd = iFoundTo + 1;
}
}
else
// "to" follows a component which is already followed by "ANY", meaning
// the new range now encompasses it, so remove the component.
iRemoveEnd = iFoundTo + 1;
// Remove intermediate entries since they are inside the range.
int iRemoveBegin = iNewFrom + 1;
int nRemoveNeeded = iRemoveEnd - iRemoveBegin;
for (int i = 0; i < nRemoveNeeded; ++i)
entries.remove(iRemoveBegin);
}
setExcludeEntries(exclude, entries);
}
private final Face face_;
private Name namespace_;
private final KeyChain keyChain_;
// Use HashMap without generics so it works with older Java compilers.
private final Map eKeyInfo_ =
new HashMap(); /**< The map key is the key Name. The value is a KeyInfo. */
private final Map keyRequests_ =
new HashMap(); /**< The map key is the double time stamp. The value is a KeyRequest. */
private final ProducerDb database_;
private final int maxRepeatAttempts_;
private final Link keyRetrievalLink_;
private static final Logger logger_ = Logger.getLogger(Producer.class.getName());
private static final int START_TIME_STAMP_INDEX = -2;
private static final int END_TIME_STAMP_INDEX = -1;
private static final Link NO_LINK = new Link();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy