package com.subgraph.orchid.circuits.hs;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import com.subgraph.orchid.Directory;
import com.subgraph.orchid.HiddenServiceCircuit;
import com.subgraph.orchid.OpenFailedException;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.StreamConnectFailedException;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorException;
import com.subgraph.orchid.circuits.CircuitManagerImpl;
public class HiddenServiceManager {
private final static int RENDEZVOUS_RETRY_COUNT = 5;
private final static int HS_STREAM_TIMEOUT = 20000;
private final static Logger logger = Logger.getLogger(HiddenServiceManager.class.getName());
private final Map<String, HiddenService> hiddenServices;
private final TorConfig config;
private final Directory directory;
private final HSDirectories hsDirectories;
private final CircuitManagerImpl circuitManager;
public HiddenServiceManager(TorConfig config, Directory directory, CircuitManagerImpl circuitManager) {
this.config = config;
this.directory = directory;
this.hiddenServices = new HashMap<String, HiddenService>();
this.hsDirectories = new HSDirectories(directory);
this.circuitManager = circuitManager;
}
public Stream getStreamTo(String onion, int port) throws OpenFailedException, InterruptedException, TimeoutException {
final HiddenService hs = getHiddenServiceForOnion(onion);
final HiddenServiceCircuit circuit = getCircuitTo(hs);
try {
return circuit.openStream(port, HS_STREAM_TIMEOUT);
} catch (StreamConnectFailedException e) {
throw new OpenFailedException("Failed to open stream to hidden service "+ hs.getOnionAddressForLogging() + " reason "+ e.getReason());
}
}
private synchronized HiddenServiceCircuit getCircuitTo(HiddenService hs) throws OpenFailedException {
if(hs.getCircuit() == null) {
final HiddenServiceCircuit c = openCircuitTo(hs);
if(c == null) {
throw new OpenFailedException("Failed to open circuit to "+ hs.getOnionAddressForLogging());
}
hs.setCircuit(c);
}
return hs.getCircuit();
}
private HiddenServiceCircuit openCircuitTo(HiddenService hs) throws OpenFailedException {
HSDescriptor descriptor = getDescriptorFor(hs);
for(int i = 0; i < RENDEZVOUS_RETRY_COUNT; i++) {
final HiddenServiceCircuit c = openRendezvousCircuit(hs, descriptor);
if(c != null) {
return c;
}
}
throw new OpenFailedException("Failed to open circuit to "+ hs.getOnionAddressForLogging());
}
HSDescriptor getDescriptorFor(HiddenService hs) throws OpenFailedException {
if(hs.hasCurrentDescriptor()) {
return hs.getDescriptor();
}
final HSDescriptor descriptor = downloadDescriptorFor(hs);
if(descriptor == null) {
final String msg = "Failed to download HS descriptor for "+ hs.getOnionAddressForLogging();
logger.info(msg);
throw new OpenFailedException(msg);
}
hs.setDescriptor(descriptor);
return descriptor;
}
private HSDescriptor downloadDescriptorFor(HiddenService hs) {
logger.fine("Downloading HS descriptor for "+ hs.getOnionAddressForLogging());
final List<HSDescriptorDirectory> dirs = hsDirectories.getDirectoriesForHiddenService(hs);
final HSDescriptorDownloader downloader = new HSDescriptorDownloader(hs, circuitManager, dirs);
return downloader.downloadDescriptor();
}
HiddenService getHiddenServiceForOnion(String onion) throws OpenFailedException {
final String key = onion.endsWith(".onion") ? onion.substring(0, onion.length() - 6) : onion;
synchronized(hiddenServices) {
if(!hiddenServices.containsKey(key)) {
hiddenServices.put(key, createHiddenServiceFor(key));
}
return hiddenServices.get(key);
}
}
private HiddenService createHiddenServiceFor(String key) throws OpenFailedException {
try {
byte[] decoded = HiddenService.decodeOnion(key);
return new HiddenService(config, decoded);
} catch (TorException e) {
final String target = config.getSafeLogging() ? "[scrubbed]" : (key + ".onion");
throw new OpenFailedException("Failed to decode onion address "+ target + " : "+ e.getMessage());
}
}
private HiddenServiceCircuit openRendezvousCircuit(HiddenService hs, HSDescriptor descriptor) {
final RendezvousCircuitBuilder builder = new RendezvousCircuitBuilder(directory, circuitManager, hs, descriptor);
try {
return builder.call();
} catch (Exception e) {
return null;
}
}
}