mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Changed the root package from net.sf.briar to org.briarproject.
This commit is contained in:
27
briar-core/src/org/briarproject/reliability/Ack.java
Normal file
27
briar-core/src/org/briarproject/reliability/Ack.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
class Ack extends Frame {
|
||||
|
||||
static final int LENGTH = 11;
|
||||
|
||||
Ack() {
|
||||
super(new byte[LENGTH]);
|
||||
buf[0] = (byte) Frame.ACK_FLAG;
|
||||
}
|
||||
|
||||
Ack(byte[] buf) {
|
||||
super(buf);
|
||||
if(buf.length != LENGTH) throw new IllegalArgumentException();
|
||||
buf[0] = (byte) Frame.ACK_FLAG;
|
||||
}
|
||||
|
||||
int getWindowSize() {
|
||||
return ByteUtils.readUint16(buf, 5);
|
||||
}
|
||||
|
||||
void setWindowSize(int windowSize) {
|
||||
ByteUtils.writeUint16(windowSize, buf, 5);
|
||||
}
|
||||
}
|
||||
27
briar-core/src/org/briarproject/reliability/Crc32.java
Normal file
27
briar-core/src/org/briarproject/reliability/Crc32.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
class Crc32 {
|
||||
|
||||
private static final long[] TABLE = new long[256];
|
||||
|
||||
static {
|
||||
for(int i = 0; i < 256; i++) {
|
||||
long c = i;
|
||||
for(int j = 0; j < 8; j++) {
|
||||
if((c & 1) != 0) c = 0xedb88320L ^ (c >> 1);
|
||||
else c >>= 1;
|
||||
}
|
||||
TABLE[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
private static long update(long c, byte[] b, int off, int len) {
|
||||
for(int i = off; i < off + len; i++)
|
||||
c = TABLE[(int) ((c ^ b[i]) & 0xff)] ^ (c >> 8);
|
||||
return c;
|
||||
}
|
||||
|
||||
static long crc(byte[] b, int off, int len) {
|
||||
return update(0xffffffffL, b, off, len) ^ 0xffffffffL;
|
||||
}
|
||||
}
|
||||
27
briar-core/src/org/briarproject/reliability/Data.java
Normal file
27
briar-core/src/org/briarproject/reliability/Data.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
class Data extends Frame {
|
||||
|
||||
static final int HEADER_LENGTH = 5, FOOTER_LENGTH = 4;
|
||||
static final int MIN_LENGTH = HEADER_LENGTH + FOOTER_LENGTH;
|
||||
static final int MAX_PAYLOAD_LENGTH = 1024;
|
||||
static final int MAX_LENGTH = MIN_LENGTH + MAX_PAYLOAD_LENGTH;
|
||||
|
||||
Data(byte[] buf) {
|
||||
super(buf);
|
||||
if(buf.length < MIN_LENGTH || buf.length > MAX_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
boolean isLastFrame() {
|
||||
return buf[0] == Frame.FIN_FLAG;
|
||||
}
|
||||
|
||||
void setLastFrame(boolean lastFrame) {
|
||||
if(lastFrame) buf[0] = (byte) Frame.FIN_FLAG;
|
||||
}
|
||||
|
||||
int getPayloadLength() {
|
||||
return buf.length - MIN_LENGTH;
|
||||
}
|
||||
}
|
||||
58
briar-core/src/org/briarproject/reliability/Frame.java
Normal file
58
briar-core/src/org/briarproject/reliability/Frame.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
abstract class Frame {
|
||||
|
||||
static final byte ACK_FLAG = (byte) 128, FIN_FLAG = 64;
|
||||
|
||||
protected final byte[] buf;
|
||||
|
||||
protected Frame(byte[] buf) {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
byte[] getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
int getLength() {
|
||||
return buf.length;
|
||||
}
|
||||
|
||||
long getChecksum() {
|
||||
return ByteUtils.readUint32(buf, buf.length - 4);
|
||||
}
|
||||
|
||||
void setChecksum(long checksum) {
|
||||
ByteUtils.writeUint32(checksum, buf, buf.length - 4);
|
||||
}
|
||||
|
||||
long calculateChecksum() {
|
||||
return Crc32.crc(buf, 0, buf.length - 4);
|
||||
}
|
||||
|
||||
long getSequenceNumber() {
|
||||
return ByteUtils.readUint32(buf, 1);
|
||||
}
|
||||
|
||||
void setSequenceNumber(long sequenceNumber) {
|
||||
ByteUtils.writeUint32(sequenceNumber, buf, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long sequenceNumber = getSequenceNumber();
|
||||
return buf[0] ^ (int) (sequenceNumber ^ (sequenceNumber >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(o instanceof Frame) {
|
||||
Frame f = (Frame) o;
|
||||
return buf[0] == f.buf[0] &&
|
||||
getSequenceNumber() == f.getSequenceNumber();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
130
briar-core/src/org/briarproject/reliability/Receiver.java
Normal file
130
briar-core/src/org/briarproject/reliability/Receiver.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.briarproject.api.reliability.ReadHandler;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
class Receiver implements ReadHandler {
|
||||
|
||||
private static final int READ_TIMEOUT = 5 * 60 * 1000; // Milliseconds
|
||||
private static final int MAX_WINDOW_SIZE = 8 * Data.MAX_PAYLOAD_LENGTH;
|
||||
|
||||
private final Clock clock;
|
||||
private final Sender sender;
|
||||
private final SortedSet<Data> dataFrames; // Locking: this
|
||||
|
||||
private int windowSize = MAX_WINDOW_SIZE; // Locking: this
|
||||
private long finalSequenceNumber = Long.MAX_VALUE;
|
||||
private long nextSequenceNumber = 1;
|
||||
|
||||
private volatile boolean valid = true;
|
||||
|
||||
Receiver(Clock clock, Sender sender) {
|
||||
this.sender = sender;
|
||||
this.clock = clock;
|
||||
dataFrames = new TreeSet<Data>(new SequenceNumberComparator());
|
||||
}
|
||||
|
||||
synchronized Data read() throws IOException, InterruptedException {
|
||||
long now = clock.currentTimeMillis(), end = now + READ_TIMEOUT;
|
||||
while(now < end && valid) {
|
||||
if(dataFrames.isEmpty()) {
|
||||
// Wait for a data frame
|
||||
wait(end - now);
|
||||
} else {
|
||||
Data d = dataFrames.first();
|
||||
if(d.getSequenceNumber() == nextSequenceNumber) {
|
||||
dataFrames.remove(d);
|
||||
// Update the window
|
||||
windowSize += d.getPayloadLength();
|
||||
sender.sendAck(0, windowSize);
|
||||
nextSequenceNumber++;
|
||||
return d;
|
||||
} else {
|
||||
// Wait for the next in-order data frame
|
||||
wait(end - now);
|
||||
}
|
||||
}
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
if(valid) throw new IOException("Read timed out");
|
||||
throw new IOException("Connection closed");
|
||||
}
|
||||
|
||||
void invalidate() {
|
||||
valid = false;
|
||||
synchronized(this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRead(byte[] b) throws IOException {
|
||||
if(!valid) throw new IOException("Connection closed");
|
||||
switch(b[0]) {
|
||||
case 0:
|
||||
case Frame.FIN_FLAG:
|
||||
handleData(b);
|
||||
break;
|
||||
case Frame.ACK_FLAG:
|
||||
sender.handleAck(b);
|
||||
break;
|
||||
default:
|
||||
// Ignore unknown frame type
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void handleData(byte[] b) throws IOException {
|
||||
if(b.length < Data.MIN_LENGTH || b.length > Data.MAX_LENGTH) {
|
||||
// Ignore data frame with invalid length
|
||||
return;
|
||||
}
|
||||
Data d = new Data(b);
|
||||
int payloadLength = d.getPayloadLength();
|
||||
if(payloadLength > windowSize) return; // No space in the window
|
||||
if(d.getChecksum() != d.calculateChecksum()) {
|
||||
// Ignore data frame with invalid checksum
|
||||
return;
|
||||
}
|
||||
long sequenceNumber = d.getSequenceNumber();
|
||||
if(sequenceNumber == 0) {
|
||||
// Window probe
|
||||
} else if(sequenceNumber < nextSequenceNumber) {
|
||||
// Duplicate data frame
|
||||
} else if(d.isLastFrame()) {
|
||||
finalSequenceNumber = sequenceNumber;
|
||||
// Remove any data frames with higher sequence numbers
|
||||
Iterator<Data> it = dataFrames.iterator();
|
||||
while(it.hasNext()) {
|
||||
Data d1 = it.next();
|
||||
if(d1.getSequenceNumber() >= finalSequenceNumber) it.remove();
|
||||
}
|
||||
if(dataFrames.add(d)) {
|
||||
windowSize -= payloadLength;
|
||||
notifyAll();
|
||||
}
|
||||
} else if(sequenceNumber < finalSequenceNumber) {
|
||||
if(dataFrames.add(d)) {
|
||||
windowSize -= payloadLength;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
// Acknowledge the data frame even if it's a duplicate
|
||||
sender.sendAck(sequenceNumber, windowSize);
|
||||
}
|
||||
|
||||
private static class SequenceNumberComparator implements Comparator<Data> {
|
||||
|
||||
public int compare(Data d1, Data d2) {
|
||||
long s1 = d1.getSequenceNumber(), s2 = d2.getSequenceNumber();
|
||||
if(s1 < s2) return -1;
|
||||
if(s1 > s2) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
class ReceiverInputStream extends InputStream {
|
||||
|
||||
private final Receiver receiver;
|
||||
|
||||
private Data data = null;
|
||||
private int offset = 0, length = 0;
|
||||
|
||||
ReceiverInputStream(Receiver receiver) {
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if(length == -1) return -1;
|
||||
while(length == 0) if(!receive()) return -1;
|
||||
int b = data.getBuffer()[offset] & 0xff;
|
||||
offset++;
|
||||
length--;
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if(length == -1) return -1;
|
||||
while(length == 0) if(!receive()) return -1;
|
||||
len = Math.min(len, length);
|
||||
System.arraycopy(data.getBuffer(), offset, b, off, len);
|
||||
offset += len;
|
||||
length -= len;
|
||||
return len;
|
||||
}
|
||||
|
||||
private boolean receive() throws IOException {
|
||||
assert length == 0;
|
||||
if(data != null && data.isLastFrame()) {
|
||||
length = -1;
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
data = receiver.read();
|
||||
} catch(InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while reading");
|
||||
}
|
||||
offset = Data.HEADER_LENGTH;
|
||||
length = data.getLength() - Data.MIN_LENGTH;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.api.reliability.ReliabilityExecutor;
|
||||
import org.briarproject.api.reliability.ReliabilityLayer;
|
||||
import org.briarproject.api.reliability.ReliabilityLayerFactory;
|
||||
import org.briarproject.api.reliability.WriteHandler;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
|
||||
class ReliabilityLayerFactoryImpl implements ReliabilityLayerFactory {
|
||||
|
||||
private final Executor executor;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
ReliabilityLayerFactoryImpl(@ReliabilityExecutor Executor executor) {
|
||||
this.executor = executor;
|
||||
clock = new SystemClock();
|
||||
}
|
||||
|
||||
public ReliabilityLayer createReliabilityLayer(WriteHandler writeHandler) {
|
||||
return new ReliabilityLayerImpl(executor, clock, writeHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.briarproject.api.reliability.ReliabilityLayer;
|
||||
import org.briarproject.api.reliability.WriteHandler;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
class ReliabilityLayerImpl implements ReliabilityLayer, WriteHandler {
|
||||
|
||||
private static final int TICK_INTERVAL = 500; // Milliseconds
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ReliabilityLayerImpl.class.getName());
|
||||
|
||||
private final Executor executor;
|
||||
private final Clock clock;
|
||||
private final WriteHandler writeHandler;
|
||||
private final BlockingQueue<byte[]> writes;
|
||||
|
||||
private volatile Receiver receiver = null;
|
||||
private volatile SlipDecoder decoder = null;
|
||||
private volatile ReceiverInputStream inputStream = null;
|
||||
private volatile SenderOutputStream outputStream = null;
|
||||
private volatile boolean running = false;
|
||||
|
||||
ReliabilityLayerImpl(Executor executor, Clock clock,
|
||||
WriteHandler writeHandler) {
|
||||
this.executor = executor;
|
||||
this.clock = clock;
|
||||
this.writeHandler = writeHandler;
|
||||
writes = new LinkedBlockingQueue<byte[]>();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
SlipEncoder encoder = new SlipEncoder(this);
|
||||
final Sender sender = new Sender(clock, encoder);
|
||||
receiver = new Receiver(clock, sender);
|
||||
decoder = new SlipDecoder(receiver, Data.MAX_LENGTH);
|
||||
inputStream = new ReceiverInputStream(receiver);
|
||||
outputStream = new SenderOutputStream(sender);
|
||||
running = true;
|
||||
executor.execute(new Runnable() {
|
||||
public void run() {
|
||||
long now = clock.currentTimeMillis();
|
||||
long next = now + TICK_INTERVAL;
|
||||
try {
|
||||
while(running) {
|
||||
byte[] b = null;
|
||||
while(now < next && b == null) {
|
||||
b = writes.poll(next - now, MILLISECONDS);
|
||||
if(!running) return;
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
if(b == null) {
|
||||
sender.tick();
|
||||
while(next <= now) next += TICK_INTERVAL;
|
||||
} else {
|
||||
if(b.length == 0) return; // Poison pill
|
||||
writeHandler.handleWrite(b);
|
||||
}
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.warning("Interrupted while waiting to write");
|
||||
Thread.currentThread().interrupt();
|
||||
running = false;
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
receiver.invalidate();
|
||||
writes.add(new byte[0]); // Poison pill
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
// The lower layer calls this method to pass data up to the SLIP decoder
|
||||
public void handleRead(byte[] b) throws IOException {
|
||||
if(running) decoder.handleRead(b);
|
||||
}
|
||||
|
||||
// The SLIP encoder calls this method to pass data down to the lower layer
|
||||
public void handleWrite(byte[] b) {
|
||||
if(running && b.length > 0) writes.add(b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.reliability.ReliabilityExecutor;
|
||||
import org.briarproject.api.reliability.ReliabilityLayerFactory;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
public class ReliabilityModule extends AbstractModule {
|
||||
|
||||
private final ExecutorService reliabilityExecutor;
|
||||
|
||||
public ReliabilityModule() {
|
||||
// The thread pool is unbounded, so use direct handoff
|
||||
BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Create threads as required and keep them in the pool for 60 seconds
|
||||
reliabilityExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
||||
60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
protected void configure() {
|
||||
bind(ReliabilityLayerFactory.class).to(
|
||||
ReliabilityLayerFactoryImpl.class);
|
||||
}
|
||||
|
||||
@Provides @Singleton @ReliabilityExecutor
|
||||
Executor getReliabilityExecutor(LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(reliabilityExecutor);
|
||||
return reliabilityExecutor;
|
||||
}
|
||||
}
|
||||
188
briar-core/src/org/briarproject/reliability/Sender.java
Normal file
188
briar-core/src/org/briarproject/reliability/Sender.java
Normal file
@@ -0,0 +1,188 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.briarproject.api.reliability.WriteHandler;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
class Sender {
|
||||
|
||||
// All times are in milliseconds
|
||||
private static final int WRITE_TIMEOUT = 5 * 60 * 1000;
|
||||
private static final int MIN_RTO = 1000;
|
||||
private static final int MAX_RTO = 60 * 1000;
|
||||
private static final int INITIAL_RTT = 0;
|
||||
private static final int INITIAL_RTT_VAR = 3 * 1000;
|
||||
private static final int MAX_WINDOW_SIZE = 64 * Data.MAX_PAYLOAD_LENGTH;
|
||||
|
||||
private final Clock clock;
|
||||
private final WriteHandler writeHandler;
|
||||
private final LinkedList<Outstanding> outstanding; // Locking: this
|
||||
|
||||
// All of the following are locking: this
|
||||
private int outstandingBytes = 0;
|
||||
private int windowSize = Data.MAX_PAYLOAD_LENGTH;
|
||||
private int rtt = INITIAL_RTT, rttVar = INITIAL_RTT_VAR;
|
||||
private int rto = rtt + (rttVar << 2);
|
||||
private long lastWindowUpdateOrProbe = Long.MAX_VALUE;
|
||||
private boolean dataWaiting = false;
|
||||
|
||||
Sender(Clock clock, WriteHandler writeHandler) {
|
||||
this.clock = clock;
|
||||
this.writeHandler = writeHandler;
|
||||
outstanding = new LinkedList<Outstanding>();
|
||||
}
|
||||
|
||||
void sendAck(long sequenceNumber, int windowSize) throws IOException {
|
||||
Ack a = new Ack();
|
||||
a.setSequenceNumber(sequenceNumber);
|
||||
a.setWindowSize(windowSize);
|
||||
a.setChecksum(a.calculateChecksum());
|
||||
writeHandler.handleWrite(a.getBuffer());
|
||||
}
|
||||
|
||||
void handleAck(byte[] b) throws IOException {
|
||||
if(b.length != Ack.LENGTH) {
|
||||
// Ignore ack frame with invalid length
|
||||
return;
|
||||
}
|
||||
Ack a = new Ack(b);
|
||||
if(a.getChecksum() != a.calculateChecksum()) {
|
||||
// Ignore ack frame with invalid checksum
|
||||
return;
|
||||
}
|
||||
long sequenceNumber = a.getSequenceNumber();
|
||||
long now = clock.currentTimeMillis();
|
||||
Outstanding fastRetransmit = null;
|
||||
synchronized(this) {
|
||||
// Remove the acked data frame if it's outstanding
|
||||
int foundIndex = -1;
|
||||
Iterator<Outstanding> it = outstanding.iterator();
|
||||
for(int i = 0; it.hasNext(); i++) {
|
||||
Outstanding o = it.next();
|
||||
if(o.data.getSequenceNumber() == sequenceNumber) {
|
||||
it.remove();
|
||||
outstandingBytes -= o.data.getPayloadLength();
|
||||
foundIndex = i;
|
||||
// Update the round-trip time and retransmission timeout
|
||||
if(!o.retransmitted) {
|
||||
int sample = (int) (now - o.lastTransmitted);
|
||||
int error = sample - rtt;
|
||||
rtt += (error >> 3);
|
||||
rttVar += (Math.abs(error) - rttVar) >> 2;
|
||||
rto = rtt + (rttVar << 2);
|
||||
if(rto < MIN_RTO) rto = MIN_RTO;
|
||||
else if(rto > MAX_RTO) rto = MAX_RTO;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If any older data frames are outstanding, retransmit the oldest
|
||||
if(foundIndex > 0) {
|
||||
fastRetransmit = outstanding.poll();
|
||||
fastRetransmit.lastTransmitted = now;
|
||||
fastRetransmit.retransmitted = true;
|
||||
outstanding.add(fastRetransmit);
|
||||
}
|
||||
// Update the window
|
||||
lastWindowUpdateOrProbe = now;
|
||||
int oldWindowSize = windowSize;
|
||||
// Don't accept an unreasonably large window size
|
||||
windowSize = Math.min(a.getWindowSize(), MAX_WINDOW_SIZE);
|
||||
// If space has become available, notify any waiting writers
|
||||
if(windowSize > oldWindowSize || foundIndex != -1) notifyAll();
|
||||
}
|
||||
// Fast retransmission
|
||||
if(fastRetransmit != null)
|
||||
writeHandler.handleWrite(fastRetransmit.data.getBuffer());
|
||||
}
|
||||
|
||||
void tick() throws IOException {
|
||||
long now = clock.currentTimeMillis();
|
||||
List<Outstanding> retransmit = null;
|
||||
boolean sendProbe = false;
|
||||
synchronized(this) {
|
||||
if(outstanding.isEmpty()) {
|
||||
if(dataWaiting && now - lastWindowUpdateOrProbe > rto) {
|
||||
sendProbe = true;
|
||||
rto <<= 1;
|
||||
if(rto > MAX_RTO) rto = MAX_RTO;
|
||||
}
|
||||
} else {
|
||||
Iterator<Outstanding> it = outstanding.iterator();
|
||||
while(it.hasNext()) {
|
||||
Outstanding o = it.next();
|
||||
if(now - o.lastTransmitted > rto) {
|
||||
it.remove();
|
||||
if(retransmit == null)
|
||||
retransmit = new ArrayList<Outstanding>();
|
||||
retransmit.add(o);
|
||||
// Update the retransmission timeout
|
||||
rto <<= 1;
|
||||
if(rto > MAX_RTO) rto = MAX_RTO;
|
||||
}
|
||||
}
|
||||
if(retransmit != null) {
|
||||
for(Outstanding o : retransmit) {
|
||||
o.lastTransmitted = now;
|
||||
o.retransmitted = true;
|
||||
outstanding.add(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Send a window probe if necessary
|
||||
if(sendProbe) {
|
||||
byte[] buf = new byte[Data.MIN_LENGTH];
|
||||
Data probe = new Data(buf);
|
||||
probe.setChecksum(probe.calculateChecksum());
|
||||
writeHandler.handleWrite(buf);
|
||||
}
|
||||
// Retransmit any lost data frames
|
||||
if(retransmit != null) {
|
||||
for(Outstanding o : retransmit)
|
||||
writeHandler.handleWrite(o.data.getBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
void write(Data d) throws IOException, InterruptedException {
|
||||
int payloadLength = d.getPayloadLength();
|
||||
synchronized(this) {
|
||||
// Wait for space in the window
|
||||
long now = clock.currentTimeMillis(), end = now + WRITE_TIMEOUT;
|
||||
while(now < end && outstandingBytes + payloadLength >= windowSize) {
|
||||
dataWaiting = true;
|
||||
wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
if(outstandingBytes + payloadLength >= windowSize)
|
||||
throw new IOException("Write timed out");
|
||||
outstanding.add(new Outstanding(d, now));
|
||||
outstandingBytes += payloadLength;
|
||||
dataWaiting = false;
|
||||
}
|
||||
writeHandler.handleWrite(d.getBuffer());
|
||||
}
|
||||
|
||||
synchronized void flush() throws IOException, InterruptedException {
|
||||
while(dataWaiting || !outstanding.isEmpty()) wait();
|
||||
}
|
||||
|
||||
private static class Outstanding {
|
||||
|
||||
private final Data data;
|
||||
|
||||
private volatile long lastTransmitted;
|
||||
private volatile boolean retransmitted;
|
||||
|
||||
private Outstanding(Data data, long lastTransmitted) {
|
||||
this.data = data;
|
||||
this.lastTransmitted = lastTransmitted;
|
||||
retransmitted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class SenderOutputStream extends OutputStream {
|
||||
|
||||
private final Sender sender;
|
||||
private final byte[] buf = new byte[Data.MAX_LENGTH];
|
||||
|
||||
private int offset = Data.HEADER_LENGTH;
|
||||
private long sequenceNumber = 1;
|
||||
|
||||
SenderOutputStream(Sender sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
send(true);
|
||||
try {
|
||||
sender.flush();
|
||||
} catch(InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while closing");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if(offset > Data.HEADER_LENGTH) send(false);
|
||||
try {
|
||||
sender.flush();
|
||||
} catch(InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while flushing");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
buf[offset] = (byte) b;
|
||||
offset++;
|
||||
if(offset == Data.HEADER_LENGTH + Data.MAX_PAYLOAD_LENGTH) send(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
int available = Data.MAX_LENGTH - offset - Data.FOOTER_LENGTH;
|
||||
while(available <= len) {
|
||||
System.arraycopy(b, off, buf, offset, available);
|
||||
offset += available;
|
||||
send(false);
|
||||
off += available;
|
||||
len -= available;
|
||||
available = Data.MAX_LENGTH - offset - Data.FOOTER_LENGTH;
|
||||
}
|
||||
System.arraycopy(b, off, buf, offset, len);
|
||||
offset += len;
|
||||
}
|
||||
|
||||
private void send(boolean lastFrame) throws IOException {
|
||||
byte[] frame = new byte[offset + Data.FOOTER_LENGTH];
|
||||
System.arraycopy(buf, 0, frame, 0, frame.length);
|
||||
Data d = new Data(frame);
|
||||
d.setLastFrame(lastFrame);
|
||||
d.setSequenceNumber(sequenceNumber++);
|
||||
d.setChecksum(d.calculateChecksum());
|
||||
try {
|
||||
sender.write(d);
|
||||
} catch(InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while writing");
|
||||
}
|
||||
offset = Data.HEADER_LENGTH;
|
||||
}
|
||||
}
|
||||
75
briar-core/src/org/briarproject/reliability/SlipDecoder.java
Normal file
75
briar-core/src/org/briarproject/reliability/SlipDecoder.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.briarproject.api.reliability.ReadHandler;
|
||||
|
||||
class SlipDecoder implements ReadHandler {
|
||||
|
||||
// https://tools.ietf.org/html/rfc1055
|
||||
private static final byte END = (byte) 192, ESC = (byte) 219;
|
||||
private static final byte TEND = (byte) 220, TESC = (byte) 221;
|
||||
|
||||
private final ReadHandler readHandler;
|
||||
private final byte[] buf;
|
||||
|
||||
private int decodedLength = 0;
|
||||
private boolean escape = false;
|
||||
|
||||
SlipDecoder(ReadHandler readHandler, int maxDecodedLength) {
|
||||
this.readHandler = readHandler;
|
||||
buf = new byte[maxDecodedLength];
|
||||
}
|
||||
|
||||
public void handleRead(byte[] b) throws IOException {
|
||||
for(int i = 0; i < b.length; i++) {
|
||||
switch(b[i]) {
|
||||
case END:
|
||||
if(escape) {
|
||||
reset(true);
|
||||
} else {
|
||||
if(decodedLength > 0) {
|
||||
byte[] decoded = new byte[decodedLength];
|
||||
System.arraycopy(buf, 0, decoded, 0, decodedLength);
|
||||
readHandler.handleRead(decoded);
|
||||
}
|
||||
reset(false);
|
||||
}
|
||||
break;
|
||||
case ESC:
|
||||
if(escape) reset(true);
|
||||
else escape = true;
|
||||
break;
|
||||
case TEND:
|
||||
if(escape) {
|
||||
escape = false;
|
||||
if(decodedLength == buf.length) reset(true);
|
||||
else buf[decodedLength++] = END;
|
||||
} else {
|
||||
if(decodedLength == buf.length) reset(true);
|
||||
else buf[decodedLength++] = TEND;
|
||||
}
|
||||
break;
|
||||
case TESC:
|
||||
if(escape) {
|
||||
escape = false;
|
||||
if(decodedLength == buf.length) reset(true);
|
||||
else buf[decodedLength++] = ESC;
|
||||
} else {
|
||||
if(decodedLength == buf.length) reset(true);
|
||||
else buf[decodedLength++] = TESC;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(escape || decodedLength == buf.length) reset(true);
|
||||
else buf[decodedLength++] = b[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reset(boolean error) {
|
||||
escape = false;
|
||||
decodedLength = 0;
|
||||
}
|
||||
}
|
||||
39
briar-core/src/org/briarproject/reliability/SlipEncoder.java
Normal file
39
briar-core/src/org/briarproject/reliability/SlipEncoder.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package org.briarproject.reliability;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.briarproject.api.reliability.WriteHandler;
|
||||
|
||||
class SlipEncoder implements WriteHandler {
|
||||
|
||||
// https://tools.ietf.org/html/rfc1055
|
||||
private static final byte END = (byte) 192, ESC = (byte) 219;
|
||||
private static final byte TEND = (byte) 220, TESC = (byte) 221;
|
||||
|
||||
private final WriteHandler writeHandler;
|
||||
|
||||
SlipEncoder(WriteHandler writeHandler) {
|
||||
this.writeHandler = writeHandler;
|
||||
}
|
||||
|
||||
public void handleWrite(byte[] b) throws IOException {
|
||||
int encodedLength = b.length + 2;
|
||||
for(int i = 0; i < b.length; i++)
|
||||
if(b[i] == END || b[i] == ESC) encodedLength++;
|
||||
byte[] encoded = new byte[encodedLength];
|
||||
encoded[0] = END;
|
||||
for(int i = 0, j = 1; i < b.length; i++) {
|
||||
if(b[i] == END) {
|
||||
encoded[j++] = ESC;
|
||||
encoded[j++] = TEND;
|
||||
} else if(b[i] == ESC) {
|
||||
encoded[j++] = ESC;
|
||||
encoded[j++] = TESC;
|
||||
} else {
|
||||
encoded[j++] = b[i];
|
||||
}
|
||||
}
|
||||
encoded[encodedLength - 1] = END;
|
||||
writeHandler.handleWrite(encoded);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user