package com.subgraph.orchid.circuits.cells;
import java.nio.ByteBuffer;
import com.subgraph.orchid.Cell;
import com.subgraph.orchid.CircuitNode;
import com.subgraph.orchid.RelayCell;
import com.subgraph.orchid.TorException;
public class RelayCellImpl extends CellImpl implements RelayCell {
public static RelayCell createFromCell(CircuitNode node, Cell cell) {
if(cell.getCommand() != Cell.RELAY)
throw new TorException("Attempted to create RelayCell from Cell type: "+ cell.getCommand());
return new RelayCellImpl(node, cell.getCellBytes());
}
private final int streamId;
private final int relayCommand;
private final CircuitNode circuitNode;
private final boolean isOutgoing;
/*
* The payload of each unencrypted RELAY cell consists of:
* Relay command [1 byte]
* 'Recognized' [2 bytes]
* StreamID [2 bytes]
* Digest [4 bytes]
* Length [2 bytes]
* Data [CELL_LEN-14 bytes]
*/
public RelayCellImpl(CircuitNode node, int circuit, int stream, int relayCommand) {
this(node, circuit, stream, relayCommand, false);
}
public RelayCellImpl(CircuitNode node, int circuit, int stream, int relayCommand, boolean isRelayEarly) {
super(circuit, (isRelayEarly) ? (Cell.RELAY_EARLY) : (Cell.RELAY));
this.circuitNode = node;
this.relayCommand = relayCommand;
this.streamId = stream;
this.isOutgoing = true;
putByte(relayCommand); // Command
putShort(0); // 'Recognized'
putShort(stream); // Stream
putInt(0); // Digest
putShort(0); // Length
}
private RelayCellImpl(CircuitNode node, byte[] rawCell) {
super(rawCell);
this.circuitNode = node;
this.relayCommand = getByte();
getShort();
this.streamId = getShort();
this.isOutgoing = false;
getInt();
int payloadLength = getShort();
cellBuffer.mark(); // End of header
if(RelayCell.HEADER_SIZE + payloadLength > rawCell.length)
throw new TorException("Header length field exceeds total size of cell");
cellBuffer.limit(RelayCell.HEADER_SIZE + payloadLength);
}
public int getStreamId() {
return streamId;
}
public int getRelayCommand() {
return relayCommand;
}
public void setLength() {
putShortAt(LENGTH_OFFSET, (short) (cellBytesConsumed() - HEADER_SIZE));
}
public void setDigest(byte[] digest) {
for(int i = 0; i < 4; i++)
putByteAt(DIGEST_OFFSET + i, digest[i]);
}
public ByteBuffer getPayloadBuffer() {
final ByteBuffer dup = cellBuffer.duplicate();
dup.reset();
return dup.slice();
}
public CircuitNode getCircuitNode() {
return circuitNode;
}
public String toString() {
if(isOutgoing)
return "["+ commandToDescription(relayCommand) +" stream="+ streamId +" payload_len="+ (cellBytesConsumed() - HEADER_SIZE) +" dest="+ circuitNode +"]";
else
return "["+ commandToString() + " stream="+ streamId + " payload_len="+ cellBuffer.remaining() +" source="+ circuitNode + "]";
}
public String commandToString() {
if(relayCommand == RELAY_TRUNCATED) {
final int code = getByteAt(HEADER_SIZE);
return commandToDescription(relayCommand) + " ("+ CellImpl.errorToDescription(code) +")";
} else if(relayCommand == RELAY_END) {
final int code = getByteAt(HEADER_SIZE);
return commandToDescription(relayCommand) +" ("+ reasonToDescription(code) +")";
}
else
return commandToDescription(relayCommand);
}
public static String reasonToDescription(int reasonCode) {
switch(reasonCode) {
case REASON_MISC:
return "Unlisted reason";
case REASON_RESOLVEFAILED:
return "Couldn't look up hostname";
case REASON_CONNECTREFUSED:
return "Remote host refused connection";
case REASON_EXITPOLICY:
return "OR refuses to connect to host or port";
case REASON_DESTROY:
return "Circuit is being destroyed";
case REASON_DONE:
return "Anonymized TCP connection was closed";
case REASON_TIMEOUT:
return "Connection timed out, or OR timed out while connecting";
case REASON_HIBERNATING:
return "OR is temporarily hibernating";
case REASON_INTERNAL:
return "Internal error at the OR";
case REASON_RESOURCELIMIT:
return "OR has no resources to fulfill request";
case REASON_CONNRESET:
return "Connection was unexpectedly reset";
case REASON_TORPROTOCOL:
return "Tor protocol violation";
case REASON_NOTDIRECTORY:
return "Client sent RELAY_BEGIN_DIR to a non-directory server.";
default:
return "Reason code "+ reasonCode;
}
}
public static String commandToDescription(int command) {
switch(command) {
case RELAY_BEGIN:
return "RELAY_BEGIN";
case RELAY_DATA:
return "RELAY_DATA";
case RELAY_END:
return "RELAY_END";
case RELAY_CONNECTED:
return "RELAY_CONNECTED";
case RELAY_SENDME:
return "RELAY_SENDME";
case RELAY_EXTEND:
return "RELAY_EXTEND";
case RELAY_EXTENDED:
return "RELAY_EXTENDED";
case RELAY_TRUNCATE:
return "RELAY_TRUNCATE";
case RELAY_TRUNCATED:
return "RELAY_TRUNCATED";
case RELAY_DROP:
return "RELAY_DROP";
case RELAY_RESOLVE:
return "RELAY_RESOLVE";
case RELAY_RESOLVED:
return "RELAY_RESOLVED";
case RELAY_BEGIN_DIR:
return "RELAY_BEGIN_DIR";
case RELAY_EXTEND2:
return "RELAY_EXTEND2";
case RELAY_EXTENDED2:
return "RELAY_EXTENDED2";
default:
return "Relay command = "+ command;
}
}
}