package com.subgraph.orchid.directory.downloader; import java.io.IOException; import java.util.List; import java.util.Set; import java.util.concurrent.TimeoutException; import com.subgraph.orchid.CircuitManager; import com.subgraph.orchid.ConsensusDocument; import com.subgraph.orchid.ConsensusDocument.RequiredCertificate; import com.subgraph.orchid.DirectoryCircuit; import com.subgraph.orchid.KeyCertificate; import com.subgraph.orchid.Router; import com.subgraph.orchid.RouterDescriptor; import com.subgraph.orchid.RouterMicrodescriptor; import com.subgraph.orchid.Stream; import com.subgraph.orchid.StreamConnectFailedException; import com.subgraph.orchid.Tor; import com.subgraph.orchid.circuits.TorInitializationTracker; import com.subgraph.orchid.data.HexDigest; /** * Synchronously downloads directory documents. */ public class DirectoryDocumentRequestor { private final static int OPEN_DIRECTORY_STREAM_TIMEOUT = 10 * 1000; private final DirectoryCircuit circuit; private final TorInitializationTracker initializationTracker; public DirectoryDocumentRequestor(DirectoryCircuit circuit) { this(circuit, null); } public DirectoryDocumentRequestor(DirectoryCircuit circuit, TorInitializationTracker initializationTracker) { this.circuit = circuit; this.initializationTracker = initializationTracker; } public RouterDescriptor downloadBridgeDescriptor(Router bridge) throws DirectoryRequestFailedException { return fetchSingleDocument(new BridgeDescriptorFetcher()); } public ConsensusDocument downloadCurrentConsensus(boolean useMicrodescriptors) throws DirectoryRequestFailedException { return fetchSingleDocument(new ConsensusFetcher(useMicrodescriptors), CircuitManager.DIRECTORY_PURPOSE_CONSENSUS); } public List<KeyCertificate> downloadKeyCertificates(Set<RequiredCertificate> required) throws DirectoryRequestFailedException { return fetchDocuments(new CertificateFetcher(required), CircuitManager.DIRECTORY_PURPOSE_CERTIFICATES); } public List<RouterDescriptor> downloadRouterDescriptors(Set<HexDigest> fingerprints) throws DirectoryRequestFailedException { return fetchDocuments(new RouterDescriptorFetcher(fingerprints), CircuitManager.DIRECTORY_PURPOSE_DESCRIPTORS); } public List<RouterMicrodescriptor> downloadRouterMicrodescriptors(Set<HexDigest> fingerprints) throws DirectoryRequestFailedException { return fetchDocuments(new MicrodescriptorFetcher(fingerprints), CircuitManager.DIRECTORY_PURPOSE_DESCRIPTORS); } private <T> T fetchSingleDocument(DocumentFetcher<T> fetcher) throws DirectoryRequestFailedException { return fetchSingleDocument(fetcher, 0); } private <T> T fetchSingleDocument(DocumentFetcher<T> fetcher, int purpose) throws DirectoryRequestFailedException { final List<T> result = fetchDocuments(fetcher, purpose); if(result.size() == 1) { return result.get(0); } return null; } private <T> List<T> fetchDocuments(DocumentFetcher<T> fetcher, int purpose) throws DirectoryRequestFailedException { try { final HttpConnection http = createHttpConnection(purpose); try { return fetcher.requestDocuments(http); } finally { http.close(); } } catch (TimeoutException e) { throw new DirectoryRequestFailedException("Directory request timed out"); } catch (StreamConnectFailedException e) { throw new DirectoryRequestFailedException("Failed to open directory stream", e); } catch (IOException e) { throw new DirectoryRequestFailedException("I/O exception processing directory request", e); } catch (InterruptedException e) { throw new DirectoryRequestFailedException("Directory request interrupted"); } } private HttpConnection createHttpConnection(int purpose) throws InterruptedException, TimeoutException, StreamConnectFailedException { return new HttpConnection(openDirectoryStream(purpose)); } private Stream openDirectoryStream(int purpose) throws InterruptedException, TimeoutException, StreamConnectFailedException { final int requestEventCode = purposeToEventCode(purpose, false); final int loadingEventCode = purposeToEventCode(purpose, true); notifyInitialization(requestEventCode); final Stream stream = circuit.openDirectoryStream(OPEN_DIRECTORY_STREAM_TIMEOUT, true); notifyInitialization(loadingEventCode); return stream; } private int purposeToEventCode(int purpose, boolean getLoadingEvent) { switch(purpose) { case CircuitManager.DIRECTORY_PURPOSE_CONSENSUS: return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_STATUS : Tor.BOOTSTRAP_STATUS_REQUESTING_STATUS; case CircuitManager.DIRECTORY_PURPOSE_CERTIFICATES: return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_KEYS : Tor.BOOTSTRAP_STATUS_REQUESTING_KEYS; case CircuitManager.DIRECTORY_PURPOSE_DESCRIPTORS: return getLoadingEvent ? Tor.BOOTSTRAP_STATUS_LOADING_DESCRIPTORS : Tor.BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS; default: return 0; } } private void notifyInitialization(int code) { if(code > 0 && initializationTracker != null) { initializationTracker.notifyEvent(code); } } }