package com.subgraph.orchid.circuits; import java.util.concurrent.TimeoutException; import com.subgraph.orchid.OpenFailedException; import com.subgraph.orchid.Stream; import com.subgraph.orchid.StreamConnectFailedException; import com.subgraph.orchid.data.IPv4Address; import com.subgraph.orchid.data.exitpolicy.ExitTarget; import com.subgraph.orchid.misc.GuardedBy; public class StreamExitRequest implements ExitTarget { private enum CompletionStatus {NOT_COMPLETED, SUCCESS, TIMEOUT, STREAM_OPEN_FAILURE, EXIT_FAILURE, INTERRUPTED}; private final boolean isAddress; private final IPv4Address address; private final String hostname; private final int port; private final Object requestCompletionLock; @GuardedBy("requestCompletionLock") private CompletionStatus completionStatus; @GuardedBy("requestCompletionLock") private Stream stream; @GuardedBy("requestCompletionLock") private int streamOpenFailReason; @GuardedBy("this") private boolean isReserved; @GuardedBy("this") private int retryCount; @GuardedBy("this") private long specificTimeout; StreamExitRequest(Object requestCompletionLock, IPv4Address address, int port) { this(requestCompletionLock, true, "", address, port); } StreamExitRequest(Object requestCompletionLock, String hostname, int port) { this(requestCompletionLock, false, hostname, null, port); } private StreamExitRequest(Object requestCompletionLock, boolean isAddress, String hostname, IPv4Address address, int port) { this.requestCompletionLock = requestCompletionLock; this.isAddress = isAddress; this.hostname = hostname; this.address = address; this.port = port; this.completionStatus = CompletionStatus.NOT_COMPLETED; } public boolean isAddressTarget() { return isAddress; } public IPv4Address getAddress() { return address; } public String getHostname() { return hostname; } public int getPort() { return port; } public synchronized void setStreamTimeout(long timeout) { specificTimeout = timeout; } public synchronized long getStreamTimeout() { if(specificTimeout > 0) { return specificTimeout; } else if(retryCount < 2) { return 10 * 1000; } else { return 15 * 1000; } } void setCompletedTimeout() { synchronized (requestCompletionLock) { newStatus(CompletionStatus.TIMEOUT); } } void setExitFailed() { synchronized (requestCompletionLock) { newStatus(CompletionStatus.EXIT_FAILURE); } } void setStreamOpenFailure(int reason) { synchronized (requestCompletionLock) { streamOpenFailReason = reason; newStatus(CompletionStatus.STREAM_OPEN_FAILURE); } } void setCompletedSuccessfully(Stream stream) { synchronized (requestCompletionLock) { this.stream = stream; newStatus(CompletionStatus.SUCCESS); } } void setInterrupted() { synchronized (requestCompletionLock) { newStatus(CompletionStatus.INTERRUPTED); } } private void newStatus(CompletionStatus newStatus) { if(completionStatus != CompletionStatus.NOT_COMPLETED) { throw new IllegalStateException("Attempt to set completion state to " + newStatus +" while status is "+ completionStatus); } completionStatus = newStatus; requestCompletionLock.notifyAll(); } Stream getStream() throws OpenFailedException, TimeoutException, StreamConnectFailedException, InterruptedException { synchronized(requestCompletionLock) { switch(completionStatus) { case NOT_COMPLETED: throw new IllegalStateException("Request not completed"); case EXIT_FAILURE: throw new OpenFailedException("Failure at exit node"); case TIMEOUT: throw new TimeoutException(); case STREAM_OPEN_FAILURE: throw new StreamConnectFailedException(streamOpenFailReason); case INTERRUPTED: throw new InterruptedException(); case SUCCESS: return stream; default: throw new IllegalStateException("Unknown completion status"); } } } synchronized void resetForRetry() { synchronized (requestCompletionLock) { streamOpenFailReason = 0; completionStatus = CompletionStatus.NOT_COMPLETED; } retryCount += 1; isReserved = false; } boolean isCompleted() { synchronized (requestCompletionLock) { return completionStatus != CompletionStatus.NOT_COMPLETED; } } synchronized boolean reserveRequest() { if(isReserved) return false; isReserved = true; return true; } synchronized boolean isReserved() { return isReserved; } public String toString() { if(isAddress) return address + ":"+ port; else return hostname + ":"+ port; } }