package com.subgraph.orchid.circuits;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import com.subgraph.orchid.Circuit;
import com.subgraph.orchid.CircuitNode;
import com.subgraph.orchid.RelayCell;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.StreamConnectFailedException;
import com.subgraph.orchid.TorException;
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
import com.subgraph.orchid.dashboard.DashboardRenderable;
import com.subgraph.orchid.dashboard.DashboardRenderer;
public class StreamImpl implements Stream, DashboardRenderable {
private final static Logger logger = Logger.getLogger(StreamImpl.class.getName());
private final static int STREAMWINDOW_START = 500;
private final static int STREAMWINDOW_INCREMENT = 50;
private final static int STREAMWINDOW_MAX_UNFLUSHED = 10;
private final CircuitImpl circuit;
private final int streamId;
private final boolean autoclose;
private final CircuitNode targetNode;
private final TorInputStream inputStream;
private final TorOutputStream outputStream;
private boolean isClosed;
private boolean relayEndReceived;
private int relayEndReason;
private boolean relayConnectedReceived;
private final Object waitConnectLock = new Object();
private final Object windowLock = new Object();
private int packageWindow;
private int deliverWindow;
private String streamTarget = "";
StreamImpl(CircuitImpl circuit, CircuitNode targetNode, int streamId, boolean autoclose) {
this.circuit = circuit;
this.targetNode = targetNode;
this.streamId = streamId;
this.autoclose = autoclose;
this.inputStream = new TorInputStream(this);
this.outputStream = new TorOutputStream(this);
packageWindow = STREAMWINDOW_START;
deliverWindow = STREAMWINDOW_START;
}
void addInputCell(RelayCell cell) {
if(isClosed)
return;
if(cell.getRelayCommand() == RelayCell.RELAY_END) {
synchronized(waitConnectLock) {
relayEndReason = cell.getByte();
relayEndReceived = true;
inputStream.addEndCell(cell);
waitConnectLock.notifyAll();
}
} else if(cell.getRelayCommand() == RelayCell.RELAY_CONNECTED) {
synchronized(waitConnectLock) {
relayConnectedReceived = true;
waitConnectLock.notifyAll();
}
} else if(cell.getRelayCommand() == RelayCell.RELAY_SENDME) {
synchronized(windowLock) {
packageWindow += STREAMWINDOW_INCREMENT;
windowLock.notifyAll();
}
}
else {
inputStream.addInputCell(cell);
synchronized(windowLock) {
deliverWindow--;
if(deliverWindow < 0)
throw new TorException("Stream has negative delivery window");
}
considerSendingSendme();
}
}
private void considerSendingSendme() {
synchronized(windowLock) {
if(deliverWindow > (STREAMWINDOW_START - STREAMWINDOW_INCREMENT))
return;
if(inputStream.unflushedCellCount() >= STREAMWINDOW_MAX_UNFLUSHED)
return;
final RelayCell sendme = circuit.createRelayCell(RelayCell.RELAY_SENDME, streamId, targetNode);
circuit.sendRelayCell(sendme);
deliverWindow += STREAMWINDOW_INCREMENT;
}
}
public int getStreamId() {
return streamId;
}
public Circuit getCircuit() {
return circuit;
}
public CircuitNode getTargetNode() {
return targetNode;
}
public void close() {
if(isClosed)
return;
logger.fine("Closing stream "+ this);
isClosed = true;
inputStream.close();
outputStream.close();
circuit.removeStream(this);
if(autoclose) {
circuit.markForClose();
}
if(!relayEndReceived) {
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_END);
cell.putByte(RelayCell.REASON_DONE);
circuit.sendRelayCellToFinalNode(cell);
}
}
public void openDirectory(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
streamTarget = "[Directory]";
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN_DIR);
circuit.sendRelayCellToFinalNode(cell);
waitForRelayConnected(timeout);
}
void openExit(String target, int port, long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
streamTarget = target + ":"+ port;
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN);
cell.putString(target + ":"+ port);
circuit.sendRelayCellToFinalNode(cell);
waitForRelayConnected(timeout);
}
private void waitForRelayConnected(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
final long start = System.currentTimeMillis();
long elapsed = 0;
synchronized(waitConnectLock) {
while(!relayConnectedReceived) {
if(relayEndReceived) {
throw new StreamConnectFailedException(relayEndReason);
}
if(elapsed >= timeout) {
throw new TimeoutException();
}
waitConnectLock.wait(timeout - elapsed);
elapsed = System.currentTimeMillis() - start;
}
}
}
public InputStream getInputStream() {
return inputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
public void waitForSendWindowAndDecrement() {
waitForSendWindow(true);
}
public void waitForSendWindow() {
waitForSendWindow(false);
}
public void waitForSendWindow(boolean decrement) {
synchronized(windowLock) {
while(packageWindow == 0) {
try {
windowLock.wait();
} catch (InterruptedException e) {
throw new TorException("Thread interrupted while waiting for stream package window");
}
}
if(decrement)
packageWindow--;
}
targetNode.waitForSendWindow();
}
public String toString() {
return "[Stream stream_id="+ streamId + " circuit="+ circuit +" target="+ streamTarget +"]";
}
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
writer.print(" ");
writer.print("[Stream stream_id="+ streamId + " cid="+ circuit.getCircuitId());
if(relayConnectedReceived) {
writer.print(" sent="+outputStream.getBytesSent() + " recv="+ inputStream.getBytesReceived());
} else {
writer.print(" (waiting connect)");
}
writer.print(" target="+ streamTarget);
writer.println("]");
}
}