com.subgraph.orchid.circuits.TorInputStream Maven / Gradle / Ivy
The newest version!
package com.subgraph.orchid.circuits;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Queue;
import com.subgraph.orchid.RelayCell;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
import com.subgraph.orchid.misc.GuardedBy;
import com.subgraph.orchid.misc.ThreadSafe;
@ThreadSafe
public class TorInputStream extends InputStream {
private final static RelayCell CLOSE_SENTINEL = new RelayCellImpl(null, 0, 0, 0);
private final static ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private final Stream stream;
private final Object lock = new Object();
/** Queue of RelayCells that have been received on this stream */
@GuardedBy("lock") private final Queue incomingCells;
/** Number of unread data bytes in current buffer and in RELAY_DATA cells on queue */
@GuardedBy("lock") private int availableBytes;
/** Total number of data bytes received in RELAY_DATA cells on this stream */
@GuardedBy("lock") private long bytesReceived;
/** Bytes of data from the RELAY_DATA cell currently being consumed */
@GuardedBy("lock") private ByteBuffer currentBuffer;
/** Set when a RELAY_END cell is received */
@GuardedBy("lock") private boolean isEOF;
/** Set when close() is called on this stream */
@GuardedBy("lock") private boolean isClosed;
TorInputStream(Stream stream) {
this.stream = stream;
this.incomingCells = new LinkedList();
this.currentBuffer = EMPTY_BUFFER;
}
long getBytesReceived() {
synchronized (lock) {
return bytesReceived;
}
}
@Override
public int read() throws IOException {
synchronized (lock) {
if(isClosed) {
throw new IOException("Stream closed");
}
refillBufferIfNeeded();
if(isEOF) {
return -1;
}
availableBytes -= 1;
return currentBuffer.get() & 0xFF;
}
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public synchronized int read(byte[] b, int off, int len) throws IOException {
synchronized (lock) {
if(isClosed) {
throw new IOException("Stream closed");
}
checkReadArguments(b, off, len);
if(len == 0) {
return 0;
}
refillBufferIfNeeded();
if(isEOF) {
return -1;
}
int bytesRead = 0;
int bytesRemaining = len;
while(bytesRemaining > 0 && !isEOF) {
refillBufferIfNeeded();
bytesRead += readFromCurrentBuffer(b, off + bytesRead, len - bytesRead);
bytesRemaining = len - bytesRead;
if(availableBytes == 0) {
return bytesRead;
}
}
return bytesRead;
}
}
@GuardedBy("lock")
private int readFromCurrentBuffer(byte[] b, int off, int len) {
final int readLength = (currentBuffer.remaining() >= len) ? (len) : (currentBuffer.remaining());
currentBuffer.get(b, off, readLength);
availableBytes -= readLength;
return readLength;
}
private void checkReadArguments(byte[] b, int off, int len) {
if(b == null) {
throw new NullPointerException();
}
if( (off < 0) || (off >= b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
}
public int available() {
synchronized(lock) {
return availableBytes;
}
}
public void close() {
synchronized (lock) {
if(isClosed) {
return;
}
isClosed = true;
incomingCells.add(CLOSE_SENTINEL);
lock.notifyAll();
}
stream.close();
}
void addEndCell(RelayCell cell) {
synchronized (lock) {
if(isClosed) {
return;
}
incomingCells.add(cell);
lock.notifyAll();
}
}
void addInputCell(RelayCell cell) {
synchronized (lock) {
if(isClosed) {
return;
}
incomingCells.add(cell);
bytesReceived += cell.cellBytesRemaining();
availableBytes += cell.cellBytesRemaining();
lock.notifyAll();
}
}
@GuardedBy("lock")
// When this method (or fillBuffer()) returns either isEOF is set or currentBuffer has at least one byte to read
private void refillBufferIfNeeded() throws IOException {
if(!isEOF) {
if(currentBuffer.hasRemaining()) {
return;
}
fillBuffer();
}
}
@GuardedBy("lock")
private void fillBuffer() throws IOException {
while(true) {
processIncomingCell(getNextCell());
if(isEOF || currentBuffer.hasRemaining()) {
return;
}
}
}
@GuardedBy("lock")
private void processIncomingCell(RelayCell nextCell) throws IOException {
if(isClosed || nextCell == CLOSE_SENTINEL) {
throw new IOException("Input stream closed");
}
switch(nextCell.getRelayCommand()) {
case RelayCell.RELAY_DATA:
currentBuffer = nextCell.getPayloadBuffer();
break;
case RelayCell.RELAY_END:
currentBuffer = EMPTY_BUFFER;
isEOF = true;
break;
default:
throw new IOException("Unexpected RelayCell command type in TorInputStream queue: "+ nextCell.getRelayCommand());
}
}
@GuardedBy("lock")
private RelayCell getNextCell() throws IOException {
try {
while(incomingCells.isEmpty()) {
lock.wait();
}
return incomingCells.remove();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Read interrupted");
}
}
int unflushedCellCount() {
synchronized (lock) {
return incomingCells.size();
}
}
public String toString() {
return "TorInputStream stream="+ stream.getStreamId() +" node="+ stream.getTargetNode();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy