diff --git a/briar-core/src/net/sf/briar/db/ExponentialBackoff.java b/briar-core/src/net/sf/briar/db/ExponentialBackoff.java
new file mode 100644
index 000000000..4fd5e1045
--- /dev/null
+++ b/briar-core/src/net/sf/briar/db/ExponentialBackoff.java
@@ -0,0 +1,30 @@
+package net.sf.briar.db;
+
+class ExponentialBackoff {
+
+ /**
+ * Returns the expiry time of a packet transmitted at time now
+ * over a transport with maximum latency maxLatency, where the
+ * packet has previously been transmitted txCount times. All times
+ * are in milliseconds. The expiry time is
+ * now + maxLatency * 2 ^ (txCount + 1), so the interval between
+ * transmissions increases exponentially. If the expiry time would
+ * be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned.
+ */
+ static long calculateExpiry(long now, long maxLatency, int txCount) {
+ if(now < 0) throw new IllegalArgumentException();
+ if(maxLatency <= 0) throw new IllegalArgumentException();
+ if(txCount < 0) throw new IllegalArgumentException();
+ // The maximum round-trip time is twice the maximum latency
+ long roundTrip = maxLatency * 2;
+ if(roundTrip < 0) return Long.MAX_VALUE;
+ // The interval between transmissions is roundTrip * 2 ^ txCount
+ for(int i = 0; i < txCount; i++) {
+ roundTrip <<= 1;
+ if(roundTrip < 0) return Long.MAX_VALUE;
+ }
+ // The expiry time is the current time plus the interval
+ long expiry = now + roundTrip;
+ return expiry < 0 ? Long.MAX_VALUE : expiry;
+ }
+}
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 2725a2248..dfa50aef8 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -5,6 +5,7 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.db.DatabaseConstants.RETENTION_MODULUS;
+import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
import java.io.File;
import java.io.FileNotFoundException;
@@ -2877,17 +2878,4 @@ abstract class JdbcDatabase implements Database {
throw new DbException(e);
}
}
-
- // FIXME: Refactor the exponential backoff logic into a separate class
- private long calculateExpiry(long now, long maxLatency, int txCount) {
- long roundTrip = maxLatency * 2;
- if(roundTrip < 0) return Long.MAX_VALUE;
- for(int i = 0; i < txCount; i++) {
- roundTrip <<= 1;
- if(roundTrip < 0) return Long.MAX_VALUE;
- }
- long expiry = now + roundTrip;
- if(expiry < 0) return Long.MAX_VALUE;
- return expiry;
- }
}
diff --git a/briar-tests/build.xml b/briar-tests/build.xml
index 62f7292bf..45b174c2a 100644
--- a/briar-tests/build.xml
+++ b/briar-tests/build.xml
@@ -77,6 +77,7 @@
+
diff --git a/briar-tests/src/net/sf/briar/db/ExponentialBackoffTest.java b/briar-tests/src/net/sf/briar/db/ExponentialBackoffTest.java
new file mode 100644
index 000000000..f2067afe7
--- /dev/null
+++ b/briar-tests/src/net/sf/briar/db/ExponentialBackoffTest.java
@@ -0,0 +1,64 @@
+package net.sf.briar.db;
+
+import net.sf.briar.BriarTestCase;
+
+import org.junit.Test;
+
+public class ExponentialBackoffTest extends BriarTestCase {
+
+ private static final int ONE_HOUR = 60 * 60 * 1000;
+
+ @Test
+ public void testFirstIntervalEqualsRoundTripTime() {
+ long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
+ assertEquals(2 * ONE_HOUR, first);
+ }
+
+ @Test
+ public void testIntervalsIncreaseExponentially() {
+ long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
+ long second = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 1);
+ long third = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 2);
+ long fourth = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 3);
+ assertEquals(third, fourth / 2);
+ assertEquals(second, third / 2);
+ assertEquals(first, second / 2);
+ }
+
+ @Test
+ public void testIntervalIsAddedToCurrentTime() {
+ long now = System.currentTimeMillis();
+ long fromZero = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
+ long fromNow = ExponentialBackoff.calculateExpiry(now, ONE_HOUR, 0);
+ assertEquals(now, fromNow - fromZero);
+ }
+
+ @Test
+ public void testRoundTripTimeOverflow() {
+ long maxLatency = Long.MAX_VALUE / 2 + 1; // RTT will overflow
+ long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
+ assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
+ }
+
+ @Test
+ public void testTransmissionCountOverflow() {
+ long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
+ long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
+ assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
+ expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 1);
+ assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
+ expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 2);
+ assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
+ }
+
+ @Test
+ public void testCurrentTimeOverflow() {
+ long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
+ long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
+ assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
+ expiry = ExponentialBackoff.calculateExpiry(1, maxLatency, 0);
+ assertEquals(Long.MAX_VALUE, expiry); // No overflow
+ expiry = ExponentialBackoff.calculateExpiry(2, maxLatency, 0);
+ assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
+ }
+}