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 0000000000000000000000000000000000000000..4fd5e1045d5d206ed70a0755df1e0ecf8fd649ae --- /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 2725a22486b246737de0c53c2d01be63f7ca3cd0..dfa50aef8f2c317abb89d3fd2adf866a60803dcb 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 62f7292bf137ce78a828f592630adfb86f583b4e..45b174c2a8d401d20c7dff8f84e5a2243d0c4679 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 0000000000000000000000000000000000000000..f2067afe73bdfee266960e9c3411757d58583445 --- /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 + } +}