Refactor ValidationManager and fix some bugs. #619

This commit is contained in:
akwizgran
2016-09-08 14:57:41 +01:00
parent fd4275733f
commit 8a3e5bfb50
34 changed files with 978 additions and 922 deletions

View File

@@ -18,6 +18,7 @@ import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.MessageStatus;
import org.briarproject.api.sync.ValidationManager.State;
import org.briarproject.api.transport.TransportKeys;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
@@ -83,10 +84,10 @@ interface Database<T> {
throws DbException;
/**
* Adds a dependency between two MessageIds
* Adds a dependency between two messages in the given group.
*/
void addMessageDependency(T txn, MessageId dependentId,
MessageId dependencyId) throws DbException;
void addMessageDependency(T txn, GroupId g, MessageId dependent,
MessageId dependency) throws DbException;
/**
* Records that a message has been offered by the given contact.
@@ -281,11 +282,13 @@ interface Database<T> {
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
/**
* Returns the dependencies of the given message.
* This method makes sure that dependencies in different groups
* are returned as {@link ValidationManager.State.INVALID}. Note that this
* is not set on the dependencies themselves; the returned states should
* only be taken in the context of the given message.
* Returns the IDs and states of all dependencies of the given message.
* Missing dependencies have the state {@link
* org.briarproject.api.sync.ValidationManager.State UNKNOWN}.
* Dependencies in other groups have the state {@link
* org.briarproject.api.sync.ValidationManager.State INVALID}.
* Note that these states are not set on the dependencies themselves; the
* returned states should only be taken in the context of the given message.
* <p/>
* Read-only.
*/
@@ -293,7 +296,9 @@ interface Database<T> {
throws DbException;
/**
* Returns all IDs of messages that depend on the given message.
* Returns all IDs and states of all dependents of the given message.
* Messages in other groups that declare a dependency on the given message
* will be returned even though such dependencies are invalid.
* <p/>
* Read-only.
*/
@@ -350,6 +355,13 @@ interface Database<T> {
*/
Metadata getMessageMetadata(T txn, MessageId m) throws DbException;
/**
* Returns the validation and delivery state of the given message.
* <p/>
* Read-only.
*/
State getMessageState(T txn, MessageId m) throws DbException;
/**
* Returns the status of all messages in the given group with respect
* to the given contact.
@@ -377,15 +389,6 @@ interface Database<T> {
Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
throws DbException;
/**
* Returns the IDs of any messages that need to be delivered to the given
* client.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToDeliver(T txn, ClientId c)
throws DbException;
/**
* Returns the IDs of some messages that are eligible to be offered to the
* given contact, up to the given number of messages.
@@ -437,6 +440,7 @@ interface Database<T> {
* <p/>
* Read-only.
*/
@Nullable
byte[] getRawMessage(T txn, MessageId m) throws DbException;
/**
@@ -592,7 +596,7 @@ interface Database<T> {
* Marks the given contact as active or inactive.
*/
void setContactActive(T txn, ContactId c, boolean active)
throws DbException;
throws DbException;
/**
* Marks the given message as shared or unshared.
@@ -601,10 +605,9 @@ interface Database<T> {
throws DbException;
/**
* Marks the given message as valid or invalid.
* Sets the validation and delivery state of the given message.
*/
void setMessageState(T txn, MessageId m, State state)
throws DbException;
void setMessageState(T txn, MessageId m, State state) throws DbException;
/**
* Sets the reordering window for the given contact and transport in the

View File

@@ -28,10 +28,10 @@ import org.briarproject.api.event.LocalAuthorRemovedEvent;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageRequestedEvent;
import org.briarproject.api.event.MessageSharedEvent;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.event.MessageToAckEvent;
import org.briarproject.api.event.MessageToRequestEvent;
import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.event.MessagesSentEvent;
import org.briarproject.api.event.SettingsUpdatedEvent;
import org.briarproject.api.identity.Author;
@@ -50,6 +50,7 @@ import org.briarproject.api.sync.Offer;
import org.briarproject.api.sync.Request;
import org.briarproject.api.sync.ValidationManager.State;
import org.briarproject.api.transport.TransportKeys;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -188,7 +189,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
}
public void addLocalMessage(Transaction transaction, Message m, ClientId c,
public void addLocalMessage(Transaction transaction, Message m,
Metadata meta, boolean shared) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
@@ -197,7 +198,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsMessage(txn, m.getId())) {
addMessage(txn, m, DELIVERED, shared);
transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m, c, true,
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED));
if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
}
@@ -271,6 +272,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.deleteMessageMetadata(txn, m);
}
@Nullable
public Ack generateAck(Transaction transaction, ContactId c,
int maxMessages) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -283,6 +285,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return new Ack(ids);
}
@Nullable
public Collection<byte[]> generateBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -301,6 +304,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return Collections.unmodifiableList(messages);
}
@Nullable
public Offer generateOffer(Transaction transaction, ContactId c,
int maxMessages, int maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -313,6 +317,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return new Offer(ids);
}
@Nullable
public Request generateRequest(Transaction transaction, ContactId c,
int maxMessages) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -326,6 +331,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return new Request(ids);
}
@Nullable
public Collection<byte[]> generateRequestedBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -420,18 +426,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessagesToValidate(txn, c);
}
public Collection<MessageId> getMessagesToDeliver(Transaction transaction,
ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToDeliver(txn, c);
}
public Collection<MessageId> getPendingMessages(Transaction transaction,
ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getPendingMessages(txn, c);
}
@Nullable
public byte[] getRawMessage(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
@@ -473,6 +474,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageMetadataForValidator(txn, m);
}
public State getMessageState(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return db.getMessageState(txn, m);
}
public Collection<MessageStatus> getMessageStatus(Transaction transaction,
ContactId c, GroupId g) throws DbException {
T txn = unbox(transaction);
@@ -720,14 +729,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (shared) transaction.attach(new MessageSharedEvent(m));
}
public void setMessageState(Transaction transaction, Message m, ClientId c,
public void setMessageState(Transaction transaction, MessageId m,
State state) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m.getId()))
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.setMessageState(txn, m.getId(), state);
transaction.attach(new MessageStateChangedEvent(m, c, false, state));
db.setMessageState(txn, m, state);
transaction.attach(new MessageStateChangedEvent(m, false, state));
}
public void addMessageDependencies(Transaction transaction,
@@ -737,8 +746,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction);
if (!db.containsMessage(txn, dependent.getId()))
throw new NoSuchMessageException();
for (MessageId dependencyId : dependencies) {
db.addMessageDependency(txn, dependent.getId(), dependencyId);
for (MessageId dependency : dependencies) {
db.addMessageDependency(txn, dependent.getGroupId(),
dependent.getId(), dependency);
}
}

View File

@@ -25,6 +25,7 @@ import org.briarproject.api.transport.IncomingKeys;
import org.briarproject.api.transport.OutgoingKeys;
import org.briarproject.api.transport.TransportKeys;
import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.Nullable;
import java.security.SecureRandom;
import java.sql.Connection;
@@ -53,7 +54,6 @@ import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.api.sync.ValidationManager.State.VALID;
import static org.briarproject.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY;
import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE;
@@ -67,8 +67,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
*/
abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 26;
private static final int MIN_SCHEMA_VERSION = 26;
private static final int SCHEMA_VERSION = 27;
private static final int MIN_SCHEMA_VERSION = 27;
private static final String CREATE_SETTINGS =
"CREATE TABLE settings"
@@ -155,8 +155,12 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_MESSAGE_DEPENDENCIES =
"CREATE TABLE messageDependencies"
+ " (messageId HASH NOT NULL,"
+ " (groupId HASH NOT NULL,"
+ " messageId HASH NOT NULL,"
+ " dependencyId HASH NOT NULL," // Not a foreign key
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)"
+ " ON DELETE CASCADE)";
@@ -609,16 +613,17 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void addMessageDependency(Connection txn, MessageId dependentId,
MessageId dependencyId) throws DbException {
public void addMessageDependency(Connection txn, GroupId g,
MessageId dependent, MessageId dependency) throws DbException {
PreparedStatement ps = null;
try {
String sql =
"INSERT INTO messageDependencies (messageId, dependencyId)"
+ " VALUES (?, ?)";
String sql = "INSERT INTO messageDependencies"
+ " (groupId, messageId, dependencyId)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, dependentId.getBytes());
ps.setBytes(2, dependencyId.getBytes());
ps.setBytes(1, g.getBytes());
ps.setBytes(2, dependent.getBytes());
ps.setBytes(3, dependency.getBytes());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
@@ -1453,7 +1458,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT d.dependencyId, m.state, m.groupId"
String sql = "SELECT d.dependencyId, m.state, d.groupId, m.groupId"
+ " FROM messageDependencies AS d"
+ " LEFT OUTER JOIN messages AS m"
+ " ON d.dependencyId = m.messageId"
@@ -1463,13 +1468,17 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery();
Map<MessageId, State> dependencies = new HashMap<MessageId, State>();
while (rs.next()) {
MessageId messageId = new MessageId(rs.getBytes(1));
MessageId dependency = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2));
if (state != UNKNOWN) {
// set dependency invalid if it is in a different group
if (!hasGroupId(txn, m, rs.getBytes(3))) state = INVALID;
if (rs.wasNull()) {
state = UNKNOWN; // Missing dependency
} else {
GroupId dependentGroupId = new GroupId(rs.getBytes(3));
GroupId dependencyGroupId = new GroupId(rs.getBytes(4));
if (!dependentGroupId.equals(dependencyGroupId))
state = INVALID; // Dependency in another group
}
dependencies.put(messageId, state);
dependencies.put(dependency, state);
}
rs.close();
ps.close();
@@ -1481,29 +1490,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
private boolean hasGroupId(Connection txn, MessageId m, byte[] g)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM messages"
+ " WHERE messageId = ? AND groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setBytes(2, g);
rs = ps.executeQuery();
boolean same = rs.next();
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return same;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Map<MessageId, State> getMessageDependents(Connection txn,
MessageId m) throws DbException {
PreparedStatement ps = null;
@@ -1511,7 +1497,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
String sql = "SELECT d.messageId, m.state"
+ " FROM messageDependencies AS d"
+ " LEFT OUTER JOIN messages AS m"
+ " JOIN messages AS m"
+ " ON d.messageId = m.messageId"
+ " WHERE dependencyId = ?";
ps = txn.prepareStatement(sql);
@@ -1519,9 +1505,9 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery();
Map<MessageId, State> dependents = new HashMap<MessageId, State>();
while (rs.next()) {
MessageId messageId = new MessageId(rs.getBytes(1));
MessageId dependent = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2));
dependents.put(messageId, state);
dependents.put(dependent, state);
}
rs.close();
ps.close();
@@ -1533,6 +1519,28 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public State getMessageState(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT state FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
State state = State.fromValue(rs.getInt(1));
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return state;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c,
int maxMessages) throws DbException {
PreparedStatement ps = null;
@@ -1655,11 +1663,6 @@ abstract class JdbcDatabase implements Database<Connection> {
return getMessagesInState(txn, c, UNKNOWN);
}
public Collection<MessageId> getMessagesToDeliver(Connection txn,
ClientId c) throws DbException {
return getMessagesInState(txn, c, VALID);
}
public Collection<MessageId> getPendingMessages(Connection txn,
ClientId c) throws DbException {
return getMessagesInState(txn, c, PENDING);
@@ -1689,6 +1692,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Nullable
public byte[] getRawMessage(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;