package com.subgraph.orchid.circuits.cells;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import com.subgraph.orchid.Cell;
public class CellImpl implements Cell {
public static CellImpl createCell(int circuitId, int command) {
return new CellImpl(circuitId, command);
}
public static CellImpl createVarCell(int circuitId, int command, int payloadLength) {
return new CellImpl(circuitId, command, payloadLength);
}
public static CellImpl readFromInputStream(InputStream input) throws IOException {
final ByteBuffer header = readHeaderFromInputStream(input);
final int circuitId = header.getShort() & 0xFFFF;
final int command = header.get() & 0xFF;
if(command == VERSIONS || command > 127) {
return readVarCell(circuitId, command, input);
}
final CellImpl cell = new CellImpl(circuitId, command);
readAll(input, cell.getCellBytes(), CELL_HEADER_LEN, CELL_PAYLOAD_LEN);
return cell;
}
private static ByteBuffer readHeaderFromInputStream(InputStream input) throws IOException {
final byte[] cellHeader = new byte[CELL_HEADER_LEN];
readAll(input, cellHeader);
return ByteBuffer.wrap(cellHeader);
}
private static CellImpl readVarCell(int circuitId, int command, InputStream input) throws IOException {
final byte[] lengthField = new byte[2];
readAll(input, lengthField);
final int length = ((lengthField[0] & 0xFF) << 8) | (lengthField[1] & 0xFF);
CellImpl cell = new CellImpl(circuitId, command, length);
readAll(input, cell.getCellBytes(), CELL_VAR_HEADER_LEN, length);
return cell;
}
private static void readAll(InputStream input, byte[] buffer) throws IOException {
readAll(input, buffer, 0, buffer.length);
}
private static void readAll(InputStream input, byte[] buffer, int offset, int length) throws IOException {
int bytesRead = 0;
while(bytesRead < length) {
final int n = input.read(buffer, offset + bytesRead, length - bytesRead);
if(n == -1)
throw new EOFException();
bytesRead += n;
}
}
private final int circuitId;
private final int command;
protected final ByteBuffer cellBuffer;
/* Variable length cell constructor (ie: VERSIONS cells only) */
private CellImpl(int circuitId, int command, int payloadLength) {
this.circuitId = circuitId;
this.command = command;
this.cellBuffer = ByteBuffer.wrap(new byte[CELL_VAR_HEADER_LEN + payloadLength]);
cellBuffer.putShort((short)circuitId);
cellBuffer.put((byte)command);
cellBuffer.putShort((short) payloadLength);
cellBuffer.mark();
}
/* Fixed length cell constructor */
protected CellImpl(int circuitId, int command) {
this.circuitId = circuitId;
this.command = command;
this.cellBuffer = ByteBuffer.wrap(new byte[CELL_LEN]);
cellBuffer.putShort((short) circuitId);
cellBuffer.put((byte) command);
cellBuffer.mark();
}
protected CellImpl(byte[] rawCell) {
this.cellBuffer = ByteBuffer.wrap(rawCell);
this.circuitId = cellBuffer.getShort() & 0xFFFF;
this.command = cellBuffer.get() & 0xFF;
cellBuffer.mark();
}
public int getCircuitId() {
return circuitId;
}
public int getCommand() {
return command;
}
public void resetToPayload() {
cellBuffer.reset();
}
public int getByte() {
return cellBuffer.get() & 0xFF;
}
public int getByteAt(int index) {
return cellBuffer.get(index) & 0xFF;
}
public int getShort() {
return cellBuffer.getShort() & 0xFFFF;
}
public int getInt() {
return cellBuffer.getInt();
}
public int getShortAt(int index) {
return cellBuffer.getShort(index) & 0xFFFF;
}
public void getByteArray(byte[] buffer) {
cellBuffer.get(buffer);
}
public int cellBytesConsumed() {
return cellBuffer.position();
}
public int cellBytesRemaining() {
return cellBuffer.remaining();
}
public void putByte(int value) {
cellBuffer.put((byte) value);
}
public void putByteAt(int index, int value) {
cellBuffer.put(index, (byte) value);
}
public void putShort(int value) {
cellBuffer.putShort((short) value);
}
public void putShortAt(int index, int value) {
cellBuffer.putShort(index, (short) value);
}
public void putInt(int value) {
cellBuffer.putInt(value);
}
public void putString(String string) {
final byte[] bytes = new byte[string.length() + 1];
for(int i = 0; i < string.length(); i++)
bytes[i] = (byte) string.charAt(i);
putByteArray(bytes);
}
public void putByteArray(byte[] data) {
cellBuffer.put(data);
}
public void putByteArray(byte[] data, int offset, int length) {
cellBuffer.put(data, offset, length);
}
public byte[] getCellBytes() {
return cellBuffer.array();
}
public String toString() {
return "Cell: circuit_id="+ circuitId +" command="+ command +" payload_len="+ cellBuffer.position();
}
public static String errorToDescription(int errorCode) {
switch(errorCode) {
case ERROR_NONE:
return "No error reason given";
case ERROR_PROTOCOL:
return "Tor protocol violation";
case ERROR_INTERNAL:
return "Internal error";
case ERROR_REQUESTED:
return "Response to a TRUNCATE command sent from client";
case ERROR_HIBERNATING:
return "Not currently operating; trying to save bandwidth.";
case ERROR_RESOURCELIMIT:
return "Out of memory, sockets, or circuit IDs.";
case ERROR_CONNECTFAILED:
return "Unable to reach server.";
case ERROR_OR_IDENTITY:
return "Connected to server, but its OR identity was not as expected.";
case ERROR_OR_CONN_CLOSED:
return "The OR connection that was carrying this circuit died.";
case ERROR_FINISHED:
return "The circuit has expired for being dirty or old.";
case ERROR_TIMEOUT:
return "Circuit construction took too long.";
case ERROR_DESTROYED:
return "The circuit was destroyed without client TRUNCATE";
case ERROR_NOSUCHSERVICE:
return "Request for unknown hidden service";
default:
return "Error code "+ errorCode;
}
}
}