From 3af077a4d8422f75b82a7787f7ab398591cae1c4 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Wed, 6 Feb 2013 23:51:23 +0000
Subject: [PATCH] Refactored exponential backoff code out of JdbcDatabase and
 added tests.

---
 .../net/sf/briar/db/ExponentialBackoff.java   | 30 +++++++++
 .../src/net/sf/briar/db/JdbcDatabase.java     | 14 +---
 briar-tests/build.xml                         |  1 +
 .../sf/briar/db/ExponentialBackoffTest.java   | 64 +++++++++++++++++++
 4 files changed, 96 insertions(+), 13 deletions(-)
 create mode 100644 briar-core/src/net/sf/briar/db/ExponentialBackoff.java
 create mode 100644 briar-tests/src/net/sf/briar/db/ExponentialBackoffTest.java

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 0000000000..4fd5e1045d
--- /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 <tt>now</tt>
+	 * over a transport with maximum latency <tt>maxLatency</tt>, where the
+	 * packet has previously been transmitted <tt>txCount</tt> times. All times
+	 * are in milliseconds. The expiry time is
+	 * <tt>now + maxLatency * 2 ^ (txCount + 1)</tt>, 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 2725a22486..dfa50aef8f 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<Connection> {
 			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 62f7292bf1..45b174c2a8 100644
--- a/briar-tests/build.xml
+++ b/briar-tests/build.xml
@@ -77,6 +77,7 @@
 			<test name='net.sf.briar.db.BasicH2Test'/>
 			<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
 			<test name='net.sf.briar.db.DatabaseComponentImplTest'/>
+			<test name='net.sf.briar.db.ExponentialBackoffTest'/>
 			<test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/>
 			<test name='net.sf.briar.lifecycle.WindowsShutdownManagerImplTest'/>
 			<test name='net.sf.briar.messaging.ConstantsTest'/>
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 0000000000..f2067afe73
--- /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
+	}
+}
-- 
GitLab