mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
New retransmission mechanism, which does away with the need for bundle IDs and should cope better with high bandwidth-delay product links.
This commit is contained in:
@@ -1,26 +0,0 @@
|
|||||||
package net.sf.briar.api.protocol;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-safe wrapper for a byte array that uniquely identifies a bundle of
|
|
||||||
* acknowledgements, subscriptions, and batches of messages.
|
|
||||||
*/
|
|
||||||
public class BundleId extends UniqueId {
|
|
||||||
|
|
||||||
public static final BundleId NONE = new BundleId(new byte[] {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
});
|
|
||||||
|
|
||||||
public BundleId(byte[] id) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if(o instanceof BundleId)
|
|
||||||
return Arrays.equals(id, ((BundleId) o).id);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,8 @@ public interface BundleWriter {
|
|||||||
/** Returns the bundle's remaining capacity in bytes. */
|
/** Returns the bundle's remaining capacity in bytes. */
|
||||||
long getRemainingCapacity() throws IOException;
|
long getRemainingCapacity() throws IOException;
|
||||||
|
|
||||||
/** Adds a header to the bundle and returns its identifier. */
|
/** Adds a header to the bundle. */
|
||||||
BundleId addHeader(Iterable<BatchId> acks, Iterable<GroupId> subs,
|
void addHeader(Iterable<BatchId> acks, Iterable<GroupId> subs,
|
||||||
Map<String, String> transports) throws IOException,
|
Map<String, String> transports) throws IOException,
|
||||||
GeneralSecurityException;
|
GeneralSecurityException;
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ public interface Header {
|
|||||||
|
|
||||||
static final int MAX_SIZE = 1024 * 1024;
|
static final int MAX_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
// FIXME: Remove BundleId when refactoring is complete
|
|
||||||
BundleId getId();
|
|
||||||
|
|
||||||
/** Returns the acknowledgements contained in the header. */
|
/** Returns the acknowledgements contained in the header. */
|
||||||
Set<BatchId> getAcks();
|
Set<BatchId> getAcks();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import net.sf.briar.api.db.DbException;
|
|||||||
import net.sf.briar.api.db.Status;
|
import net.sf.briar.api.db.Status;
|
||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
@@ -35,14 +34,10 @@ import net.sf.briar.api.protocol.MessageId;
|
|||||||
interface Database<T> {
|
interface Database<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A batch sent to a contact is considered lost when this many bundles have
|
* A batch sent to a contact is considered lost when this many more
|
||||||
* been received from the contact since the batch was sent.
|
* recently sent batches have been acknowledged.
|
||||||
* <p>
|
|
||||||
* FIXME: Come up with a better retransmission scheme. This scheme doesn't
|
|
||||||
* cope well with transports that have high latency but send bundles
|
|
||||||
* frequently.
|
|
||||||
*/
|
*/
|
||||||
static final int RETRANSMIT_THRESHOLD = 3;
|
static final int RETRANSMIT_THRESHOLD = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database.
|
* Opens the database.
|
||||||
@@ -103,15 +98,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void addOutstandingBatch(T txn, ContactId c, BatchId b, Set<MessageId> sent) throws DbException;
|
void addOutstandingBatch(T txn, ContactId c, BatchId b, Set<MessageId> sent) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Records a received bundle. This should be called after processing the
|
|
||||||
* bundle's contents, and may result in outstanding messages becoming
|
|
||||||
* eligible for retransmission.
|
|
||||||
* <p>
|
|
||||||
* Locking: contacts read, messages read, messageStatuses write.
|
|
||||||
*/
|
|
||||||
Set<BatchId> addReceivedBundle(T txn, ContactId c, BundleId b) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to the given group.
|
* Subscribes to the given group.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -167,6 +153,14 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
GroupId getGroup(T txn, MessageId m) throws DbException;
|
GroupId getGroup(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IDs of any batches sent to the given contact that should now
|
||||||
|
* be considered lost.
|
||||||
|
* <p>
|
||||||
|
* Locking: contacts read, messages read, messageStatuses read.
|
||||||
|
*/
|
||||||
|
Set<BatchId> getLostBatches(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the message identified by the given ID.
|
* Returns the message identified by the given ID.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class H2Database extends JdbcDatabase {
|
|||||||
@Inject
|
@Inject
|
||||||
H2Database(File dir, MessageFactory messageFactory,
|
H2Database(File dir, MessageFactory messageFactory,
|
||||||
@DatabasePassword Password password, long maxSize) {
|
@DatabasePassword Password password, long maxSize) {
|
||||||
super(messageFactory, "BINARY(32)");
|
super(messageFactory, "BINARY(32)", "BIGINT");
|
||||||
home = new File(dir, "db");
|
home = new File(dir, "db");
|
||||||
this.password = password;
|
this.password = password;
|
||||||
url = "jdbc:h2:split:" + home.getPath()
|
url = "jdbc:h2:split:" + home.getPath()
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -26,7 +25,6 @@ import net.sf.briar.api.db.DbException;
|
|||||||
import net.sf.briar.api.db.Status;
|
import net.sf.briar.api.db.Status;
|
||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageFactory;
|
import net.sf.briar.api.protocol.MessageFactory;
|
||||||
@@ -73,7 +71,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String CREATE_CONTACTS =
|
private static final String CREATE_CONTACTS =
|
||||||
"CREATE TABLE contacts"
|
"CREATE TABLE contacts"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " lastBundleReceived XXXX NOT NULL,"
|
|
||||||
+ " PRIMARY KEY (contactId))";
|
+ " PRIMARY KEY (contactId))";
|
||||||
|
|
||||||
private static final String CREATE_BATCHES_TO_ACK =
|
private static final String CREATE_BATCHES_TO_ACK =
|
||||||
@@ -96,7 +93,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE TABLE outstandingBatches"
|
"CREATE TABLE outstandingBatches"
|
||||||
+ " (batchId XXXX NOT NULL,"
|
+ " (batchId XXXX NOT NULL,"
|
||||||
+ " contactId INT NOT NULL,"
|
+ " contactId INT NOT NULL,"
|
||||||
+ " lastBundleReceived XXXX NOT NULL,"
|
+ " timestamp YYYY NOT NULL,"
|
||||||
|
+ " passover INT NOT NULL,"
|
||||||
+ " PRIMARY KEY (batchId),"
|
+ " PRIMARY KEY (batchId),"
|
||||||
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
@@ -124,15 +122,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " rating SMALLINT NOT NULL,"
|
+ " rating SMALLINT NOT NULL,"
|
||||||
+ " PRIMARY KEY (authorId))";
|
+ " PRIMARY KEY (authorId))";
|
||||||
|
|
||||||
private static final String CREATE_RECEIVED_BUNDLES =
|
|
||||||
"CREATE TABLE receivedBundles"
|
|
||||||
+ " (bundleId XXXX NOT NULL,"
|
|
||||||
+ " contactId INT NOT NULL,"
|
|
||||||
+ " timestamp BIGINT NOT NULL,"
|
|
||||||
+ " PRIMARY KEY (bundleId, contactId),"
|
|
||||||
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
|
||||||
+ " ON DELETE CASCADE)";
|
|
||||||
|
|
||||||
private static final String CREATE_STATUSES =
|
private static final String CREATE_STATUSES =
|
||||||
"CREATE TABLE statuses"
|
"CREATE TABLE statuses"
|
||||||
+ " (messageId XXXX NOT NULL,"
|
+ " (messageId XXXX NOT NULL,"
|
||||||
@@ -169,7 +158,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Logger.getLogger(JdbcDatabase.class.getName());
|
Logger.getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
private final MessageFactory messageFactory;
|
private final MessageFactory messageFactory;
|
||||||
private final String hashType;
|
// Different database libraries use different names for certain types
|
||||||
|
private final String hashType, bigIntType;
|
||||||
private final LinkedList<Connection> connections =
|
private final LinkedList<Connection> connections =
|
||||||
new LinkedList<Connection>(); // Locking: self
|
new LinkedList<Connection>(); // Locking: self
|
||||||
|
|
||||||
@@ -178,9 +168,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
protected abstract Connection createConnection() throws SQLException;
|
protected abstract Connection createConnection() throws SQLException;
|
||||||
|
|
||||||
JdbcDatabase(MessageFactory messageFactory, String hashType) {
|
JdbcDatabase(MessageFactory messageFactory, String hashType,
|
||||||
|
String bigIntType) {
|
||||||
this.messageFactory = messageFactory;
|
this.messageFactory = messageFactory;
|
||||||
this.hashType = hashType;
|
this.hashType = hashType;
|
||||||
|
this.bigIntType = bigIntType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void open(boolean resume, File dir, String driverClass)
|
protected void open(boolean resume, File dir, String driverClass)
|
||||||
@@ -219,47 +211,44 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating localSubscriptions table");
|
LOG.fine("Creating localSubscriptions table");
|
||||||
s.executeUpdate(insertHashType(CREATE_LOCAL_SUBSCRIPTIONS));
|
s.executeUpdate(insertTypeNames(CREATE_LOCAL_SUBSCRIPTIONS));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating messages table");
|
LOG.fine("Creating messages table");
|
||||||
s.executeUpdate(insertHashType(CREATE_MESSAGES));
|
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
|
s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_AUTHOR);
|
s.executeUpdate(INDEX_MESSAGES_BY_AUTHOR);
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_TIMESTAMP);
|
s.executeUpdate(INDEX_MESSAGES_BY_TIMESTAMP);
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_SENDABILITY);
|
s.executeUpdate(INDEX_MESSAGES_BY_SENDABILITY);
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating contacts table");
|
LOG.fine("Creating contacts table");
|
||||||
s.executeUpdate(insertHashType(CREATE_CONTACTS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating batchesToAck table");
|
LOG.fine("Creating batchesToAck table");
|
||||||
s.executeUpdate(insertHashType(CREATE_BATCHES_TO_ACK));
|
s.executeUpdate(insertTypeNames(CREATE_BATCHES_TO_ACK));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating contactSubscriptions table");
|
LOG.fine("Creating contactSubscriptions table");
|
||||||
s.executeUpdate(insertHashType(CREATE_CONTACT_SUBSCRIPTIONS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACT_SUBSCRIPTIONS));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating outstandingBatches table");
|
LOG.fine("Creating outstandingBatches table");
|
||||||
s.executeUpdate(insertHashType(CREATE_OUTSTANDING_BATCHES));
|
s.executeUpdate(insertTypeNames(CREATE_OUTSTANDING_BATCHES));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating outstandingMessages table");
|
LOG.fine("Creating outstandingMessages table");
|
||||||
s.executeUpdate(insertHashType(CREATE_OUTSTANDING_MESSAGES));
|
s.executeUpdate(insertTypeNames(CREATE_OUTSTANDING_MESSAGES));
|
||||||
s.executeUpdate(INDEX_OUTSTANDING_MESSAGES_BY_BATCH);
|
s.executeUpdate(INDEX_OUTSTANDING_MESSAGES_BY_BATCH);
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating ratings table");
|
LOG.fine("Creating ratings table");
|
||||||
s.executeUpdate(insertHashType(CREATE_RATINGS));
|
s.executeUpdate(insertTypeNames(CREATE_RATINGS));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
|
||||||
LOG.fine("Creating receivedBundles table");
|
|
||||||
s.executeUpdate(insertHashType(CREATE_RECEIVED_BUNDLES));
|
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating statuses table");
|
LOG.fine("Creating statuses table");
|
||||||
s.executeUpdate(insertHashType(CREATE_STATUSES));
|
s.executeUpdate(insertTypeNames(CREATE_STATUSES));
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_MESSAGE);
|
s.executeUpdate(INDEX_STATUSES_BY_MESSAGE);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating contact transports table");
|
LOG.fine("Creating contact transports table");
|
||||||
s.executeUpdate(insertHashType(CREATE_CONTACT_TRANSPORTS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS));
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating local transports table");
|
LOG.fine("Creating local transports table");
|
||||||
s.executeUpdate(insertHashType(CREATE_LOCAL_TRANSPORTS));
|
s.executeUpdate(insertTypeNames(CREATE_LOCAL_TRANSPORTS));
|
||||||
s.close();
|
s.close();
|
||||||
} catch(SQLException e) {
|
} catch(SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -268,9 +257,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Get rid of this if we're definitely not using Derby
|
private String insertTypeNames(String s) {
|
||||||
private String insertHashType(String s) {
|
return s.replaceAll("XXXX", hashType).replaceAll("YYYY", bigIntType);
|
||||||
return s.replaceAll("XXXX", hashType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(Connection c) {
|
private void tryToClose(Connection c) {
|
||||||
@@ -371,8 +359,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO batchesToAck"
|
String sql = "INSERT INTO batchesToAck (batchId, contactId)"
|
||||||
+ " (batchId, contactId)"
|
|
||||||
+ " VALUES (?, ?)";
|
+ " VALUES (?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, b.getBytes());
|
ps.setBytes(1, b.getBytes());
|
||||||
@@ -405,30 +392,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Create a new contact row
|
// Create a new contact row
|
||||||
sql = "INSERT INTO contacts"
|
sql = "INSERT INTO contacts (contactId) VALUES (?)";
|
||||||
+ " (contactId, lastBundleReceived)"
|
|
||||||
+ " VALUES (?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setBytes(2, BundleId.NONE.getBytes());
|
|
||||||
int rowsAffected = ps.executeUpdate();
|
int rowsAffected = ps.executeUpdate();
|
||||||
assert rowsAffected == 1;
|
assert rowsAffected == 1;
|
||||||
ps.close();
|
ps.close();
|
||||||
// Create a dummy received bundle row for BundleId.NONE
|
|
||||||
sql = "INSERT INTO receivedBundles"
|
|
||||||
+ " (bundleId, contactId, timestamp)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, BundleId.NONE.getBytes());
|
|
||||||
ps.setInt(2, c.getInt());
|
|
||||||
ps.setLong(3, System.currentTimeMillis());
|
|
||||||
rowsAffected = ps.executeUpdate();
|
|
||||||
assert rowsAffected == 1;
|
|
||||||
ps.close();
|
|
||||||
// Store the contact's transport details
|
// Store the contact's transport details
|
||||||
if(transports != null) {
|
if(transports != null) {
|
||||||
sql = "INSERT INTO contactTransports"
|
sql = "INSERT INTO contactTransports (contactId, key, value)"
|
||||||
+ " (contactId, key, value)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -485,27 +457,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
// Find the ID of the last bundle received from c
|
|
||||||
String sql = "SELECT lastBundleReceived FROM contacts"
|
|
||||||
+ " WHERE contactId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean found = rs.next();
|
|
||||||
assert found;
|
|
||||||
byte[] lastBundleReceived = rs.getBytes(1);
|
|
||||||
boolean more = rs.next();
|
|
||||||
assert !more;
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
// Create an outstanding batch row
|
// Create an outstanding batch row
|
||||||
sql = "INSERT INTO outstandingBatches"
|
String sql = "INSERT INTO outstandingBatches"
|
||||||
+ " (batchId, contactId, lastBundleReceived)"
|
+ " (batchId, contactId, timestamp, passover)"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?, ZERO())";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, b.getBytes());
|
ps.setBytes(1, b.getBytes());
|
||||||
ps.setInt(2, c.getInt());
|
ps.setInt(2, c.getInt());
|
||||||
ps.setBytes(3, lastBundleReceived);
|
ps.setLong(3, System.currentTimeMillis());
|
||||||
int rowsAffected = ps.executeUpdate();
|
int rowsAffected = ps.executeUpdate();
|
||||||
assert rowsAffected == 1;
|
assert rowsAffected == 1;
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -551,98 +510,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<BatchId> addReceivedBundle(Connection txn, ContactId c,
|
|
||||||
BundleId b) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
// Update the ID of the last bundle received from c
|
|
||||||
String sql = "UPDATE contacts SET lastBundleReceived = ?"
|
|
||||||
+ " WHERE contactId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, b.getBytes());
|
|
||||||
ps.setInt(2, c.getInt());
|
|
||||||
int rowsAffected = ps.executeUpdate();
|
|
||||||
assert rowsAffected == 1;
|
|
||||||
ps.close();
|
|
||||||
// Count the received bundle records for c and find the oldest
|
|
||||||
sql = "SELECT bundleId, timestamp FROM receivedBundles"
|
|
||||||
+ " WHERE contactId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
int received = 0;
|
|
||||||
long oldestTimestamp = Long.MAX_VALUE;
|
|
||||||
byte[] oldestBundle = null;
|
|
||||||
while(rs.next()) {
|
|
||||||
received++;
|
|
||||||
byte[] bundle = rs.getBytes(1);
|
|
||||||
long timestamp = rs.getLong(2);
|
|
||||||
if(timestamp < oldestTimestamp) {
|
|
||||||
oldestTimestamp = timestamp;
|
|
||||||
oldestBundle = bundle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
Set<BatchId> lost;
|
|
||||||
if(received == RETRANSMIT_THRESHOLD) {
|
|
||||||
// Expire batches related to the oldest received bundle
|
|
||||||
assert oldestBundle != null;
|
|
||||||
lost = findLostBatches(txn, c, oldestBundle);
|
|
||||||
sql = "DELETE FROM receivedBundles WHERE bundleId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, oldestBundle);
|
|
||||||
rowsAffected = ps.executeUpdate();
|
|
||||||
assert rowsAffected == 1;
|
|
||||||
ps.close();
|
|
||||||
} else {
|
|
||||||
lost = Collections.emptySet();
|
|
||||||
}
|
|
||||||
// Record the new received bundle
|
|
||||||
sql = "INSERT INTO receivedBundles"
|
|
||||||
+ " (bundleId, contactId, timestamp)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, b.getBytes());
|
|
||||||
ps.setInt(2, c.getInt());
|
|
||||||
ps.setLong(3, System.currentTimeMillis());
|
|
||||||
rowsAffected = ps.executeUpdate();
|
|
||||||
assert rowsAffected == 1;
|
|
||||||
ps.close();
|
|
||||||
return lost;
|
|
||||||
} catch(SQLException e) {
|
|
||||||
tryToClose(rs);
|
|
||||||
tryToClose(ps);
|
|
||||||
tryToClose(txn);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<BatchId> findLostBatches(Connection txn, ContactId c,
|
|
||||||
byte[] lastBundleReceived) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT batchId FROM outstandingBatches"
|
|
||||||
+ " WHERE contactId = ? AND lastBundleReceived = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setBytes(2, lastBundleReceived);
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
Set<BatchId> lost = new HashSet<BatchId>();
|
|
||||||
while(rs.next()) lost.add(new BatchId(rs.getBytes(1)));
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return lost;
|
|
||||||
} catch(SQLException e) {
|
|
||||||
tryToClose(rs);
|
|
||||||
tryToClose(ps);
|
|
||||||
tryToClose(txn);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSubscription(Connection txn, GroupId g) throws DbException {
|
public void addSubscription(Connection txn, GroupId g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
@@ -829,6 +696,30 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<BatchId> getLostBatches(Connection txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT batchId FROM outstandingBatches"
|
||||||
|
+ " WHERE contactId = ? AND passover >= ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, RETRANSMIT_THRESHOLD);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
Set<BatchId> ids = new HashSet<BatchId>();
|
||||||
|
while(rs.next()) ids.add(new BatchId(rs.getBytes(1)));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return ids;
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
tryToClose(txn);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Message getMessage(Connection txn, MessageId m) throws DbException {
|
public Message getMessage(Connection txn, MessageId m) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -1158,6 +1049,38 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
public void removeAckedBatch(Connection txn, ContactId c, BatchId b)
|
public void removeAckedBatch(Connection txn, ContactId c, BatchId b)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
|
// Increment the passover count of all older outstanding batches
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT timestamp FROM outstandingBatches"
|
||||||
|
+ " WHERE contactId = ? AND batchId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setBytes(2, b.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
boolean found = rs.next();
|
||||||
|
assert found;
|
||||||
|
long timestamp = rs.getLong(1);
|
||||||
|
boolean more = rs.next();
|
||||||
|
assert !more;
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
sql = "UPDATE outstandingBatches SET passover = passover + ?"
|
||||||
|
+ " WHERE contactId = ? AND timestamp < ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, 1);
|
||||||
|
ps.setInt(2, c.getInt());
|
||||||
|
ps.setLong(3, timestamp);
|
||||||
|
int rowsAffected = ps.executeUpdate();
|
||||||
|
assert rowsAffected >= 0;
|
||||||
|
ps.close();
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
tryToClose(txn);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
removeBatch(txn, c, b, Status.SEEN);
|
removeBatch(txn, c, b, Status.SEEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import net.sf.briar.api.db.NoSuchContactException;
|
|||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.Batch;
|
import net.sf.briar.api.protocol.Batch;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.BundleReader;
|
import net.sf.briar.api.protocol.BundleReader;
|
||||||
import net.sf.briar.api.protocol.BundleWriter;
|
import net.sf.briar.api.protocol.BundleWriter;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
@@ -525,7 +524,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Received " + batches + " batches");
|
LOG.fine("Received " + batches + " batches");
|
||||||
b.finish();
|
b.finish();
|
||||||
retransmitLostBatches(c, h.getId());
|
findLostBatches(c);
|
||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,8 +572,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retransmitLostBatches(ContactId c, BundleId b)
|
private void findLostBatches(ContactId c) throws DbException {
|
||||||
throws DbException {
|
|
||||||
// Find any lost batches that need to be retransmitted
|
// Find any lost batches that need to be retransmitted
|
||||||
Set<BatchId> lost;
|
Set<BatchId> lost;
|
||||||
contactLock.readLock().lock();
|
contactLock.readLock().lock();
|
||||||
@@ -586,7 +584,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
try {
|
try {
|
||||||
Txn txn = db.startTransaction();
|
Txn txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
lost = db.addReceivedBundle(txn, c, b);
|
lost = db.getLostBatches(txn, c);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
db.abortTransaction(txn);
|
db.abortTransaction(txn);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import net.sf.briar.api.db.NoSuchContactException;
|
|||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.Batch;
|
import net.sf.briar.api.protocol.Batch;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.BundleReader;
|
import net.sf.briar.api.protocol.BundleReader;
|
||||||
import net.sf.briar.api.protocol.BundleWriter;
|
import net.sf.briar.api.protocol.BundleWriter;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
@@ -396,7 +395,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Received " + batches + " batches");
|
LOG.fine("Received " + batches + " batches");
|
||||||
b.finish();
|
b.finish();
|
||||||
retransmitLostBatches(c, h.getId());
|
findLostBatches(c);
|
||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +431,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retransmitLostBatches(ContactId c, BundleId b)
|
private void findLostBatches(ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
// Find any lost batches that need to be retransmitted
|
// Find any lost batches that need to be retransmitted
|
||||||
Set<BatchId> lost;
|
Set<BatchId> lost;
|
||||||
@@ -442,7 +441,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
synchronized(messageStatusLock) {
|
synchronized(messageStatusLock) {
|
||||||
Txn txn = db.startTransaction();
|
Txn txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
lost = db.addReceivedBundle(txn, c, b);
|
lost = db.getLostBatches(txn, c);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
db.abortTransaction(txn);
|
db.abortTransaction(txn);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import java.util.Set;
|
|||||||
|
|
||||||
import net.sf.briar.api.protocol.Batch;
|
import net.sf.briar.api.protocol.Batch;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.BundleReader;
|
import net.sf.briar.api.protocol.BundleReader;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Header;
|
import net.sf.briar.api.protocol.Header;
|
||||||
@@ -66,7 +65,6 @@ class BundleReaderImpl implements BundleReader {
|
|||||||
signature.initVerify(publicKey);
|
signature.initVerify(publicKey);
|
||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
// Read the signed data
|
// Read the signed data
|
||||||
in.setDigesting(true);
|
|
||||||
in.setSigning(true);
|
in.setSigning(true);
|
||||||
r.setReadLimit(Header.MAX_SIZE);
|
r.setReadLimit(Header.MAX_SIZE);
|
||||||
Set<BatchId> acks = new HashSet<BatchId>();
|
Set<BatchId> acks = new HashSet<BatchId>();
|
||||||
@@ -86,11 +84,9 @@ class BundleReaderImpl implements BundleReader {
|
|||||||
in.setSigning(false);
|
in.setSigning(false);
|
||||||
// Read and verify the signature
|
// Read and verify the signature
|
||||||
byte[] sig = r.readRaw();
|
byte[] sig = r.readRaw();
|
||||||
in.setDigesting(false);
|
|
||||||
if(!signature.verify(sig)) throw new SignatureException();
|
if(!signature.verify(sig)) throw new SignatureException();
|
||||||
// Build and return the header
|
// Build and return the header
|
||||||
BundleId id = new BundleId(messageDigest.digest());
|
return headerFactory.createHeader(acks, subs, transports);
|
||||||
return headerFactory.createHeader(id, acks, subs, transports);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Batch getNextBatch() throws IOException, GeneralSecurityException {
|
public Batch getNextBatch() throws IOException, GeneralSecurityException {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package net.sf.briar.protocol;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.DigestOutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
@@ -10,7 +9,6 @@ import java.security.Signature;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.BundleWriter;
|
import net.sf.briar.api.protocol.BundleWriter;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
@@ -22,7 +20,7 @@ class BundleWriterImpl implements BundleWriter {
|
|||||||
|
|
||||||
private static enum State { START, FIRST_BATCH, MORE_BATCHES, END };
|
private static enum State { START, FIRST_BATCH, MORE_BATCHES, END };
|
||||||
|
|
||||||
private final SigningOutputStream out;
|
private final SigningDigestingOutputStream out;
|
||||||
private final Writer w;
|
private final Writer w;
|
||||||
private final PrivateKey privateKey;
|
private final PrivateKey privateKey;
|
||||||
private final Signature signature;
|
private final Signature signature;
|
||||||
@@ -33,8 +31,8 @@ class BundleWriterImpl implements BundleWriter {
|
|||||||
BundleWriterImpl(OutputStream out, WriterFactory writerFactory,
|
BundleWriterImpl(OutputStream out, WriterFactory writerFactory,
|
||||||
PrivateKey privateKey, Signature signature,
|
PrivateKey privateKey, Signature signature,
|
||||||
MessageDigest messageDigest, long capacity) {
|
MessageDigest messageDigest, long capacity) {
|
||||||
OutputStream out1 = new DigestOutputStream(out, messageDigest);
|
this.out =
|
||||||
this.out = new SigningOutputStream(out1, signature);
|
new SigningDigestingOutputStream(out, signature, messageDigest);
|
||||||
w = writerFactory.createWriter(this.out);
|
w = writerFactory.createWriter(this.out);
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
@@ -46,13 +44,12 @@ class BundleWriterImpl implements BundleWriter {
|
|||||||
return capacity - w.getRawBytesWritten();
|
return capacity - w.getRawBytesWritten();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BundleId addHeader(Iterable<BatchId> acks, Iterable<GroupId> subs,
|
public void addHeader(Iterable<BatchId> acks, Iterable<GroupId> subs,
|
||||||
Map<String, String> transports) throws IOException,
|
Map<String, String> transports) throws IOException,
|
||||||
GeneralSecurityException {
|
GeneralSecurityException {
|
||||||
if(state != State.START) throw new IllegalStateException();
|
if(state != State.START) throw new IllegalStateException();
|
||||||
// Initialise the output stream
|
// Initialise the output stream
|
||||||
signature.initSign(privateKey);
|
signature.initSign(privateKey);
|
||||||
messageDigest.reset();
|
|
||||||
// Write the data to be signed
|
// Write the data to be signed
|
||||||
out.setSigning(true);
|
out.setSigning(true);
|
||||||
w.writeListStart();
|
w.writeListStart();
|
||||||
@@ -66,9 +63,8 @@ class BundleWriterImpl implements BundleWriter {
|
|||||||
// Create and write the signature
|
// Create and write the signature
|
||||||
byte[] sig = signature.sign();
|
byte[] sig = signature.sign();
|
||||||
w.writeRaw(sig);
|
w.writeRaw(sig);
|
||||||
// Calculate and return the ID
|
// Expect a (possibly empty) list of batches
|
||||||
state = State.FIRST_BATCH;
|
state = State.FIRST_BATCH;
|
||||||
return new BundleId(messageDigest.digest());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BatchId addBatch(Iterable<Message> messages) throws IOException,
|
public BatchId addBatch(Iterable<Message> messages) throws IOException,
|
||||||
@@ -82,6 +78,7 @@ class BundleWriterImpl implements BundleWriter {
|
|||||||
signature.initSign(privateKey);
|
signature.initSign(privateKey);
|
||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
// Write the data to be signed
|
// Write the data to be signed
|
||||||
|
out.setDigesting(true);
|
||||||
out.setSigning(true);
|
out.setSigning(true);
|
||||||
w.writeListStart();
|
w.writeListStart();
|
||||||
for(Message m : messages) w.writeRaw(m);
|
for(Message m : messages) w.writeRaw(m);
|
||||||
@@ -90,6 +87,7 @@ class BundleWriterImpl implements BundleWriter {
|
|||||||
// Create and write the signature
|
// Create and write the signature
|
||||||
byte[] sig = signature.sign();
|
byte[] sig = signature.sign();
|
||||||
w.writeRaw(sig);
|
w.writeRaw(sig);
|
||||||
|
out.setDigesting(false);
|
||||||
// Calculate and return the ID
|
// Calculate and return the ID
|
||||||
return new BatchId(messageDigest.digest());
|
return new BatchId(messageDigest.digest());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Header;
|
import net.sf.briar.api.protocol.Header;
|
||||||
|
|
||||||
interface HeaderFactory {
|
interface HeaderFactory {
|
||||||
|
|
||||||
Header createHeader(BundleId id, Set<BatchId> acks, Set<GroupId> subs,
|
Header createHeader(Set<BatchId> acks, Set<GroupId> subs,
|
||||||
Map<String, String> transports);
|
Map<String, String> transports);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Header;
|
import net.sf.briar.api.protocol.Header;
|
||||||
|
|
||||||
class HeaderFactoryImpl implements HeaderFactory {
|
class HeaderFactoryImpl implements HeaderFactory {
|
||||||
|
|
||||||
public Header createHeader(BundleId id, Set<BatchId> acks,
|
public Header createHeader(Set<BatchId> acks, Set<GroupId> subs,
|
||||||
Set<GroupId> subs, Map<String, String> transports) {
|
Map<String, String> transports) {
|
||||||
return new HeaderImpl(id, acks, subs, transports);
|
return new HeaderImpl(acks, subs, transports);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,30 +4,23 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Header;
|
import net.sf.briar.api.protocol.Header;
|
||||||
|
|
||||||
/** A simple in-memory implementation of a header. */
|
/** A simple in-memory implementation of a header. */
|
||||||
class HeaderImpl implements Header {
|
class HeaderImpl implements Header {
|
||||||
|
|
||||||
private final BundleId id;
|
|
||||||
private final Set<BatchId> acks;
|
private final Set<BatchId> acks;
|
||||||
private final Set<GroupId> subs;
|
private final Set<GroupId> subs;
|
||||||
private final Map<String, String> transports;
|
private final Map<String, String> transports;
|
||||||
|
|
||||||
HeaderImpl(BundleId id, Set<BatchId> acks, Set<GroupId> subs,
|
HeaderImpl(Set<BatchId> acks, Set<GroupId> subs,
|
||||||
Map<String, String> transports) {
|
Map<String, String> transports) {
|
||||||
this.id = id;
|
|
||||||
this.acks = acks;
|
this.acks = acks;
|
||||||
this.subs = subs;
|
this.subs = subs;
|
||||||
this.transports = transports;
|
this.transports = transports;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BundleId getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<BatchId> getAcks() {
|
public Set<BatchId> getAcks() {
|
||||||
return acks;
|
return acks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,35 @@ package net.sf.briar.protocol;
|
|||||||
import java.io.FilterOutputStream;
|
import java.io.FilterOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
|
|
||||||
/** An output stream that passes its output through a signature. */
|
/**
|
||||||
class SigningOutputStream extends FilterOutputStream {
|
* An output stream that passes its output through a signature and a message
|
||||||
|
* digest.
|
||||||
|
*/
|
||||||
|
class SigningDigestingOutputStream extends FilterOutputStream {
|
||||||
|
|
||||||
private final Signature signature;
|
private final Signature signature;
|
||||||
private boolean signing = false;
|
private final MessageDigest messageDigest;
|
||||||
|
private boolean signing = false, digesting = false;
|
||||||
|
|
||||||
public SigningOutputStream(OutputStream out, Signature signature) {
|
public SigningDigestingOutputStream(OutputStream out, Signature signature,
|
||||||
|
MessageDigest messageDigest) {
|
||||||
super(out);
|
super(out);
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
|
this.messageDigest = messageDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSigning(boolean signing) {
|
void setSigning(boolean signing) {
|
||||||
this.signing = signing;
|
this.signing = signing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDigesting(boolean digesting) {
|
||||||
|
this.digesting = digesting;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] b) throws IOException {
|
public void write(byte[] b) throws IOException {
|
||||||
write(b, 0, b.length);
|
write(b, 0, b.length);
|
||||||
@@ -36,6 +47,7 @@ class SigningOutputStream extends FilterOutputStream {
|
|||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(digesting) messageDigest.update(b, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,5 +60,6 @@ class SigningOutputStream extends FilterOutputStream {
|
|||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(digesting) messageDigest.update((byte) b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,6 @@ import net.sf.briar.api.db.Status;
|
|||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.Batch;
|
import net.sf.briar.api.protocol.Batch;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.BundleReader;
|
import net.sf.briar.api.protocol.BundleReader;
|
||||||
import net.sf.briar.api.protocol.BundleWriter;
|
import net.sf.briar.api.protocol.BundleWriter;
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
@@ -33,7 +32,6 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
protected final Object txn = new Object();
|
protected final Object txn = new Object();
|
||||||
protected final AuthorId authorId;
|
protected final AuthorId authorId;
|
||||||
protected final BatchId batchId;
|
protected final BatchId batchId;
|
||||||
protected final BundleId bundleId;
|
|
||||||
protected final ContactId contactId;
|
protected final ContactId contactId;
|
||||||
protected final GroupId groupId;
|
protected final GroupId groupId;
|
||||||
protected final MessageId messageId, parentId;
|
protected final MessageId messageId, parentId;
|
||||||
@@ -51,7 +49,6 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
super();
|
super();
|
||||||
authorId = new AuthorId(TestUtils.getRandomId());
|
authorId = new AuthorId(TestUtils.getRandomId());
|
||||||
batchId = new BatchId(TestUtils.getRandomId());
|
batchId = new BatchId(TestUtils.getRandomId());
|
||||||
bundleId = new BundleId(TestUtils.getRandomId());
|
|
||||||
contactId = new ContactId(123);
|
contactId = new ContactId(123);
|
||||||
groupId = new GroupId(TestUtils.getRandomId());
|
groupId = new GroupId(TestUtils.getRandomId());
|
||||||
messageId = new MessageId(TestUtils.getRandomId());
|
messageId = new MessageId(TestUtils.getRandomId());
|
||||||
@@ -478,7 +475,6 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
will(returnValue(transports));
|
will(returnValue(transports));
|
||||||
// Build the header
|
// Build the header
|
||||||
oneOf(bundleWriter).addHeader(acks, subs, transports);
|
oneOf(bundleWriter).addHeader(acks, subs, transports);
|
||||||
will(returnValue(bundleId));
|
|
||||||
// Add a batch to the bundle
|
// Add a batch to the bundle
|
||||||
oneOf(bundleWriter).getRemainingCapacity();
|
oneOf(bundleWriter).getRemainingCapacity();
|
||||||
will(returnValue(1024L * 1024L - headerSize));
|
will(returnValue(1024L * 1024L - headerSize));
|
||||||
@@ -579,9 +575,7 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
oneOf(bundleReader).finish();
|
oneOf(bundleReader).finish();
|
||||||
// Lost batches
|
// Lost batches
|
||||||
oneOf(header).getId();
|
oneOf(database).getLostBatches(txn, contactId);
|
||||||
will(returnValue(bundleId));
|
|
||||||
oneOf(database).addReceivedBundle(txn, contactId, bundleId);
|
|
||||||
will(returnValue(Collections.singleton(batchId)));
|
will(returnValue(Collections.singleton(batchId)));
|
||||||
oneOf(database).removeLostBatch(txn, contactId, batchId);
|
oneOf(database).removeLostBatch(txn, contactId, batchId);
|
||||||
}});
|
}});
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import net.sf.briar.api.db.DbException;
|
|||||||
import net.sf.briar.api.db.Status;
|
import net.sf.briar.api.db.Status;
|
||||||
import net.sf.briar.api.protocol.AuthorId;
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
import net.sf.briar.api.protocol.BatchId;
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
import net.sf.briar.api.protocol.BundleId;
|
|
||||||
import net.sf.briar.api.protocol.GroupId;
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageFactory;
|
import net.sf.briar.api.protocol.MessageFactory;
|
||||||
@@ -489,13 +488,10 @@ public class H2DatabaseTest extends TestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRetransmission() throws DbException {
|
public void testRetransmission() throws DbException {
|
||||||
BundleId bundleId = new BundleId(TestUtils.getRandomId());
|
BatchId[] ids = new BatchId[Database.RETRANSMIT_THRESHOLD + 5];
|
||||||
BundleId bundleId1 = new BundleId(TestUtils.getRandomId());
|
for(int i = 0; i < ids.length; i++) {
|
||||||
BundleId bundleId2 = new BundleId(TestUtils.getRandomId());
|
ids[i] = new BatchId(TestUtils.getRandomId());
|
||||||
BundleId bundleId3 = new BundleId(TestUtils.getRandomId());
|
}
|
||||||
BundleId bundleId4 = new BundleId(TestUtils.getRandomId());
|
|
||||||
BatchId batchId1 = new BatchId(TestUtils.getRandomId());
|
|
||||||
BatchId batchId2 = new BatchId(TestUtils.getRandomId());
|
|
||||||
Set<MessageId> empty = Collections.emptySet();
|
Set<MessageId> empty = Collections.emptySet();
|
||||||
Mockery context = new Mockery();
|
Mockery context = new Mockery();
|
||||||
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
@@ -504,30 +500,62 @@ public class H2DatabaseTest extends TestCase {
|
|||||||
// Add a contact
|
// Add a contact
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
assertEquals(contactId, db.addContact(txn, null));
|
assertEquals(contactId, db.addContact(txn, null));
|
||||||
// Add an oustanding batch (associated with BundleId.NONE)
|
// Add some outstanding batches, a few ms apart
|
||||||
db.addOutstandingBatch(txn, contactId, batchId, empty);
|
for(int i = 0; i < ids.length; i++) {
|
||||||
// Receive a bundle
|
db.addOutstandingBatch(txn, contactId, ids[i], empty);
|
||||||
Set<BatchId> lost = db.addReceivedBundle(txn, contactId, bundleId);
|
try {
|
||||||
assertTrue(lost.isEmpty());
|
Thread.sleep(5);
|
||||||
// Add a couple more outstanding batches (associated with bundleId)
|
} catch(InterruptedException ignored) {}
|
||||||
db.addOutstandingBatch(txn, contactId, batchId1, empty);
|
}
|
||||||
db.addOutstandingBatch(txn, contactId, batchId2, empty);
|
// The contact acks the batches in reverse order. The first
|
||||||
// Receive another bundle
|
// RETRANSMIT_THRESHOLD - 1 acks should not trigger any retransmissions
|
||||||
lost = db.addReceivedBundle(txn, contactId, bundleId1);
|
for(int i = 0; i < Database.RETRANSMIT_THRESHOLD - 1; i++) {
|
||||||
assertTrue(lost.isEmpty());
|
db.removeAckedBatch(txn, contactId, ids[ids.length - i - 1]);
|
||||||
// The contact acks one of the batches - it should not be retransmitted
|
Set<BatchId> lost = db.getLostBatches(txn, contactId);
|
||||||
db.removeAckedBatch(txn, contactId, batchId1);
|
assertEquals(Collections.emptySet(), lost);
|
||||||
// Receive another bundle - batchId should now be considered lost
|
}
|
||||||
lost = db.addReceivedBundle(txn, contactId, bundleId2);
|
// The next ack should trigger the retransmission of the remaining
|
||||||
assertEquals(1, lost.size());
|
// five outstanding batches
|
||||||
assertTrue(lost.contains(batchId));
|
int index = ids.length - Database.RETRANSMIT_THRESHOLD;
|
||||||
// Receive another bundle - batchId2 should now be considered lost
|
db.removeAckedBatch(txn, contactId, ids[index]);
|
||||||
lost = db.addReceivedBundle(txn, contactId, bundleId3);
|
Set<BatchId> lost = db.getLostBatches(txn, contactId);
|
||||||
assertEquals(1, lost.size());
|
for(int i = 0; i < index; i++) {
|
||||||
assertTrue(lost.contains(batchId2));
|
assertTrue(lost.contains(ids[i]));
|
||||||
// Receive another bundle - no further losses
|
}
|
||||||
lost = db.addReceivedBundle(txn, contactId, bundleId4);
|
db.commitTransaction(txn);
|
||||||
assertTrue(lost.isEmpty());
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoRetransmission() throws DbException {
|
||||||
|
BatchId[] ids = new BatchId[Database.RETRANSMIT_THRESHOLD * 2];
|
||||||
|
for(int i = 0; i < ids.length; i++) {
|
||||||
|
ids[i] = new BatchId(TestUtils.getRandomId());
|
||||||
|
}
|
||||||
|
Set<MessageId> empty = Collections.emptySet();
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
|
||||||
|
// Add a contact
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
assertEquals(contactId, db.addContact(txn, null));
|
||||||
|
// Add some outstanding batches, a few ms apart
|
||||||
|
for(int i = 0; i < ids.length; i++) {
|
||||||
|
db.addOutstandingBatch(txn, contactId, ids[i], empty);
|
||||||
|
try {
|
||||||
|
Thread.sleep(5);
|
||||||
|
} catch(InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
// The contact acks the batches in the order they were sent - nothing
|
||||||
|
// should be retransmitted
|
||||||
|
for(int i = 0; i < ids.length; i++) {
|
||||||
|
db.removeAckedBatch(txn, contactId, ids[i]);
|
||||||
|
Set<BatchId> lost = db.getLostBatches(txn, contactId);
|
||||||
|
assertEquals(Collections.emptySet(), lost);
|
||||||
|
}
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package net.sf.briar.protocol;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.security.DigestOutputStream;
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
@@ -39,7 +38,8 @@ public class SigningStreamTest extends TestCase {
|
|||||||
random.nextBytes(input);
|
random.nextBytes(input);
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
SigningOutputStream signOut = new SigningOutputStream(out, sig);
|
SigningDigestingOutputStream signOut =
|
||||||
|
new SigningDigestingOutputStream(out, sig, dig);
|
||||||
sig.initSign(keyPair.getPrivate());
|
sig.initSign(keyPair.getPrivate());
|
||||||
|
|
||||||
signOut.setSigning(true);
|
signOut.setSigning(true);
|
||||||
@@ -80,7 +80,8 @@ public class SigningStreamTest extends TestCase {
|
|||||||
random.nextBytes(input);
|
random.nextBytes(input);
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
SigningOutputStream signOut = new SigningOutputStream(out, sig);
|
SigningDigestingOutputStream signOut =
|
||||||
|
new SigningDigestingOutputStream(out, sig, dig);
|
||||||
sig.initSign(keyPair.getPrivate());
|
sig.initSign(keyPair.getPrivate());
|
||||||
|
|
||||||
// Sign bytes 0-499, skip bytes 500-749, sign bytes 750-999
|
// Sign bytes 0-499, skip bytes 500-749, sign bytes 750-999
|
||||||
@@ -121,16 +122,17 @@ public class SigningStreamTest extends TestCase {
|
|||||||
random.nextBytes(input);
|
random.nextBytes(input);
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
DigestOutputStream digOut = new DigestOutputStream(out, dig);
|
SigningDigestingOutputStream signOut =
|
||||||
|
new SigningDigestingOutputStream(out, sig, dig);
|
||||||
dig.reset();
|
dig.reset();
|
||||||
|
|
||||||
// Digest bytes 0-499, skip bytes 500-749, digest bytes 750-999
|
// Digest bytes 0-499, skip bytes 500-749, digest bytes 750-999
|
||||||
digOut.on(true);
|
signOut.setDigesting(true);
|
||||||
digOut.write(input, 0, 500);
|
signOut.write(input, 0, 500);
|
||||||
digOut.on(false);
|
signOut.setDigesting(false);
|
||||||
digOut.write(input, 500, 250);
|
signOut.write(input, 500, 250);
|
||||||
digOut.on(true);
|
signOut.setDigesting(true);
|
||||||
digOut.write(input, 750, 250);
|
signOut.write(input, 750, 250);
|
||||||
|
|
||||||
byte[] hash = dig.digest();
|
byte[] hash = dig.digest();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user