Merge branch '756-avoid-lost-messages' into 'master'

Use new group visibility state to avoid lost messages

Depends on !410. Closes #756.

See merge request !411
This commit is contained in:
Torsten Grote
2016-11-16 16:22:30 +00:00
5 changed files with 90 additions and 38 deletions

View File

@@ -27,8 +27,8 @@ import static org.briarproject.api.sync.Group.Visibility.SHARED;
import static org.briarproject.privategroup.invitation.CreatorState.DISSOLVED; import static org.briarproject.privategroup.invitation.CreatorState.DISSOLVED;
import static org.briarproject.privategroup.invitation.CreatorState.ERROR; import static org.briarproject.privategroup.invitation.CreatorState.ERROR;
import static org.briarproject.privategroup.invitation.CreatorState.INVITED; import static org.briarproject.privategroup.invitation.CreatorState.INVITED;
import static org.briarproject.privategroup.invitation.CreatorState.INVITEE_JOINED; import static org.briarproject.privategroup.invitation.CreatorState.JOINED;
import static org.briarproject.privategroup.invitation.CreatorState.INVITEE_LEFT; import static org.briarproject.privategroup.invitation.CreatorState.LEFT;
import static org.briarproject.privategroup.invitation.CreatorState.START; import static org.briarproject.privategroup.invitation.CreatorState.START;
@Immutable @Immutable
@@ -55,8 +55,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
case START: case START:
return onLocalInvite(txn, s, message, timestamp, signature); return onLocalInvite(txn, s, message, timestamp, signature);
case INVITED: case INVITED:
case INVITEE_JOINED: case JOINED:
case INVITEE_LEFT: case LEFT:
case DISSOLVED: case DISSOLVED:
case ERROR: case ERROR:
throw new ProtocolStateException(); // Invalid in these states throw new ProtocolStateException(); // Invalid in these states
@@ -80,8 +80,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
case ERROR: case ERROR:
return s; // Ignored in these states return s; // Ignored in these states
case INVITED: case INVITED:
case INVITEE_JOINED: case JOINED:
case INVITEE_LEFT: case LEFT:
return onLocalLeave(txn, s); return onLocalLeave(txn, s);
default: default:
throw new AssertionError(); throw new AssertionError();
@@ -105,8 +105,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
JoinMessage m) throws DbException, FormatException { JoinMessage m) throws DbException, FormatException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case INVITEE_JOINED: case JOINED:
case INVITEE_LEFT: case LEFT:
return abort(txn, s); // Invalid in these states return abort(txn, s); // Invalid in these states
case INVITED: case INVITED:
return onRemoteAccept(txn, s, m); return onRemoteAccept(txn, s, m);
@@ -123,11 +123,11 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
LeaveMessage m) throws DbException, FormatException { LeaveMessage m) throws DbException, FormatException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case INVITEE_LEFT: case LEFT:
return abort(txn, s); // Invalid in these states return abort(txn, s); // Invalid in these states
case INVITED: case INVITED:
return onRemoteDecline(txn, s, m); return onRemoteDecline(txn, s, m);
case INVITEE_JOINED: case JOINED:
return onRemoteLeave(txn, s, m); return onRemoteLeave(txn, s, m);
case DISSOLVED: case DISSOLVED:
case ERROR: case ERROR:
@@ -180,6 +180,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId())) if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s);
// Send a JOIN message
Message sent = sendJoinMessage(txn, s, false);
// Mark the response visible in the UI // Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getId(), true); markMessageVisibleInUi(txn, m.getId(), true);
// Track the message // Track the message
@@ -191,10 +193,10 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
ContactId contactId = getContactId(txn, m.getContactGroupId()); ContactId contactId = getContactId(txn, m.getContactGroupId());
txn.attach(new GroupInvitationResponseReceivedEvent(contactId, txn.attach(new GroupInvitationResponseReceivedEvent(contactId,
createInvitationResponse(m, contactId, true))); createInvitationResponse(m, contactId, true)));
// Move to the INVITEE_JOINED state // Move to the JOINED state
return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(), return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(), sent.getId(), m.getId(), sent.getTimestamp(),
s.getInviteTimestamp(), INVITEE_JOINED); s.getInviteTimestamp(), JOINED);
} }
private CreatorSession onRemoteDecline(Transaction txn, CreatorSession s, private CreatorSession onRemoteDecline(Transaction txn, CreatorSession s,
@@ -228,10 +230,10 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
return abort(txn, s); return abort(txn, s);
// Make the private group invisible to the contact // Make the private group invisible to the contact
setPrivateGroupVisibility(txn, s, INVISIBLE); setPrivateGroupVisibility(txn, s, INVISIBLE);
// Move to the INVITEE_LEFT state // Move to the LEFT state
return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(), return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(), s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
s.getInviteTimestamp(), INVITEE_LEFT); s.getInviteTimestamp(), LEFT);
} }
private CreatorSession abort(Transaction txn, CreatorSession s) private CreatorSession abort(Transaction txn, CreatorSession s)

View File

@@ -4,8 +4,7 @@ import org.briarproject.api.FormatException;
enum CreatorState implements State { enum CreatorState implements State {
START(0), INVITED(1), INVITEE_JOINED(2), INVITEE_LEFT(3), DISSOLVED(4), START(0), INVITED(1), JOINED(2), LEFT(3), DISSOLVED(4), ERROR(5);
ERROR(5);
private final int value; private final int value;

View File

@@ -27,11 +27,13 @@ import javax.annotation.concurrent.Immutable;
import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.api.sync.Group.Visibility.SHARED;
import static org.briarproject.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.privategroup.invitation.InviteeState.ACCEPTED;
import static org.briarproject.privategroup.invitation.InviteeState.DISSOLVED; import static org.briarproject.privategroup.invitation.InviteeState.DISSOLVED;
import static org.briarproject.privategroup.invitation.InviteeState.ERROR; import static org.briarproject.privategroup.invitation.InviteeState.ERROR;
import static org.briarproject.privategroup.invitation.InviteeState.INVITED; import static org.briarproject.privategroup.invitation.InviteeState.INVITED;
import static org.briarproject.privategroup.invitation.InviteeState.INVITEE_JOINED; import static org.briarproject.privategroup.invitation.InviteeState.JOINED;
import static org.briarproject.privategroup.invitation.InviteeState.INVITEE_LEFT; import static org.briarproject.privategroup.invitation.InviteeState.LEFT;
import static org.briarproject.privategroup.invitation.InviteeState.START; import static org.briarproject.privategroup.invitation.InviteeState.START;
@Immutable @Immutable
@@ -62,8 +64,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
throws DbException { throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case INVITEE_JOINED: case ACCEPTED:
case INVITEE_LEFT: case JOINED:
case LEFT:
case DISSOLVED: case DISSOLVED:
case ERROR: case ERROR:
throw new ProtocolStateException(); // Invalid in these states throw new ProtocolStateException(); // Invalid in these states
@@ -79,13 +82,14 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
throws DbException { throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case INVITEE_LEFT: case LEFT:
case DISSOLVED: case DISSOLVED:
case ERROR: case ERROR:
return s; // Ignored in these states return s; // Ignored in these states
case INVITED: case INVITED:
return onLocalDecline(txn, s); return onLocalDecline(txn, s);
case INVITEE_JOINED: case ACCEPTED:
case JOINED:
return onLocalLeave(txn, s); return onLocalLeave(txn, s);
default: default:
throw new AssertionError(); throw new AssertionError();
@@ -105,8 +109,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
case START: case START:
return onRemoteInvite(txn, s, m); return onRemoteInvite(txn, s, m);
case INVITED: case INVITED:
case INVITEE_JOINED: case ACCEPTED:
case INVITEE_LEFT: case JOINED:
case LEFT:
case DISSOLVED: case DISSOLVED:
return abort(txn, s); // Invalid in these states return abort(txn, s); // Invalid in these states
case ERROR: case ERROR:
@@ -119,7 +124,20 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
@Override @Override
public InviteeSession onJoinMessage(Transaction txn, InviteeSession s, public InviteeSession onJoinMessage(Transaction txn, InviteeSession s,
JoinMessage m) throws DbException, FormatException { JoinMessage m) throws DbException, FormatException {
return abort(txn, s); // Invalid in this role switch (s.getState()) {
case START:
case INVITED:
case JOINED:
case LEFT:
case DISSOLVED:
return abort(txn, s); // Invalid in these states
case ACCEPTED:
return onRemoteJoin(txn, s, m);
case ERROR:
return s; // Ignored in this state
default:
throw new AssertionError();
}
} }
@Override @Override
@@ -130,8 +148,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
case DISSOLVED: case DISSOLVED:
return abort(txn, s); // Invalid in these states return abort(txn, s); // Invalid in these states
case INVITED: case INVITED:
case INVITEE_JOINED: case ACCEPTED:
case INVITEE_LEFT: case JOINED:
case LEFT:
return onRemoteLeave(txn, s, m); return onRemoteLeave(txn, s, m);
case ERROR: case ERROR:
return s; // Ignored in this state return s; // Ignored in this state
@@ -159,15 +178,15 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
try { try {
// Subscribe to the private group // Subscribe to the private group
subscribeToPrivateGroup(txn, inviteId); subscribeToPrivateGroup(txn, inviteId);
// Share the private group with the contact // Make the private group visible to the contact
setPrivateGroupVisibility(txn, s, SHARED); setPrivateGroupVisibility(txn, s, VISIBLE);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); // Invalid group metadata throw new DbException(e); // Invalid group metadata
} }
// Move to the INVITEE_JOINED state // Move to the ACCEPTED state
return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(), return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(), sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp(), INVITEE_JOINED); s.getInviteTimestamp(), ACCEPTED);
} }
private InviteeSession onLocalDecline(Transaction txn, InviteeSession s) private InviteeSession onLocalDecline(Transaction txn, InviteeSession s)
@@ -190,10 +209,10 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
throws DbException { throws DbException {
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false); Message sent = sendLeaveMessage(txn, s, false);
// Move to the INVITEE_LEFT state // Move to the LEFT state
return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(), return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(), sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp(), INVITEE_LEFT); s.getInviteTimestamp(), LEFT);
} }
private InviteeSession onRemoteInvite(Transaction txn, InviteeSession s, private InviteeSession onRemoteInvite(Transaction txn, InviteeSession s,
@@ -222,6 +241,25 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
m.getTimestamp(), INVITED); m.getTimestamp(), INVITED);
} }
private InviteeSession onRemoteJoin(Transaction txn, InviteeSession s,
JoinMessage m) throws DbException, FormatException {
// The timestamp must be higher than the last invite message, if any
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
try {
// Share the private group with the contact
setPrivateGroupVisibility(txn, s, SHARED);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Move to the JOINED state
return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
s.getInviteTimestamp(), JOINED);
}
private InviteeSession onRemoteLeave(Transaction txn, InviteeSession s, private InviteeSession onRemoteLeave(Transaction txn, InviteeSession s,
LeaveMessage m) throws DbException, FormatException { LeaveMessage m) throws DbException, FormatException {
// The timestamp must be higher than the last invite message, if any // The timestamp must be higher than the last invite message, if any

View File

@@ -4,8 +4,8 @@ import org.briarproject.api.FormatException;
enum InviteeState implements State { enum InviteeState implements State {
START(0), INVITED(1), INVITEE_JOINED(2), INVITEE_LEFT(3), DISSOLVED(4), START(0), INVITED(1), ACCEPTED(2), JOINED(3), LEFT(4), DISSOLVED(5),
ERROR(5); ERROR(6);
private final int value; private final int value;

View File

@@ -22,6 +22,7 @@ import javax.annotation.concurrent.Immutable;
import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.api.sync.Group.Visibility.SHARED;
import static org.briarproject.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.privategroup.invitation.PeerState.AWAIT_MEMBER; import static org.briarproject.privategroup.invitation.PeerState.AWAIT_MEMBER;
import static org.briarproject.privategroup.invitation.PeerState.BOTH_JOINED; import static org.briarproject.privategroup.invitation.PeerState.BOTH_JOINED;
import static org.briarproject.privategroup.invitation.PeerState.ERROR; import static org.briarproject.privategroup.invitation.PeerState.ERROR;
@@ -169,6 +170,12 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
PeerSession s) throws DbException { PeerSession s) throws DbException {
// Send a JOIN message // Send a JOIN message
Message sent = sendJoinMessage(txn, s, false); Message sent = sendJoinMessage(txn, s, false);
try {
// Make the private group visible to the contact
setPrivateGroupVisibility(txn, s, VISIBLE);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Move to the LOCAL_JOINED state // Move to the LOCAL_JOINED state
return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(), return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(), sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
@@ -212,6 +219,12 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
PeerSession s) throws DbException { PeerSession s) throws DbException {
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false); Message sent = sendLeaveMessage(txn, s, false);
try {
// Make the private group invisible to the contact
setPrivateGroupVisibility(txn, s, INVISIBLE);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Move to the NEITHER_JOINED state // Move to the NEITHER_JOINED state
return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(), return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(), sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
@@ -316,8 +329,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId())) if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s);
// Make the private group invisible to the contact // Unshare the private group with the contact
setPrivateGroupVisibility(txn, s, INVISIBLE); setPrivateGroupVisibility(txn, s, VISIBLE);
// Move to the LOCAL_JOINED state // Move to the LOCAL_JOINED state
return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(), return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(), s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),