<commit_before>/**
 * Part of the CCNx Java Library.
 *
 * Copyright (C) 2008, 2009 Palo Alto Research Center, Inc.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation. 
 * This library 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 library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.ccnx.ccn.profiles.security.access.group;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Comparator;
import java.util.HashMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;

import org.bouncycastle.util.Arrays;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.security.keys.SecureKeyCache;
import org.ccnx.ccn.impl.support.ByteArrayCompare;
import org.ccnx.ccn.impl.support.DataUtils;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.ContentDecodingException;
import org.ccnx.ccn.io.content.ContentEncodingException;
import org.ccnx.ccn.io.content.ContentGoneException;
import org.ccnx.ccn.io.content.ContentNotReadyException;
import org.ccnx.ccn.io.content.Link;
import org.ccnx.ccn.io.content.LinkAuthenticator;
import org.ccnx.ccn.io.content.WrappedKey;
import org.ccnx.ccn.io.content.Link.LinkObject;
import org.ccnx.ccn.io.content.WrappedKey.WrappedKeyObject;
import org.ccnx.ccn.profiles.VersionMissingException;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.profiles.nameenum.EnumeratedNameList;
import org.ccnx.ccn.profiles.security.KeyProfile;
import org.ccnx.ccn.profiles.security.access.AccessControlProfile;
import org.ccnx.ccn.profiles.security.access.AccessDeniedException;
import org.ccnx.ccn.profiles.security.access.group.GroupAccessControlProfile.PrincipalInfo;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.PublisherID;


/**
 * A key directory holds a set of keys, wrapped under different
 * target keys. It is implemented as a set of wrapped key objects
 * all stored in one directory. Wrapped key objects are typically short
 * and only need one segment. The directory the keys are stored in
 * is prefixed by a version, to allow the contents to evolve. In addition
 * some potential supporting information pointing to previous
 * or subsequent versions of this key is kept. A particular wrapped key
 * entry's name would look like:
 *
 * <pre>.../v123/xxx/s0</pre>
 * <br>Where xxx is the identifier of the wrapped key.
 *
 * This structure is used for representing both node keys and group
 * (private) keys. We encapsulate functionality to walk such a directory
 * and find our target key here.
 * 
 * Our model is that higher-level function may use this interface
 * to try many ways to get a given key. Some will work (access is
 * allowed), some may not -- the latter does not mean that the
 * principal doesn't have access, just that the principal doesn't
 * have access by this route. So for the moment, we return null
 * when we don't conclusively know that this principal doesn't
 * have access to this data somehow, rather than throwing
 * AccessDeniedException.
 */
public class KeyDirectory extends EnumeratedNameList {
	
	static Comparator<byte[]> byteArrayComparator = new ByteArrayCompare();
		
	CCNHandle _handle;
	GroupAccessControlManager _manager; // to get at key cache, GroupManager
	boolean cacheHit; // true if one of the unwrapping keys is in our cache
	
	/**
	 * Maps the friendly names of principals (typically groups) to their information.
	 */
	HashMap<String, PrincipalInfo> _principals = new HashMap<String, PrincipalInfo>();
	private final ReadWriteLock _principalsLock = new ReentrantReadWriteLock();

	/**
	 * The set _keyIDs contains the digests of the (public) wrapping keys
	 * of the wrapped key objects stored in the KeyDirectory.
	 */
	TreeSet<byte []> _keyIDs = new TreeSet<byte []>(byteArrayComparator);
	private final ReadWriteLock _keyIDLock = new ReentrantReadWriteLock();

	/**
	 * The set _otherNames records the presence of superseded keys, previous keys, 
	 * group private keys, etc. 
	 */
	TreeSet<byte []> _otherNames = new TreeSet<byte []>(byteArrayComparator);
	private final ReadWriteLock _otherNamesLock = new ReentrantReadWriteLock();
	
	/**
	 * Directory name should be versioned, else we pull the latest version; start
	 * enumeration.
	 * @param manager the access control manager.
	 * @param directoryName the root of the KeyDirectory.
	 * @param handle
	 * @throws IOException
	 */
	public KeyDirectory(GroupAccessControlManager manager, ContentName directoryName, CCNHandle handle) 
					throws IOException {
		this(manager, directoryName, true, handle);
	}
	
	/**
	 * Directory name should be versioned, else we pull the latest version.
	 * @param manager the access control manager.
	 * @param directoryName the root of the KeyDirectory.
	 * @param handle
	 * @throws IOException
	 */
	public KeyDirectory(GroupAccessControlManager manager, ContentName directoryName, boolean enumerate, CCNHandle handle) 
					throws IOException {
		super(directoryName, false, handle);
		_handle = handle;
		if (null == manager) {
			stopEnumerating();
			throw new IllegalArgumentException("Manager cannot be null.");
		}	
		_manager = manager;
		cacheHit = false;
		initialize(enumerate);
	}

	/**
	 * We don't start enumerating until we get here.
	 * @throws IOException
	 */
	protected void initialize(boolean startEnumerating) throws IOException {
		if (!VersioningProfile.hasTerminalVersion(_namePrefix)) {
			ContentObject latestVersionObject = 
					VersioningProfile.getLatestVersion(_namePrefix, 
						null, SystemConfiguration.MAX_TIMEOUT, _enumerator.handle().defaultVerifier(), _enumerator.handle());
			
			if (null == latestVersionObject) {
				throw new IOException("Cannot find content for any version of " + _namePrefix + "!");
			}
			
			ContentName versionedNamePrefix = 
				latestVersionObject.name().subname(0, _namePrefix.count() + 1);
			
			if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINE)) {
				Log.fine(Log.FAC_ACCESSCONTROL, "KeyDirectory: initialize: {0} is the latest version found for {1}.", versionedNamePrefix, _namePrefix);
			}
			_namePrefix = versionedNamePrefix;
		}
		
		// We don't register prefix in constructor anymore; don't start enumerating till we finish
		// initialize. Note that if you subclass KeyDirectory, will need to override initialize().
		if (startEnumerating) {
			synchronized(_childLock) {
				_enumerator.registerPrefix(_namePrefix);
			}
		}
	}
	
	/**
	 * Called each time new data comes in, gets to parse it and load processed
	 * arrays.
	 */
	@Override
	protected void processNewChildren(SortedSet<ContentName> newChildren) {
		for (ContentName childName : newChildren) {
			// currently encapsulated in single-component ContentNames
			byte [] wkChildName = childName.lastComponent();
			if (KeyProfile.isKeyNameComponent(wkChildName)) {
				byte[] keyid = KeyProfile.getKeyIDFromNameComponent(wkChildName);
				try{
					_keyIDLock.writeLock().lock();
					_keyIDs.add(keyid);
				} finally {
					_keyIDLock.writeLock().unlock();
				}
				if (_handle.keyManager().getSecureKeyCache().containsKey(keyid)) cacheHit = true;
			} else if (PrincipalInfo.isPrincipalNameComponent(wkChildName)) {
				addPrincipal(wkChildName);
			} else {
				try{
					_otherNamesLock.writeLock().lock();
					_otherNames.add(wkChildName);
				}finally{
					_otherNamesLock.writeLock().unlock();
				}
			}
		}
	}
	
	@Override
	public boolean hasResult() { 
		// For now, we only report a result if we have an unwrapping key that is in our key cache.
		// TODO: also consider reporting results if we have an unwrapping key for a group that we know we're a member of.
		return cacheHit;
	}
	
	/**
	 * Return a copy to avoid synchronization problems.
	 * @throws ContentNotReadyException 
	 * 
	 */
	public TreeSet<byte []> getCopyOfWrappingKeyIDs() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		TreeSet<byte []> copy = new TreeSet<byte []>(byteArrayComparator);
		try {
			_keyIDLock.readLock().lock();
			for (byte[] elt: _keyIDs) copy.add(elt);
		} finally {
			_keyIDLock.readLock().unlock();
		}
		return copy; 	
	}
	
	/**
	 * Return a copy to avoid synchronization problems.
	 * @throws ContentNotReadyException 
	 */
	public HashMap<String, PrincipalInfo> getCopyOfPrincipals() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		HashMap<String, PrincipalInfo> copy = new HashMap<String, PrincipalInfo>();
		try {
			_principalsLock.readLock().lock();
			for (String key: _principals.keySet()) {
				PrincipalInfo value = _principals.get(key);
				copy.put(key, value);
			}
		} finally {
			_principalsLock.readLock().unlock();
		}
		return copy; 	
	}
	
	/**
	 * Returns principal info
	 * @throws ContentNotReadyException 
	 */
	public PrincipalInfo getPrincipalInfo(String principal) throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		PrincipalInfo pi = null;
		try {
			_principalsLock.readLock().lock();
			pi = _principals.get(principal);
		} finally {
			_principalsLock.readLock().unlock();
		}
		return pi;
	}
	
	/**
	 * Returns a copy to avoid synchronization problems
	 * @throws ContentNotReadyException 
	 */
	public TreeSet<byte []> getCopyOfOtherNames() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		TreeSet<byte []> copy = new TreeSet<byte []>(byteArrayComparator);
		try {
			_otherNamesLock.readLock().lock();
			for (byte[] elt: _otherNames) copy.add(elt);
		} finally {
			_otherNamesLock.readLock().unlock();
		}
		return copy;	
	}
	
	/**
	 * Adds a principal name
	 * @param wkChildName the principal name
	 */
	protected void addPrincipal(byte [] wkChildName) {
		PrincipalInfo pi = new PrincipalInfo(wkChildName);
		try{
				_principalsLock.writeLock().lock();
				_principals.put(pi.friendlyName(), pi);
		}finally{
			_principalsLock.writeLock().unlock();
		}
	}
	
	/**
	 * Returns the wrapped key object corresponding to a public key specified by its digest.
	 * Up to caller to decide when this is reasonable to call; should call available() on result.
	 * @param keyID the digest of the specified public key. 
	 * @return the corresponding wrapped key object.
	 * @throws ContentDecodingException 
	 * @throws IOException 
	 */
	public WrappedKeyObject getWrappedKeyForKeyID(byte [] keyID) throws ContentDecodingException, IOException {

		ContentName wrappedKeyName = getWrappedKeyNameForKeyID(keyID);
		return getWrappedKey(wrappedKeyName);
	}
	
	/**
	 * Returns the wrapped key name for a public key specified by its digest.
	 * @param keyID the digest of the public key.
	 * @return the corresponding wrapped key name.
	 */
	public ContentName getWrappedKeyNameForKeyID(byte [] keyID) {
		return KeyProfile.keyName(_namePrefix, keyID);
	}
	
	/**
	 * Returns the wrapped key object corresponding to a specified principal.
	 * @param principalName the principal.
	 * @return the corresponding wrapped key object.
	 * @throws IOException 
	 * @throws ContentNotReadyException
	 * @throws ContentDecodingException 
	 */
	public WrappedKeyObject getWrappedKeyForPrincipal(String principalName) 
		throws ContentNotReadyException, ContentDecodingException, IOException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		
		PrincipalInfo pi = null;
		try{
			_principalsLock.readLock().lock();
			if (!_principals.containsKey(principalName)) {
				return null;
			}
			pi = _principals.get(principalName);
		}finally{
			_principalsLock.readLock().unlock();
		}
		if (null == pi) {
			if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
				Log.info(Log.FAC_ACCESSCONTROL, "No block available for principal: {0}", principalName);
			}
			return null;
		}
		ContentName principalLinkName = getWrappedKeyNameForPrincipal(pi);
		// This should be a link to the actual key block
		// TODO DKS should wait on link data...
		LinkObject principalLink = new LinkObject(principalLinkName, _handle);
		if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
			Log.info(Log.FAC_ACCESSCONTROL, "Retrieving wrapped key for principal {0} at {1}", principalName, principalLink.getTargetName());
		}
		ContentName wrappedKeyName = principalLink.getTargetName();
		return getWrappedKey(wrappedKeyName);
	}
	
	/**
	 * Returns the wrapped key name for a specified principal.
	 * @param isGroup whether the principal is a group.
	 * @param principalName the name of the principal.
	 * @param principalVersion the version of the principal.
	 * @return the corresponding wrapped key name. 
	 */
	public ContentName getWrappedKeyNameForPrincipal(PrincipalInfo pi) {
		ContentName principalLinkName = new ContentName(_namePrefix, pi.toNameComponent());
		return principalLinkName;
	}
	
	/**
	 * Returns the wrapped key name for a principal specified by the name of its public key. 
	 * @param principalPublicKeyName the name of the public key of the principal.
	 * @return the corresponding wrapped key name.
	 * @throws VersionMissingException
	 * @throws ContentEncodingException 
	 */
	public ContentName getWrappedKeyNameForPrincipal(ContentName principalPublicKeyName) throws VersionMissingException, ContentEncodingException {
		PrincipalInfo info = new PrincipalInfo(_manager, principalPublicKeyName);
		return getWrappedKeyNameForPrincipal(info);
	}

	/**
	 * Checks for the existence of a superseded block.
	 * @throws ContentNotReadyException 
	 */
	public boolean hasSupersededBlock() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		boolean b = false;
		try{
			_otherNamesLock.readLock().lock();
			b = _otherNames.contains(AccessControlProfile.SUPERSEDED_MARKER.getBytes());
		} finally {
			_otherNamesLock.readLock().unlock();
		}
		return b;
	}
	
	public ContentName getSupersededBlockName() {
		return getSupersededBlockNameForKey(_namePrefix);
	}
	
	public static ContentName getSupersededBlockNameForKey(ContentName versionedKeyName) {
		return ContentName.fromNative(versionedKeyName, GroupAccessControlProfile.SUPERSEDED_MARKER);
	}
	
	/**
	 * We have several choices for how to represent superseded and previous keys.
	 * Ignoring for now the case where we might have to have more than one per key directory
	 * (e.g. if we represent removal of several interposed ACLs), we could have the
	 * wrapped key block stored in the superseded block location, and the previous
	 * key block be a link, or the previous key block be a wrapped key and the superseded
	 * location be a link. Or we could store wrapped key blocks in both places. Because
	 * the wrapped key blocks can contain the name of the key that wrapped them (but
	 * not the key being wrapped), they are in essence a pointer forward to the replacing
	 * key. So, the superseded block, if it contains a wrapped key, is both a key and a link.
	 * If the block was stored at the previous key, it would not be both a key and a link,
	 * as its wrapping key is indicated by where it is. So it should indeed be a link -- 
	 * except in the case of an interposed ACL, where there is nothing to link to; 
	 * and it instead stores a wrapped key block containing the effective node key that
	 * was the previous key.
	 * This method checks for the existence of a superseded block.
	 * @return
	 * @throws ContentNotReadyException 
	 * @throws ContentDecodingException 
	 * @throws IOException
	 */
	public WrappedKeyObject getSupersededWrappedKey() throws ContentDecodingException, ContentNotReadyException, IOException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		if (!hasSupersededBlock())
			return null;
		return getWrappedKey(getSupersededBlockName());
	}
	
	/**
	 * Returns the wrapped key object corresponding to the specified wrapped key name.
	 * @param wrappedKeyName
	 * @return
	 * @throws IOException 
	 * @throws ContentDecodingException 
	 */
	public WrappedKeyObject getWrappedKey(ContentName wrappedKeyName) throws ContentDecodingException, IOException {
		WrappedKeyObject wrappedKey = new WrappedKeyObject(wrappedKeyName, _handle);
		if (!wrappedKey.available()) { // for some reason we timed out, try again.
			wrappedKey.update();
		}
		return wrappedKey;		
	}<commit_msg>UpdateAny the key blocks in a key directory, as we don't expect more than one version.<commit_after>/**
 * Part of the CCNx Java Library.
 *
 * Copyright (C) 2008, 2009 Palo Alto Research Center, Inc.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation. 
 * This library 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 library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.ccnx.ccn.profiles.security.access.group;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Comparator;
import java.util.HashMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;

import org.bouncycastle.util.Arrays;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.security.keys.SecureKeyCache;
import org.ccnx.ccn.impl.support.ByteArrayCompare;
import org.ccnx.ccn.impl.support.DataUtils;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.ContentDecodingException;
import org.ccnx.ccn.io.content.ContentEncodingException;
import org.ccnx.ccn.io.content.ContentGoneException;
import org.ccnx.ccn.io.content.ContentNotReadyException;
import org.ccnx.ccn.io.content.Link;
import org.ccnx.ccn.io.content.LinkAuthenticator;
import org.ccnx.ccn.io.content.WrappedKey;
import org.ccnx.ccn.io.content.Link.LinkObject;
import org.ccnx.ccn.io.content.WrappedKey.WrappedKeyObject;
import org.ccnx.ccn.profiles.VersionMissingException;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.profiles.nameenum.EnumeratedNameList;
import org.ccnx.ccn.profiles.security.KeyProfile;
import org.ccnx.ccn.profiles.security.access.AccessControlProfile;
import org.ccnx.ccn.profiles.security.access.AccessDeniedException;
import org.ccnx.ccn.profiles.security.access.group.GroupAccessControlProfile.PrincipalInfo;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.PublisherID;


/**
 * A key directory holds a set of keys, wrapped under different
 * target keys. It is implemented as a set of wrapped key objects
 * all stored in one directory. Wrapped key objects are typically short
 * and only need one segment. The directory the keys are stored in
 * is prefixed by a version, to allow the contents to evolve. In addition
 * some potential supporting information pointing to previous
 * or subsequent versions of this key is kept. A particular wrapped key
 * entry's name would look like:
 *
 * <pre>.../v123/xxx/s0</pre>
 * <br>Where xxx is the identifier of the wrapped key.
 *
 * This structure is used for representing both node keys and group
 * (private) keys. We encapsulate functionality to walk such a directory
 * and find our target key here.
 * 
 * Our model is that higher-level function may use this interface
 * to try many ways to get a given key. Some will work (access is
 * allowed), some may not -- the latter does not mean that the
 * principal doesn't have access, just that the principal doesn't
 * have access by this route. So for the moment, we return null
 * when we don't conclusively know that this principal doesn't
 * have access to this data somehow, rather than throwing
 * AccessDeniedException.
 */
public class KeyDirectory extends EnumeratedNameList {
	
	static Comparator<byte[]> byteArrayComparator = new ByteArrayCompare();
		
	CCNHandle _handle;
	GroupAccessControlManager _manager; // to get at key cache, GroupManager
	boolean cacheHit; // true if one of the unwrapping keys is in our cache
	
	/**
	 * Maps the friendly names of principals (typically groups) to their information.
	 */
	HashMap<String, PrincipalInfo> _principals = new HashMap<String, PrincipalInfo>();
	private final ReadWriteLock _principalsLock = new ReentrantReadWriteLock();

	/**
	 * The set _keyIDs contains the digests of the (public) wrapping keys
	 * of the wrapped key objects stored in the KeyDirectory.
	 */
	TreeSet<byte []> _keyIDs = new TreeSet<byte []>(byteArrayComparator);
	private final ReadWriteLock _keyIDLock = new ReentrantReadWriteLock();

	/**
	 * The set _otherNames records the presence of superseded keys, previous keys, 
	 * group private keys, etc. 
	 */
	TreeSet<byte []> _otherNames = new TreeSet<byte []>(byteArrayComparator);
	private final ReadWriteLock _otherNamesLock = new ReentrantReadWriteLock();
	
	/**
	 * Directory name should be versioned, else we pull the latest version; start
	 * enumeration.
	 * @param manager the access control manager.
	 * @param directoryName the root of the KeyDirectory.
	 * @param handle
	 * @throws IOException
	 */
	public KeyDirectory(GroupAccessControlManager manager, ContentName directoryName, CCNHandle handle) 
					throws IOException {
		this(manager, directoryName, true, handle);
	}
	
	/**
	 * Directory name should be versioned, else we pull the latest version.
	 * @param manager the access control manager.
	 * @param directoryName the root of the KeyDirectory.
	 * @param handle
	 * @throws IOException
	 */
	public KeyDirectory(GroupAccessControlManager manager, ContentName directoryName, boolean enumerate, CCNHandle handle) 
					throws IOException {
		super(directoryName, false, handle);
		_handle = handle;
		if (null == manager) {
			stopEnumerating();
			throw new IllegalArgumentException("Manager cannot be null.");
		}	
		_manager = manager;
		cacheHit = false;
		initialize(enumerate);
	}

	/**
	 * We don't start enumerating until we get here.
	 * @throws IOException
	 */
	protected void initialize(boolean startEnumerating) throws IOException {
		if (!VersioningProfile.hasTerminalVersion(_namePrefix)) {
			ContentObject latestVersionObject = 
					VersioningProfile.getLatestVersion(_namePrefix, 
						null, SystemConfiguration.MAX_TIMEOUT, _enumerator.handle().defaultVerifier(), _enumerator.handle());
			
			if (null == latestVersionObject) {
				throw new IOException("Cannot find content for any version of " + _namePrefix + "!");
			}
			
			ContentName versionedNamePrefix = 
				latestVersionObject.name().subname(0, _namePrefix.count() + 1);
			
			if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINE)) {
				Log.fine(Log.FAC_ACCESSCONTROL, "KeyDirectory: initialize: {0} is the latest version found for {1}.", versionedNamePrefix, _namePrefix);
			}
			_namePrefix = versionedNamePrefix;
		}
		
		// We don't register prefix in constructor anymore; don't start enumerating till we finish
		// initialize. Note that if you subclass KeyDirectory, will need to override initialize().
		if (startEnumerating) {
			synchronized(_childLock) {
				_enumerator.registerPrefix(_namePrefix);
			}
		}
	}
	
	/**
	 * Called each time new data comes in, gets to parse it and load processed
	 * arrays.
	 */
	@Override
	protected void processNewChildren(SortedSet<ContentName> newChildren) {
		for (ContentName childName : newChildren) {
			// currently encapsulated in single-component ContentNames
			byte [] wkChildName = childName.lastComponent();
			if (KeyProfile.isKeyNameComponent(wkChildName)) {
				byte[] keyid = KeyProfile.getKeyIDFromNameComponent(wkChildName);
				try{
					_keyIDLock.writeLock().lock();
					_keyIDs.add(keyid);
				} finally {
					_keyIDLock.writeLock().unlock();
				}
				if (_handle.keyManager().getSecureKeyCache().containsKey(keyid)) cacheHit = true;
			} else if (PrincipalInfo.isPrincipalNameComponent(wkChildName)) {
				addPrincipal(wkChildName);
			} else {
				try{
					_otherNamesLock.writeLock().lock();
					_otherNames.add(wkChildName);
				}finally{
					_otherNamesLock.writeLock().unlock();
				}
			}
		}
	}
	
	@Override
	public boolean hasResult() { 
		// For now, we only report a result if we have an unwrapping key that is in our key cache.
		// TODO: also consider reporting results if we have an unwrapping key for a group that we know we're a member of.
		return cacheHit;
	}
	
	/**
	 * Return a copy to avoid synchronization problems.
	 * @throws ContentNotReadyException 
	 * 
	 */
	public TreeSet<byte []> getCopyOfWrappingKeyIDs() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		TreeSet<byte []> copy = new TreeSet<byte []>(byteArrayComparator);
		try {
			_keyIDLock.readLock().lock();
			for (byte[] elt: _keyIDs) copy.add(elt);
		} finally {
			_keyIDLock.readLock().unlock();
		}
		return copy; 	
	}
	
	/**
	 * Return a copy to avoid synchronization problems.
	 * @throws ContentNotReadyException 
	 */
	public HashMap<String, PrincipalInfo> getCopyOfPrincipals() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		HashMap<String, PrincipalInfo> copy = new HashMap<String, PrincipalInfo>();
		try {
			_principalsLock.readLock().lock();
			for (String key: _principals.keySet()) {
				PrincipalInfo value = _principals.get(key);
				copy.put(key, value);
			}
		} finally {
			_principalsLock.readLock().unlock();
		}
		return copy; 	
	}
	
	/**
	 * Returns principal info
	 * @throws ContentNotReadyException 
	 */
	public PrincipalInfo getPrincipalInfo(String principal) throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		PrincipalInfo pi = null;
		try {
			_principalsLock.readLock().lock();
			pi = _principals.get(principal);
		} finally {
			_principalsLock.readLock().unlock();
		}
		return pi;
	}
	
	/**
	 * Returns a copy to avoid synchronization problems
	 * @throws ContentNotReadyException 
	 */
	public TreeSet<byte []> getCopyOfOtherNames() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		TreeSet<byte []> copy = new TreeSet<byte []>(byteArrayComparator);
		try {
			_otherNamesLock.readLock().lock();
			for (byte[] elt: _otherNames) copy.add(elt);
		} finally {
			_otherNamesLock.readLock().unlock();
		}
		return copy;	
	}
	
	/**
	 * Adds a principal name
	 * @param wkChildName the principal name
	 */
	protected void addPrincipal(byte [] wkChildName) {
		PrincipalInfo pi = new PrincipalInfo(wkChildName);
		try{
				_principalsLock.writeLock().lock();
				_principals.put(pi.friendlyName(), pi);
		}finally{
			_principalsLock.writeLock().unlock();
		}
	}
	
	/**
	 * Returns the wrapped key object corresponding to a public key specified by its digest.
	 * Up to caller to decide when this is reasonable to call; should call available() on result.
	 * @param keyID the digest of the specified public key. 
	 * @return the corresponding wrapped key object.
	 * @throws ContentDecodingException 
	 * @throws IOException 
	 */
	public WrappedKeyObject getWrappedKeyForKeyID(byte [] keyID) throws ContentDecodingException, IOException {

		ContentName wrappedKeyName = getWrappedKeyNameForKeyID(keyID);
		return getWrappedKey(wrappedKeyName);
	}
	
	/**
	 * Returns the wrapped key name for a public key specified by its digest.
	 * @param keyID the digest of the public key.
	 * @return the corresponding wrapped key name.
	 */
	public ContentName getWrappedKeyNameForKeyID(byte [] keyID) {
		return KeyProfile.keyName(_namePrefix, keyID);
	}
	
	/**
	 * Returns the wrapped key object corresponding to a specified principal.
	 * @param principalName the principal.
	 * @return the corresponding wrapped key object.
	 * @throws IOException 
	 * @throws ContentNotReadyException
	 * @throws ContentDecodingException 
	 */
	public WrappedKeyObject getWrappedKeyForPrincipal(String principalName) 
		throws ContentNotReadyException, ContentDecodingException, IOException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		
		PrincipalInfo pi = null;
		try{
			_principalsLock.readLock().lock();
			if (!_principals.containsKey(principalName)) {
				return null;
			}
			pi = _principals.get(principalName);
		}finally{
			_principalsLock.readLock().unlock();
		}
		if (null == pi) {
			if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
				Log.info(Log.FAC_ACCESSCONTROL, "No block available for principal: {0}", principalName);
			}
			return null;
		}
		ContentName principalLinkName = getWrappedKeyNameForPrincipal(pi);
		// This should be a link to the actual key block
		// TODO DKS should wait on link data...
		LinkObject principalLink = new LinkObject(principalLinkName, _handle);
		if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
			Log.info(Log.FAC_ACCESSCONTROL, "Retrieving wrapped key for principal {0} at {1}", principalName, principalLink.getTargetName());
		}
		ContentName wrappedKeyName = principalLink.getTargetName();
		return getWrappedKey(wrappedKeyName);
	}
	
	/**
	 * Returns the wrapped key name for a specified principal.
	 * @param isGroup whether the principal is a group.
	 * @param principalName the name of the principal.
	 * @param principalVersion the version of the principal.
	 * @return the corresponding wrapped key name. 
	 */
	public ContentName getWrappedKeyNameForPrincipal(PrincipalInfo pi) {
		ContentName principalLinkName = new ContentName(_namePrefix, pi.toNameComponent());
		return principalLinkName;
	}
	
	/**
	 * Returns the wrapped key name for a principal specified by the name of its public key. 
	 * @param principalPublicKeyName the name of the public key of the principal.
	 * @return the corresponding wrapped key name.
	 * @throws VersionMissingException
	 * @throws ContentEncodingException 
	 */
	public ContentName getWrappedKeyNameForPrincipal(ContentName principalPublicKeyName) throws VersionMissingException, ContentEncodingException {
		PrincipalInfo info = new PrincipalInfo(_manager, principalPublicKeyName);
		return getWrappedKeyNameForPrincipal(info);
	}

	/**
	 * Checks for the existence of a superseded block.
	 * @throws ContentNotReadyException 
	 */
	public boolean hasSupersededBlock() throws ContentNotReadyException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		boolean b = false;
		try{
			_otherNamesLock.readLock().lock();
			b = _otherNames.contains(AccessControlProfile.SUPERSEDED_MARKER.getBytes());
		} finally {
			_otherNamesLock.readLock().unlock();
		}
		return b;
	}
	
	public ContentName getSupersededBlockName() {
		return getSupersededBlockNameForKey(_namePrefix);
	}
	
	public static ContentName getSupersededBlockNameForKey(ContentName versionedKeyName) {
		return ContentName.fromNative(versionedKeyName, GroupAccessControlProfile.SUPERSEDED_MARKER);
	}
	
	/**
	 * We have several choices for how to represent superseded and previous keys.
	 * Ignoring for now the case where we might have to have more than one per key directory
	 * (e.g. if we represent removal of several interposed ACLs), we could have the
	 * wrapped key block stored in the superseded block location, and the previous
	 * key block be a link, or the previous key block be a wrapped key and the superseded
	 * location be a link. Or we could store wrapped key blocks in both places. Because
	 * the wrapped key blocks can contain the name of the key that wrapped them (but
	 * not the key being wrapped), they are in essence a pointer forward to the replacing
	 * key. So, the superseded block, if it contains a wrapped key, is both a key and a link.
	 * If the block was stored at the previous key, it would not be both a key and a link,
	 * as its wrapping key is indicated by where it is. So it should indeed be a link -- 
	 * except in the case of an interposed ACL, where there is nothing to link to; 
	 * and it instead stores a wrapped key block containing the effective node key that
	 * was the previous key.
	 * This method checks for the existence of a superseded block.
	 * @return
	 * @throws ContentNotReadyException 
	 * @throws ContentDecodingException 
	 * @throws IOException
	 */
	public WrappedKeyObject getSupersededWrappedKey() throws ContentDecodingException, ContentNotReadyException, IOException {
		if (!hasChildren()) {
			throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
		}
		if (!hasSupersededBlock())
			return null;
		return getWrappedKey(getSupersededBlockName());
	}
	
	/**
	 * Returns the wrapped key object corresponding to the specified wrapped key name. We know
	 * there is only one version of this object, so avoid getLatestVersion.
	 * @param wrappedKeyName
	 * @return
	 * @throws IOException 
	 * @throws ContentDecodingException 
	 */
	public WrappedKeyObject getWrappedKey(ContentName wrappedKeyName) throws ContentDecodingException, IOException {
		WrappedKeyObject wrappedKey = new WrappedKeyObject(wrappedKeyName, null, null, _handle);
		wrappedKey.updateAny();
		if (!wrappedKey.available()) { // for some reason we timed out, try again.
			wrappedKey.updateAny();
		}
		return wrappedKey;		
	}
{'fraction_non_alphanumeric': 0.08880015746703034, 'fraction_numerical': 0.0017996232038916852, 'mean_word_length': 4.336734693877551, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1920455', 'n_tokens_mistral': 10958, 'n_tokens_neox': 9853, 'n_words': 3355}