From 58f6af513d0b48423f04f9011c5eb71e037fe10e Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Tue, 15 Nov 2016 15:37:31 +0000
Subject: [PATCH] Use new group visibility state to avoid lost messages. #756

---
 .../invitation/CreatorProtocolEngine.java     | 32 +++++----
 .../privategroup/invitation/CreatorState.java |  3 +-
 .../invitation/InviteeProtocolEngine.java     | 72 ++++++++++++++-----
 .../privategroup/invitation/InviteeState.java |  4 +-
 .../invitation/PeerProtocolEngine.java        | 17 ++++-
 5 files changed, 90 insertions(+), 38 deletions(-)

diff --git a/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java
index 26f1715797..d31e88aa28 100644
--- a/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java
+++ b/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java
@@ -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)
diff --git a/briar-core/src/org/briarproject/privategroup/invitation/CreatorState.java b/briar-core/src/org/briarproject/privategroup/invitation/CreatorState.java
index 3a54b00a37..a759ee7f34 100644
--- a/briar-core/src/org/briarproject/privategroup/invitation/CreatorState.java
+++ b/briar-core/src/org/briarproject/privategroup/invitation/CreatorState.java
@@ -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;
 
diff --git a/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java
index 4398badbf2..fe03fdb7c6 100644
--- a/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java
+++ b/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java
@@ -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
diff --git a/briar-core/src/org/briarproject/privategroup/invitation/InviteeState.java b/briar-core/src/org/briarproject/privategroup/invitation/InviteeState.java
index 97e6480a5d..3299d747ea 100644
--- a/briar-core/src/org/briarproject/privategroup/invitation/InviteeState.java
+++ b/briar-core/src/org/briarproject/privategroup/invitation/InviteeState.java
@@ -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;
 
diff --git a/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java
index 06b019649e..7eaebba496 100644
--- a/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java
+++ b/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java
@@ -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(),
-- 
GitLab