/* Tor Research Framework - easy to use tor client library/framework Copyright (C) 2014 Dr Gareth Owen <drgowen@gmail.com> www.ghowen.me / github.com/drgowen/tor-research-framework This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package tor; import tor.util.ByteFifo; import tor.util.TorInputStream; import tor.util.TorOutputStream; import java.io.IOException; import java.util.Arrays; public class TorStream { int streamId; TorCircuit circ; public enum STATES {CONNECTING, READY, DESTROYED} ; STATES state = STATES.CONNECTING; public ByteFifo recvBuffer = new ByteFifo(16384); TorStreamListener listener; int recvWindow = 500; final static int recvWindowIncrement = 50; public TorInputStream getInputStream() { return in; } public TorOutputStream getOutputStream() { return out; } TorInputStream in; TorOutputStream out; public TorStream(int streamId, TorCircuit circ, TorStreamListener list) { this.streamId = streamId; this.circ = circ; listener = list; in = new TorInputStream(this); out = new TorOutputStream(this); } public void setState(STATES newState) { synchronized (this) { state = newState; this.notifyAll(); } } public void sendHTTPGETRequest(String url, String host) throws IOException { send(("GET " + url + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n").getBytes()); } public void waitForState(STATES desired) throws IOException { while (true) { synchronized (this) { try { this.wait(1000); if (state == STATES.DESTROYED && desired != STATES.DESTROYED) throw new IOException("Waiting for unreachable state - circuit destroyed"); } catch (InterruptedException e) { e.printStackTrace(); } } if (state.equals(desired)) return; } } /** * Reads from receive buffer, blocking until bytes available. * * @param output * @param block * @return bytes received * @throws IOException */ public synchronized int recv(byte output[], boolean block) throws IOException { if (block) { synchronized (this) { while (recvBuffer.isEmpty() && state != STATES.DESTROYED) { try { wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } if (recvBuffer.isEmpty() && state == STATES.DESTROYED) return -1; byte out[] = recvBuffer.get(output.length); System.arraycopy(out, 0, output, 0, out.length); return out.length; } /** * Send bytes down this stream * * @param b Bytes to send */ public void send(byte b[]) throws IOException { if (state == STATES.DESTROYED) throw new IOException("stream destroyed"); final int maxDataLen = 509 - 1 - 2 - 2 - 4 - 2; for (int i = 0; i < Math.ceil(b.length / (double) maxDataLen) * maxDataLen; i += maxDataLen) { byte data[] = Arrays.copyOfRange(b, i, Math.min(b.length, i + maxDataLen)); circ.send(data, TorCircuit.RELAY_DATA, false, (short) streamId); } } public void destroy() throws IOException { if (state == STATES.DESTROYED) return; // don't redo! setState(STATES.DESTROYED); circ.send(new byte[]{6}, TorCircuit.RELAY_END, false, (short) streamId); circ.streams.remove(new Integer(streamId)); } /** * Internal function. Used to add received bytes to object. * * @param b Bytes */ protected void _putRecved(byte b[]) { recvWindow--; if (recvWindow < 450) { try { //System.out.println("sent SENDME (STREAM) "+recvWindow); circ.send(null, TorCircuit.RELAY_SENDME, false, (short) streamId); } catch (IOException e) { e.printStackTrace(); } recvWindow += recvWindowIncrement; } recvBuffer.put(b); synchronized (this) { this.notifyAll(); } if (listener != null) listener.dataArrived(this); } public void notifyDisconnect() { setState(STATES.DESTROYED); if (listener != null) listener.disconnected(this); } public void notifyConnect() { setState(STATES.READY); if (listener != null) listener.connected(this); } public interface TorStreamListener { public void dataArrived(TorStream s); public void connected(TorStream s); public void disconnected(TorStream s); public void failure(TorStream s); } }