package org.torproject.jtor.circuits.impl; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import org.torproject.jtor.TorException; import org.torproject.jtor.circuits.Circuit; import org.torproject.jtor.circuits.CircuitNode; import org.torproject.jtor.circuits.OpenStreamResponse; import org.torproject.jtor.circuits.Stream; import org.torproject.jtor.circuits.cells.RelayCell; public class StreamImpl implements Stream { private final static int STREAM_CONNECT_TIMEOUT = 20 * 1000; 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 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; StreamImpl(CircuitImpl circuit, CircuitNode targetNode, int streamId) { this.circuit = circuit; this.targetNode = targetNode; this.streamId = streamId; 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; } CircuitNode getTargetNode() { return targetNode; } public void close() { if(isClosed) return; isClosed = true; inputStream.close(); outputStream.close(); circuit.removeStream(this); if(!relayEndReceived) { final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_END); cell.putByte(RelayCell.REASON_DONE); circuit.sendRelayCellToFinalNode(cell); } } void openDirectory() { final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN_DIR); circuit.sendRelayCellToFinalNode(cell); waitForRelayConnected(); } OpenStreamResponse openExit(String target, int port) { final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN); cell.putString(target + ":"+ port); circuit.sendRelayCellToFinalNode(cell); return waitForRelayConnected(); } private OpenStreamResponse waitForRelayConnected() { final Date startWait = new Date(); synchronized(waitConnectLock) { while(!relayConnectedReceived) { if(relayEndReceived) return OpenStreamResponseImpl.createStreamError(relayEndReason); if(hasStreamConnectTimedOut(startWait)) return OpenStreamResponseImpl.createStreamTimeout(); try { waitConnectLock.wait(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return OpenStreamResponseImpl.createStreamTimeout(); } } } return OpenStreamResponseImpl.createStreamOpened(this); } private static boolean hasStreamConnectTimedOut(Date startTime) { final Date now = new Date(); final long diff = now.getTime() - startTime.getTime(); return diff >= STREAM_CONNECT_TIMEOUT; } 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 +" ]"; } }