package com.subgraph.orchid.circuits;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import com.subgraph.orchid.Cell;
import com.subgraph.orchid.Circuit;
import com.subgraph.orchid.CircuitNode;
import com.subgraph.orchid.Connection;
import com.subgraph.orchid.DirectoryCircuit;
import com.subgraph.orchid.ExitCircuit;
import com.subgraph.orchid.InternalCircuit;
import com.subgraph.orchid.RelayCell;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.StreamConnectFailedException;
import com.subgraph.orchid.TorException;
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
import com.subgraph.orchid.circuits.path.PathSelectionFailedException;
import com.subgraph.orchid.dashboard.DashboardRenderable;
import com.subgraph.orchid.dashboard.DashboardRenderer;
/**
* This class represents an established circuit through the Tor network.
*
*/
public abstract class CircuitImpl implements Circuit, DashboardRenderable {
protected final static Logger logger = Logger.getLogger(CircuitImpl.class.getName());
static ExitCircuit createExitCircuit(CircuitManagerImpl circuitManager, Router exitRouter) {
return new ExitCircuitImpl(circuitManager, exitRouter);
}
static ExitCircuit createExitCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
return new ExitCircuitImpl(circuitManager, prechosenPath);
}
static DirectoryCircuit createDirectoryCircuit(CircuitManagerImpl circuitManager) {
return new DirectoryCircuitImpl(circuitManager, null);
}
static DirectoryCircuit createDirectoryCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
return new DirectoryCircuitImpl(circuitManager, prechosenPath);
}
static InternalCircuit createInternalCircuitTo(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
return new InternalCircuitImpl(circuitManager, prechosenPath);
}
private final CircuitManagerImpl circuitManager;
protected final List<Router> prechosenPath;
private final List<CircuitNode> nodeList;
private final CircuitStatus status;
private CircuitIO io;
protected CircuitImpl(CircuitManagerImpl circuitManager) {
this(circuitManager, null);
}
protected CircuitImpl(CircuitManagerImpl circuitManager, List<Router> prechosenPath) {
nodeList = new ArrayList<CircuitNode>();
this.circuitManager = circuitManager;
this.prechosenPath = prechosenPath;
status = new CircuitStatus();
}
List<Router> choosePath(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException {
if(prechosenPath != null) {
return new ArrayList<Router>(prechosenPath);
} else {
return choosePathForCircuit(pathChooser);
}
}
protected abstract List<Router> choosePathForCircuit(CircuitPathChooser pathChooser) throws InterruptedException, PathSelectionFailedException;
void bindToConnection(Connection connection) {
if(io != null) {
throw new IllegalStateException("Circuit already bound to a connection");
}
final int id = connection.bindCircuit(this);
io = new CircuitIO(this, connection, id);
}
public void markForClose() {
if(io != null) {
io.markForClose();
}
}
public boolean isMarkedForClose() {
if(io == null) {
return false;
} else {
return io.isMarkedForClose();
}
}
CircuitStatus getStatus() {
return status;
}
public boolean isConnected() {
return status.isConnected();
}
public boolean isPending() {
return status.isBuilding();
}
public boolean isClean() {
return !status.isDirty();
}
public int getSecondsDirty() {
return (int) (status.getMillisecondsDirty() / 1000);
}
void notifyCircuitBuildStart() {
if(!status.isUnconnected()) {
throw new IllegalStateException("Can only connect UNCONNECTED circuits");
}
status.updateCreatedTimestamp();
status.setStateBuilding();
circuitManager.addActiveCircuit(this);
}
void notifyCircuitBuildFailed() {
status.setStateFailed();
circuitManager.removeActiveCircuit(this);
}
void notifyCircuitBuildCompleted() {
status.setStateOpen();
status.updateCreatedTimestamp();
}
public Connection getConnection() {
if(!isConnected())
throw new TorException("Circuit is not connected.");
return io.getConnection();
}
public int getCircuitId() {
if(io == null) {
return 0;
} else {
return io.getCircuitId();
}
}
public void sendRelayCell(RelayCell cell) {
io.sendRelayCellTo(cell, cell.getCircuitNode());
}
public void sendRelayCellToFinalNode(RelayCell cell) {
io.sendRelayCellTo(cell, getFinalCircuitNode());
}
public void appendNode(CircuitNode node) {
nodeList.add(node);
}
List<CircuitNode> getNodeList() {
return nodeList;
}
int getCircuitLength() {
return nodeList.size();
}
public CircuitNode getFinalCircuitNode() {
if(nodeList.isEmpty())
throw new TorException("getFinalCircuitNode() called on empty circuit");
return nodeList.get( getCircuitLength() - 1);
}
public RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode) {
return io.createRelayCell(relayCommand, streamId, targetNode);
}
public RelayCell receiveRelayCell() {
return io.dequeueRelayResponseCell();
}
void sendCell(Cell cell) {
io.sendCell(cell);
}
Cell receiveControlCellResponse() {
return io.receiveControlCellResponse();
}
/*
* This is called by the cell reading thread in ConnectionImpl to deliver control cells
* associated with this circuit (CREATED or CREATED_FAST).
*/
public void deliverControlCell(Cell cell) {
io.deliverControlCell(cell);
}
/* This is called by the cell reading thread in ConnectionImpl to deliver RELAY cells. */
public void deliverRelayCell(Cell cell) {
io.deliverRelayCell(cell);
}
protected StreamImpl createNewStream(boolean autoclose) {
return io.createNewStream(autoclose);
}
protected StreamImpl createNewStream() {
return createNewStream(false);
}
void setStateDestroyed() {
status.setStateDestroyed();
circuitManager.removeActiveCircuit(this);
}
public void destroyCircuit() {
io.destroyCircuit();
circuitManager.removeActiveCircuit(this);
}
public void removeStream(StreamImpl stream) {
io.removeStream(stream);
}
protected Stream processStreamOpenException(Exception e) throws InterruptedException, TimeoutException, StreamConnectFailedException {
if(e instanceof InterruptedException) {
throw (InterruptedException) e;
} else if(e instanceof TimeoutException) {
throw(TimeoutException) e;
} else if(e instanceof StreamConnectFailedException) {
throw(StreamConnectFailedException) e;
} else {
throw new IllegalStateException();
}
}
protected abstract String getCircuitTypeLabel();
public String toString() {
return " Circuit ("+ getCircuitTypeLabel() + ") id="+ getCircuitId() +" state=" + status.getStateAsString() +" "+ pathToString();
}
protected String pathToString() {
final StringBuilder sb = new StringBuilder();
sb.append("[");
for(CircuitNode node: nodeList) {
if(sb.length() > 1)
sb.append(",");
sb.append(node.toString());
}
sb.append("]");
return sb.toString();
}
public List<Stream> getActiveStreams() {
if(io == null) {
return Collections.emptyList();
} else {
return io.getActiveStreams();
}
}
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
if(io != null) {
writer.println(toString());
renderer.renderComponent(writer, flags, io);
}
}
}