Package com.subgraph.orchid.directory

Source Code of com.subgraph.orchid.directory.DirectoryImpl

package com.subgraph.orchid.directory;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import com.subgraph.orchid.ConsensusDocument;
import com.subgraph.orchid.ConsensusDocument.ConsensusFlavor;
import com.subgraph.orchid.ConsensusDocument.RequiredCertificate;
import com.subgraph.orchid.Descriptor;
import com.subgraph.orchid.Directory;
import com.subgraph.orchid.DirectoryServer;
import com.subgraph.orchid.DirectoryStore;
import com.subgraph.orchid.DirectoryStore.CacheFile;
import com.subgraph.orchid.GuardEntry;
import com.subgraph.orchid.KeyCertificate;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.RouterDescriptor;
import com.subgraph.orchid.RouterMicrodescriptor;
import com.subgraph.orchid.RouterStatus;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorConfig.AutoBoolValue;
import com.subgraph.orchid.TorException;
import com.subgraph.orchid.crypto.TorRandom;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.data.RandomSet;
import com.subgraph.orchid.directory.parsing.DocumentParser;
import com.subgraph.orchid.directory.parsing.DocumentParserFactory;
import com.subgraph.orchid.directory.parsing.DocumentParsingResult;
import com.subgraph.orchid.events.Event;
import com.subgraph.orchid.events.EventHandler;
import com.subgraph.orchid.events.EventManager;

public class DirectoryImpl implements Directory {
  private final static Logger logger = Logger.getLogger(DirectoryImpl.class.getName());

  private final Object loadLock = new Object();
  private boolean isLoaded = false;
 
  private final DirectoryStore store;
  private final TorConfig config;
  private final StateFile stateFile;
  private final DescriptorCache<RouterMicrodescriptor> microdescriptorCache;
  private final DescriptorCache<RouterDescriptor> basicDescriptorCache;
 
  private final Map<HexDigest, RouterImpl> routersByIdentity;
  private final Map<String, RouterImpl> routersByNickname;
  private final RandomSet<RouterImpl> directoryCaches;
  private final Set<ConsensusDocument.RequiredCertificate> requiredCertificates;
  private boolean haveMinimumRouterInfo;
  private boolean needRecalculateMinimumRouterInfo;
  private final EventManager consensusChangedManager;
  private final TorRandom random;
  private final static DocumentParserFactory parserFactory = new DocumentParserFactoryImpl();
 
  private ConsensusDocument currentConsensus;
  private ConsensusDocument consensusWaitingForCertificates;

  public DirectoryImpl(TorConfig config, DirectoryStore customDirectoryStore) {
    store = (customDirectoryStore == null) ? (new DirectoryStoreImpl(config)) : (customDirectoryStore);
    this.config = config;
    stateFile = new StateFile(store, this);
    microdescriptorCache = createMicrodescriptorCache(store);
    basicDescriptorCache = createBasicDescriptorCache(store)
    routersByIdentity = new HashMap<HexDigest, RouterImpl>();
    routersByNickname = new HashMap<String, RouterImpl>();
    directoryCaches = new RandomSet<RouterImpl>();
    requiredCertificates = new HashSet<ConsensusDocument.RequiredCertificate>();
    consensusChangedManager = new EventManager();
    random = new TorRandom();
  }

  private static DescriptorCache<RouterMicrodescriptor> createMicrodescriptorCache(DirectoryStore store) {
    return new DescriptorCache<RouterMicrodescriptor>(store, CacheFile.MICRODESCRIPTOR_CACHE, CacheFile.MICRODESCRIPTOR_JOURNAL) {
      @Override
      protected DocumentParser<RouterMicrodescriptor> createDocumentParser(ByteBuffer buffer) {
        return parserFactory.createRouterMicrodescriptorParser(buffer);
      }
    };
  }

  private static DescriptorCache<RouterDescriptor> createBasicDescriptorCache(DirectoryStore store) {
    return new DescriptorCache<RouterDescriptor>(store, CacheFile.DESCRIPTOR_CACHE, CacheFile.DESCRIPTOR_JOURNAL) {
      @Override
      protected DocumentParser<RouterDescriptor> createDocumentParser(ByteBuffer buffer) {
        return parserFactory.createRouterDescriptorParser(buffer, false);
      }
    };
  }

  public synchronized boolean haveMinimumRouterInfo() {
    if(needRecalculateMinimumRouterInfo) {
      checkMinimumRouterInfo();
    }
    return haveMinimumRouterInfo;
  }

  private synchronized void checkMinimumRouterInfo() {
    if(currentConsensus == null || !currentConsensus.isLive()) {
      needRecalculateMinimumRouterInfo = true;
      haveMinimumRouterInfo = false;
      return;
    }

    int routerCount = 0;
    int descriptorCount = 0;
    for(Router r: routersByIdentity.values()) {
      routerCount++;
      if(!r.isDescriptorDownloadable())
        descriptorCount++;
    }
    needRecalculateMinimumRouterInfo = false;
    haveMinimumRouterInfo = (descriptorCount * 4 > routerCount);
  }

  public void loadFromStore() {
    logger.info("Loading cached network information from disk");
   
    synchronized(loadLock) {
      if(isLoaded) {
        return;
      }
      boolean useMicrodescriptors = config.getUseMicrodescriptors() != AutoBoolValue.FALSE;
      last = System.currentTimeMillis();
      logger.info("Loading certificates");
      loadCertificates(store.loadCacheFile(CacheFile.CERTIFICATES));
      logElapsed();
     
      logger.info("Loading consensus");
      loadConsensus(store.loadCacheFile(useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS));
      logElapsed();
     
      if(!useMicrodescriptors) {
        logger.info("Loading descriptors");
        basicDescriptorCache.initialLoad();
      } else {
        logger.info("Loading microdescriptor cache");
        microdescriptorCache.initialLoad();
      }
      needRecalculateMinimumRouterInfo = true;
      logElapsed();
     
      logger.info("loading state file");
      stateFile.parseBuffer(store.loadCacheFile(CacheFile.STATE));
      logElapsed();
     
      isLoaded = true;
      loadLock.notifyAll();
    }
  }

  public void close() {
    basicDescriptorCache.shutdown();
    microdescriptorCache.shutdown();
  }

  private long last = 0;
  private void logElapsed() {
    final long now = System.currentTimeMillis();
    final long elapsed =  now - last;
    last = now;
    logger.fine("Loaded in "+ elapsed + " ms.");
  }

  private void loadCertificates(ByteBuffer buffer) {
    final DocumentParser<KeyCertificate> parser = parserFactory.createKeyCertificateParser(buffer);
    final DocumentParsingResult<KeyCertificate> result = parser.parse();
    if(testResult(result, "certificates")) {
      for(KeyCertificate cert: result.getParsedDocuments()) {
        addCertificate(cert);
      }
    }
  }
 
  private void loadConsensus(ByteBuffer buffer) {
    final DocumentParser<ConsensusDocument> parser = parserFactory.createConsensusDocumentParser(buffer);
    final DocumentParsingResult<ConsensusDocument> result = parser.parse();
    if(testResult(result, "consensus")) {
      addConsensusDocument(result.getDocument(), true);
    }
  }

  private boolean testResult(DocumentParsingResult<?> result, String type) {
    if(result.isOkay()) {
      return true;
    } else if(result.isError()) {
      logger.warning("Parsing error loading "+ type + " : "+ result.getMessage());
    } else if(result.isInvalid()) {
      logger.warning("Problem loading "+ type + " : "+ result.getMessage());
    } else {
      logger.warning("Unknown problem loading "+ type);
    }
    return false;
  }
 
  public void waitUntilLoaded() {
    synchronized (loadLock) {
      while(!isLoaded) {
        try {
          loadLock.wait();
        } catch (InterruptedException e) {
          logger.warning("Thread interrupted while waiting for directory to load from disk");
        }
      }
    }
  }

  public Collection<DirectoryServer> getDirectoryAuthorities() {
    return TrustedAuthorities.getInstance().getAuthorityServers();
  }

  public DirectoryServer getRandomDirectoryAuthority() {
    final List<DirectoryServer> servers = TrustedAuthorities.getInstance().getAuthorityServers();
    final int idx = random.nextInt(servers.size());
    return servers.get(idx);
  }

  public Set<ConsensusDocument.RequiredCertificate> getRequiredCertificates() {
    return new HashSet<ConsensusDocument.RequiredCertificate>(requiredCertificates);
  }
 
  public void addCertificate(KeyCertificate certificate) {
    synchronized(TrustedAuthorities.getInstance()) {
      final boolean wasRequired = removeRequiredCertificate(certificate);
      final DirectoryServer as = TrustedAuthorities.getInstance().getAuthorityServerByIdentity(certificate.getAuthorityFingerprint());
      if(as == null) {
        logger.warning("Certificate read for unknown directory authority with identity: "+ certificate.getAuthorityFingerprint());
        return;
      }
      as.addCertificate(certificate);
     
      if(consensusWaitingForCertificates != null && wasRequired) {
       
        switch(consensusWaitingForCertificates.verifySignatures()) {
        case STATUS_FAILED:
          consensusWaitingForCertificates = null;
          return;
         
        case STATUS_VERIFIED:
          addConsensusDocument(consensusWaitingForCertificates, false);
          consensusWaitingForCertificates = null;
          return;

        case STATUS_NEED_CERTS:
          requiredCertificates.addAll(consensusWaitingForCertificates.getRequiredCertificates());
          return;
        }
      }
    }
  }
 
  private boolean removeRequiredCertificate(KeyCertificate certificate) {
    final Iterator<RequiredCertificate> it = requiredCertificates.iterator();
    while(it.hasNext()) {
      RequiredCertificate r = it.next();
      if(r.getSigningKey().equals(certificate.getAuthoritySigningKey().getFingerprint())) {
        it.remove();
        return true;
      }
    }
    return false;
  }
 
  public void storeCertificates() {
    synchronized(TrustedAuthorities.getInstance()) {
      final List<KeyCertificate> certs = new ArrayList<KeyCertificate>();
      for(DirectoryServer ds: TrustedAuthorities.getInstance().getAuthorityServers()) {
        certs.addAll(ds.getCertificates());
      }
      store.writeDocumentList(CacheFile.CERTIFICATES, certs);
    }
  }

  public void addRouterDescriptors(List<RouterDescriptor> descriptors) {
    basicDescriptorCache.addDescriptors(descriptors);
    needRecalculateMinimumRouterInfo = true;
  }

  public synchronized void addConsensusDocument(ConsensusDocument consensus, boolean fromCache) {
    if(consensus.equals(currentConsensus))
      return;

    if(currentConsensus != null && consensus.getValidAfterTime().isBefore(currentConsensus.getValidAfterTime())) {
      logger.warning("New consensus document is older than current consensus document");
      return;
    }

    synchronized(TrustedAuthorities.getInstance()) {
      switch(consensus.verifySignatures()) {
      case STATUS_FAILED:
        logger.warning("Unable to verify signatures on consensus document, discarding...");
        return;
       
      case STATUS_NEED_CERTS:
        consensusWaitingForCertificates = consensus;
        requiredCertificates.addAll(consensus.getRequiredCertificates());
        return;

      case STATUS_VERIFIED:
        break;
      }
      requiredCertificates.addAll(consensus.getRequiredCertificates());
   
    }
    final Map<HexDigest, RouterImpl> oldRouterByIdentity = new HashMap<HexDigest, RouterImpl>(routersByIdentity);

    clearAll();

    for(RouterStatus status: consensus.getRouterStatusEntries()) {
      if(status.hasFlag("Running") && status.hasFlag("Valid")) {
        final RouterImpl router = updateOrCreateRouter(status, oldRouterByIdentity);
        addRouter(router);
        classifyRouter(router);
      }
      final Descriptor d = getDescriptorForRouterStatus(status, consensus.getFlavor() == ConsensusFlavor.MICRODESC);
      if(d != null) {
        d.setLastListed(consensus.getValidAfterTime().getTime());
      }
    }
   
    logger.fine("Loaded "+ routersByIdentity.size() +" routers from consensus document");
    currentConsensus = consensus;
   
    if(!fromCache) {
      storeCurrentConsensus();
    }
    consensusChangedManager.fireEvent(new Event() {});
  }

  private void storeCurrentConsensus() {
    if(currentConsensus != null) {
      if(currentConsensus.getFlavor() == ConsensusFlavor.MICRODESC) {
        store.writeDocument(CacheFile.CONSENSUS_MICRODESC, currentConsensus);
      } else {
        store.writeDocument(CacheFile.CONSENSUS, currentConsensus);
      }
    }
  }

  private Descriptor getDescriptorForRouterStatus(RouterStatus rs, boolean isMicrodescriptor) {
    if(isMicrodescriptor) {
      return microdescriptorCache.getDescriptor(rs.getMicrodescriptorDigest());
    } else {
      return basicDescriptorCache.getDescriptor(rs.getDescriptorDigest());
    }
  }
 
  private RouterImpl updateOrCreateRouter(RouterStatus status, Map<HexDigest, RouterImpl> knownRouters) {
    final RouterImpl router = knownRouters.get(status.getIdentity());
    if(router == null)
      return RouterImpl.createFromRouterStatus(this, status);
    router.updateStatus(status);
    return router;
  }

  private void clearAll() {
    routersByIdentity.clear();
    routersByNickname.clear();
    directoryCaches.clear();
  }

  private void classifyRouter(RouterImpl router) {
    if(isValidDirectoryCache(router)) {
      directoryCaches.add(router);
    } else {
      directoryCaches.remove(router);
    }
  }

  private boolean isValidDirectoryCache(RouterImpl router) {
    if(router.getDirectoryPort() == 0)
      return false;
    if(router.hasFlag("BadDirectory"))
      return false;
    return router.hasFlag("V2Dir");
  }

  private void addRouter(RouterImpl router) {
    routersByIdentity.put(router.getIdentityHash(), router);
    addRouterByNickname(router);
  }

  private void addRouterByNickname(RouterImpl router) {
    final String name = router.getNickname();
    if(name == null || name.equals("Unnamed"))
      return;
    if(routersByNickname.containsKey(router.getNickname())) {
      //logger.warn("Duplicate router nickname: "+ router.getNickname());
      return;
    }
    routersByNickname.put(name, router);
  }

  public synchronized void addRouterMicrodescriptors(List<RouterMicrodescriptor> microdescriptors) {
    microdescriptorCache.addDescriptors(microdescriptors);
    needRecalculateMinimumRouterInfo = true;
  }

  synchronized public List<Router> getRoutersWithDownloadableDescriptors() {
    waitUntilLoaded();
    final List<Router> routers = new ArrayList<Router>();
    for(RouterImpl router: routersByIdentity.values()) {
      if(router.isDescriptorDownloadable())
        routers.add(router);
    }

    for(int i = 0; i < routers.size(); i++) {
      final Router a = routers.get(i);
      final int swapIdx = random.nextInt(routers.size());
      final Router b = routers.get(swapIdx);
      routers.set(i, b);
      routers.set(swapIdx, a);
    }

    return routers;
  }

  public ConsensusDocument getCurrentConsensusDocument() {
    return currentConsensus;
  }

  public boolean hasPendingConsensus() {
    synchronized (TrustedAuthorities.getInstance()) {
      return consensusWaitingForCertificates != null
    }
  }

  public void registerConsensusChangedHandler(EventHandler handler) {
    consensusChangedManager.addListener(handler);
  }

  public void unregisterConsensusChangedHandler(EventHandler handler) {
    consensusChangedManager.removeListener(handler);
  }

  public Router getRouterByName(String name) {
    if(name.equals("Unnamed")) {
      return null;
    }
    if(name.length() == 41 && name.charAt(0) == '$') {
      try {
        final HexDigest identity = HexDigest.createFromString(name.substring(1));
        return getRouterByIdentity(identity);
      } catch (Exception e) {
        return null;
      }
    }
    waitUntilLoaded();
    return routersByNickname.get(name);
  }

  public Router getRouterByIdentity(HexDigest identity) {
    waitUntilLoaded();
    synchronized (routersByIdentity) {
      return routersByIdentity.get(identity);
    }
  }

  public List<Router> getRouterListByNames(List<String> names) {
    waitUntilLoaded();
    final List<Router> routers = new ArrayList<Router>();
    for(String n: names) {
      final Router r = getRouterByName(n);
      if(r == null)
        throw new TorException("Could not find router named: "+ n);
      routers.add(r);
    }
    return routers;
  }

  public List<Router> getAllRouters() {
    waitUntilLoaded();
    synchronized(routersByIdentity) {
      return new ArrayList<Router>(routersByIdentity.values());
    }
  }

  public GuardEntry createGuardEntryFor(Router router) {
    waitUntilLoaded();
    return stateFile.createGuardEntryFor(router);
  }

  public List<GuardEntry> getGuardEntries() {
    waitUntilLoaded();
    return stateFile.getGuardEntries();
  }

  public void removeGuardEntry(GuardEntry entry) {
    waitUntilLoaded();
    stateFile.removeGuardEntry(entry);
  }

  public void addGuardEntry(GuardEntry entry) {
    waitUntilLoaded();
    stateFile.addGuardEntry(entry);
  }

  public RouterMicrodescriptor getMicrodescriptorFromCache(HexDigest descriptorDigest) {
    return microdescriptorCache.getDescriptor(descriptorDigest);
  }


  public RouterDescriptor getBasicDescriptorFromCache(HexDigest descriptorDigest) {
    return basicDescriptorCache.getDescriptor(descriptorDigest);
  }
}
TOP

Related Classes of com.subgraph.orchid.directory.DirectoryImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.