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

View File

@@ -4,8 +4,7 @@ import org.briarproject.api.FormatException;
enum CreatorState implements State {
START(0), INVITED(1), INVITEE_JOINED(2), INVITEE_LEFT(3), DISSOLVED(4),
ERROR(5);
START(0), INVITED(1), JOINED(2), LEFT(3), DISSOLVED(4), ERROR(5);
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.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.ERROR;
import static org.briarproject.privategroup.invitation.InviteeState.INVITED;
import static org.briarproject.privategroup.invitation.InviteeState.INVITEE_JOINED;
import static org.briarproject.privategroup.invitation.InviteeState.INVITEE_LEFT;
import static org.briarproject.privategroup.invitation.InviteeState.JOINED;
import static org.briarproject.privategroup.invitation.InviteeState.LEFT;
import static org.briarproject.privategroup.invitation.InviteeState.START;
@Immutable
@@ -62,8 +64,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
throws DbException {
switch (s.getState()) {
case START:
case INVITEE_JOINED:
case INVITEE_LEFT:
case ACCEPTED:
case JOINED:
case LEFT:
case DISSOLVED:
case ERROR:
throw new ProtocolStateException(); // Invalid in these states
@@ -79,13 +82,14 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
throws DbException {
switch (s.getState()) {
case START:
case INVITEE_LEFT:
case LEFT:
case DISSOLVED:
case ERROR:
return s; // Ignored in these states
case INVITED:
return onLocalDecline(txn, s);
case INVITEE_JOINED:
case ACCEPTED:
case JOINED:
return onLocalLeave(txn, s);
default:
throw new AssertionError();
@@ -105,8 +109,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
case START:
return onRemoteInvite(txn, s, m);
case INVITED:
case INVITEE_JOINED:
case INVITEE_LEFT:
case ACCEPTED:
case JOINED:
case LEFT:
case DISSOLVED:
return abort(txn, s); // Invalid in these states
case ERROR:
@@ -119,7 +124,20 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
@Override
public InviteeSession onJoinMessage(Transaction txn, InviteeSession s,
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
@@ -130,8 +148,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
case DISSOLVED:
return abort(txn, s); // Invalid in these states
case INVITED:
case INVITEE_JOINED:
case INVITEE_LEFT:
case ACCEPTED:
case JOINED:
case LEFT:
return onRemoteLeave(txn, s, m);
case ERROR:
return s; // Ignored in this state
@@ -159,15 +178,15 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
try {
// Subscribe to the private group
subscribeToPrivateGroup(txn, inviteId);
// Share the private group with the contact
setPrivateGroupVisibility(txn, s, SHARED);
// 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 INVITEE_JOINED state
// Move to the ACCEPTED state
return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp(), INVITEE_JOINED);
s.getInviteTimestamp(), ACCEPTED);
}
private InviteeSession onLocalDecline(Transaction txn, InviteeSession s)
@@ -190,10 +209,10 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
throws DbException {
// Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false);
// Move to the INVITEE_LEFT state
// Move to the LEFT state
return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp(), INVITEE_LEFT);
s.getInviteTimestamp(), LEFT);
}
private InviteeSession onRemoteInvite(Transaction txn, InviteeSession s,
@@ -222,6 +241,25 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
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,
LeaveMessage m) throws DbException, FormatException {
// 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 {
START(0), INVITED(1), INVITEE_JOINED(2), INVITEE_LEFT(3), DISSOLVED(4),
ERROR(5);
START(0), INVITED(1), ACCEPTED(2), JOINED(3), LEFT(4), DISSOLVED(5),
ERROR(6);
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.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.BOTH_JOINED;
import static org.briarproject.privategroup.invitation.PeerState.ERROR;
@@ -169,6 +170,12 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
PeerSession s) throws DbException {
// Send a JOIN message
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
return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
@@ -212,6 +219,12 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
PeerSession s) throws DbException {
// Send a LEAVE message
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
return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(),
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
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Make the private group invisible to the contact
setPrivateGroupVisibility(txn, s, INVISIBLE);
// Unshare the private group with the contact
setPrivateGroupVisibility(txn, s, VISIBLE);
// Move to the LOCAL_JOINED state
return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),