package com.subgraph.orchid.circuits.guards;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.subgraph.orchid.BridgeRouter;
import com.subgraph.orchid.DirectoryDownloader;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.RouterDescriptor;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.config.TorConfigBridgeLine;
import com.subgraph.orchid.crypto.TorRandom;
import com.subgraph.orchid.directory.downloader.DirectoryRequestFailedException;
public class Bridges {
private static final Logger logger = Logger.getLogger(Bridges.class.getName());
private class DescriptorDownloader implements Runnable {
private final BridgeRouterImpl target;
DescriptorDownloader(BridgeRouterImpl target) {
this.target = target;
}
public void run() {
try {
downloadDescriptor();
} finally {
decrementOutstandingTasks();
}
}
private void downloadDescriptor() {
logger.fine("Downloading descriptor for bridge: "+ target);
try {
final RouterDescriptor descriptor = directoryDownloader.downloadBridgeDescriptor(target);
if(descriptor != null) {
logger.fine("Descriptor received for bridge "+ target +". Adding to list of usable bridges");
target.setDescriptor(descriptor);
synchronized(lock) {
bridgeRouters.add(target);
lock.notifyAll();
}
}
} catch (DirectoryRequestFailedException e) {
logger.warning("Failed to download descriptor for bridge: "+ e.getMessage());
}
}
private void decrementOutstandingTasks() {
if(outstandingDownloadTasks.decrementAndGet() == 0) {
logger.fine("Initial descriptor fetch complete");
synchronized(lock) {
bridgesInitialized = true;
lock.notifyAll();
}
}
}
}
private final TorConfig config;
private final DirectoryDownloader directoryDownloader;
private final Set<BridgeRouterImpl> bridgeRouters;
private final TorRandom random;
private final Object lock;
/** Initialization started */
private boolean bridgesInitializing;
/** Initialization completed */
private boolean bridgesInitialized;
private AtomicInteger outstandingDownloadTasks;
Bridges(TorConfig config, DirectoryDownloader directoryDownloader) {
this.config = config;
this.directoryDownloader = directoryDownloader;
this.bridgeRouters = new HashSet<BridgeRouterImpl>();
this.random = new TorRandom();
this.lock = new Object();
this.outstandingDownloadTasks = new AtomicInteger();
}
BridgeRouter chooseRandomBridge(Set<Router> excluded) throws InterruptedException {
synchronized(lock) {
if(!bridgesInitialized && !bridgesInitializing) {
initializeBridges();
}
while(!bridgesInitialized && !hasCandidates(excluded)) {
lock.wait();
}
final List<BridgeRouter> candidates = getCandidates(excluded);
if(candidates.isEmpty()) {
logger.warning("Bridges enabled but no usable bridges configured");
return null;
}
return candidates.get(random.nextInt(candidates.size()));
}
}
private boolean hasCandidates(Set<Router> excluded) {
return !(getCandidates(excluded).isEmpty());
}
private List<BridgeRouter> getCandidates(Set<Router> excluded) {
if(bridgeRouters.isEmpty()) {
return Collections.emptyList();
}
final List<BridgeRouter> candidates = new ArrayList<BridgeRouter>(bridgeRouters.size());
for(BridgeRouter br: bridgeRouters) {
if(!excluded.contains(br)) {
candidates.add(br);
}
}
return candidates;
}
private void initializeBridges() {
logger.fine("Initializing bridges...");
synchronized(lock) {
if(bridgesInitializing || bridgesInitialized) {
return;
}
if(directoryDownloader == null) {
throw new IllegalStateException("Cannot download bridge descriptors because DirectoryDownload instance not initialized");
}
bridgesInitializing = true;
startAllDownloadTasks();
}
}
private List<Runnable> createDownloadTasks() {
final List<Runnable> tasks = new ArrayList<Runnable>();
for(TorConfigBridgeLine line: config.getBridges()) {
tasks.add(new DescriptorDownloader(createBridgeFromLine(line)));
}
return tasks;
}
private void startAllDownloadTasks() {
final List<Runnable> tasks = createDownloadTasks();
outstandingDownloadTasks.set(tasks.size());
for(Runnable r: tasks) {
final Thread thread = new Thread(r);
thread.start();
}
}
private BridgeRouterImpl createBridgeFromLine(TorConfigBridgeLine line) {
final BridgeRouterImpl bridge = new BridgeRouterImpl(line.getAddress(), line.getPort());
if(line.getFingerprint() != null) {
bridge.setIdentity(line.getFingerprint());
}
return bridge;
}
}