Merge branch '2084-aborted-introduction-sessions' into '1802-sync-via-removable-storage'

Allow aborted introduction sessions to be retried

See merge request briar/briar!1490
This commit is contained in:
Torsten Grote
2021-06-23 16:05:31 +00:00
2 changed files with 72 additions and 57 deletions

View File

@@ -154,7 +154,8 @@ class IntroduceeProtocolEngine
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
return abort(txn, session); // Invalid in these states // Invalid in these states
return abort(txn, session, m.getMessageId());
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -174,7 +175,8 @@ class IntroduceeProtocolEngine
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
return abort(txn, session); // Invalid in these states // Invalid in these states
return abort(txn, session, m.getMessageId());
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -194,7 +196,8 @@ class IntroduceeProtocolEngine
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
return abort(txn, session); // Invalid in these states // Invalid in these states
return abort(txn, session, m.getMessageId());
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -213,7 +216,8 @@ class IntroduceeProtocolEngine
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
return abort(txn, session); // Invalid in these states // Invalid in these states
return abort(txn, session, m.getMessageId());
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -232,7 +236,8 @@ class IntroduceeProtocolEngine
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
return abort(txn, session); // Invalid in these states // Invalid in these states
return abort(txn, session, m.getMessageId());
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -248,7 +253,7 @@ class IntroduceeProtocolEngine
IntroduceeSession s, RequestMessage m) throws DbException { IntroduceeSession s, RequestMessage m) throws DbException {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m.getMessageId());
// Mark the request visible in the UI and available to answer // Mark the request visible in the UI and available to answer
markMessageVisibleInUi(txn, m.getMessageId()); markMessageVisibleInUi(txn, m.getMessageId());
@@ -343,10 +348,10 @@ class IntroduceeProtocolEngine
IntroduceeSession s, AcceptMessage m) throws DbException { IntroduceeSession s, AcceptMessage m) throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m.getMessageId());
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m.getMessageId());
// Determine next state // Determine next state
IntroduceeState state = IntroduceeState state =
@@ -365,10 +370,10 @@ class IntroduceeProtocolEngine
IntroduceeSession s, DeclineMessage m) throws DbException { IntroduceeSession s, DeclineMessage m) throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m.getMessageId());
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m.getMessageId());
// Mark the response visible in the UI // Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getMessageId()); markMessageVisibleInUi(txn, m.getMessageId());
@@ -398,10 +403,10 @@ class IntroduceeProtocolEngine
throws DbException { throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m.getMessageId());
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m.getMessageId());
// Move to START state // Move to START state
return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),
@@ -423,7 +428,7 @@ class IntroduceeProtocolEngine
signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey()); signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey());
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return abort(txn, s); return abort(txn, s, s.getLastRemoteMessageId());
} }
if (s.getState() != AWAIT_AUTH) throw new AssertionError(); if (s.getState() != AWAIT_AUTH) throw new AssertionError();
long localTimestamp = getTimestampForInvisibleMessage(s); long localTimestamp = getTimestampForInvisibleMessage(s);
@@ -436,14 +441,14 @@ class IntroduceeProtocolEngine
IntroduceeSession s, AuthMessage m) throws DbException { IntroduceeSession s, AuthMessage m) throws DbException {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m.getMessageId());
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
try { try {
crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId());
crypto.verifySignature(m.getSignature(), s); crypto.verifySignature(m.getSignature(), s);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
return abort(txn, s); return abort(txn, s, m.getMessageId());
} }
long timestamp = Math.min(s.getLocal().acceptTimestamp, long timestamp = Math.min(s.getLocal().acceptTimestamp,
s.getRemote().acceptTimestamp); s.getRemote().acceptTimestamp);
@@ -487,13 +492,13 @@ class IntroduceeProtocolEngine
IntroduceeSession s, ActivateMessage m) throws DbException { IntroduceeSession s, ActivateMessage m) throws DbException {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m.getMessageId());
// Validate MAC // Validate MAC
try { try {
crypto.verifyActivateMac(m.getMac(), s); crypto.verifyActivateMac(m.getMac(), s);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
return abort(txn, s); return abort(txn, s, m.getMessageId());
} }
// We might not have added transport keys // We might not have added transport keys
@@ -522,8 +527,8 @@ class IntroduceeProtocolEngine
s.getLocalTimestamp(), m.getMessageId()); s.getLocalTimestamp(), m.getMessageId());
} }
private IntroduceeSession abort(Transaction txn, IntroduceeSession s) private IntroduceeSession abort(Transaction txn, IntroduceeSession s,
throws DbException { @Nullable MessageId lastRemoteMessageId) throws DbException {
// Mark the request message unavailable to answer // Mark the request message unavailable to answer
markRequestsUnavailableToAnswer(txn, s); markRequestsUnavailableToAnswer(txn, s);
@@ -536,7 +541,7 @@ class IntroduceeProtocolEngine
// Reset the session back to initial state // Reset the session back to initial state
return IntroduceeSession.clear(s, START, sent.getId(), return IntroduceeSession.clear(s, START, sent.getId(),
sent.getTimestamp(), s.getLastRemoteMessageId()); sent.getTimestamp(), lastRemoteMessageId);
} }
private boolean isInvalidDependency(IntroduceeSession s, private boolean isInvalidDependency(IntroduceeSession s,

View File

@@ -115,7 +115,7 @@ class IntroducerProtocolEngine
@Override @Override
public IntroducerSession onRequestMessage(Transaction txn, public IntroducerSession onRequestMessage(Transaction txn,
IntroducerSession s, RequestMessage m) throws DbException { IntroducerSession s, RequestMessage m) throws DbException {
return abort(txn, s); // Invalid in this role return abort(txn, s, m); // Invalid in this role
} }
@Override @Override
@@ -136,7 +136,7 @@ class IntroducerProtocolEngine
case AWAIT_ACTIVATES: case AWAIT_ACTIVATES:
case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_A:
case AWAIT_ACTIVATE_B: case AWAIT_ACTIVATE_B:
return abort(txn, s); // Invalid in these states return abort(txn, s, m); // Invalid in these states
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -160,7 +160,7 @@ class IntroducerProtocolEngine
case AWAIT_ACTIVATES: case AWAIT_ACTIVATES:
case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_A:
case AWAIT_ACTIVATE_B: case AWAIT_ACTIVATE_B:
return abort(txn, s); // Invalid in these states return abort(txn, s, m); // Invalid in these states
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -183,7 +183,7 @@ class IntroducerProtocolEngine
case AWAIT_ACTIVATES: case AWAIT_ACTIVATES:
case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_A:
case AWAIT_ACTIVATE_B: case AWAIT_ACTIVATE_B:
return abort(txn, s); // Invalid in these states return abort(txn, s, m); // Invalid in these states
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -206,7 +206,7 @@ class IntroducerProtocolEngine
case AWAIT_AUTHS: case AWAIT_AUTHS:
case AWAIT_AUTH_A: case AWAIT_AUTH_A:
case AWAIT_AUTH_B: case AWAIT_AUTH_B:
return abort(txn, s); // Invalid in these states return abort(txn, s, m); // Invalid in these states
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -244,17 +244,17 @@ class IntroducerProtocolEngine
IntroducerSession s, AcceptMessage m) throws DbException { IntroducerSession s, AcceptMessage m) throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m);
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m);
// The message must be expected in the current state // The message must be expected in the current state
boolean senderIsAlice = senderIsAlice(s, m); boolean senderIsAlice = senderIsAlice(s, m);
if (s.getState() != AWAIT_RESPONSES) { if (s.getState() != AWAIT_RESPONSES) {
if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A) if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A)
return abort(txn, s); return abort(txn, s, m);
else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B) else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B)
return abort(txn, s); return abort(txn, s, m);
} }
// Mark the response visible in the UI // Mark the response visible in the UI
@@ -309,16 +309,16 @@ class IntroducerProtocolEngine
IntroducerSession s, AcceptMessage m) throws DbException { IntroducerSession s, AcceptMessage m) throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m);
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m);
// The message must be expected in the current state // The message must be expected in the current state
boolean senderIsAlice = senderIsAlice(s, m); boolean senderIsAlice = senderIsAlice(s, m);
if (senderIsAlice && s.getState() != B_DECLINED) if (senderIsAlice && s.getState() != B_DECLINED)
return abort(txn, s); return abort(txn, s, m);
else if (!senderIsAlice && s.getState() != A_DECLINED) else if (!senderIsAlice && s.getState() != A_DECLINED)
return abort(txn, s); return abort(txn, s, m);
// Mark the response visible in the UI // Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getMessageId()); markMessageVisibleInUi(txn, m.getMessageId());
@@ -362,17 +362,17 @@ class IntroducerProtocolEngine
IntroducerSession s, DeclineMessage m) throws DbException { IntroducerSession s, DeclineMessage m) throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m);
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m);
// The message must be expected in the current state // The message must be expected in the current state
boolean senderIsAlice = senderIsAlice(s, m); boolean senderIsAlice = senderIsAlice(s, m);
if (s.getState() != AWAIT_RESPONSES) { if (s.getState() != AWAIT_RESPONSES) {
if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A) if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A)
return abort(txn, s); return abort(txn, s, m);
else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B) else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B)
return abort(txn, s); return abort(txn, s, m);
} }
// Mark the response visible in the UI // Mark the response visible in the UI
@@ -419,16 +419,16 @@ class IntroducerProtocolEngine
IntroducerSession s, DeclineMessage m) throws DbException { IntroducerSession s, DeclineMessage m) throws DbException {
// The timestamp must be higher than the last request message // The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp()) if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s); return abort(txn, s, m);
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m);
// The message must be expected in the current state // The message must be expected in the current state
boolean senderIsAlice = senderIsAlice(s, m); boolean senderIsAlice = senderIsAlice(s, m);
if (senderIsAlice && s.getState() != B_DECLINED) if (senderIsAlice && s.getState() != B_DECLINED)
return abort(txn, s); return abort(txn, s, m);
else if (!senderIsAlice && s.getState() != A_DECLINED) else if (!senderIsAlice && s.getState() != A_DECLINED)
return abort(txn, s); return abort(txn, s, m);
// Mark the response visible in the UI // Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getMessageId()); markMessageVisibleInUi(txn, m.getMessageId());
@@ -470,14 +470,14 @@ class IntroducerProtocolEngine
IntroducerSession s, AuthMessage m) throws DbException { IntroducerSession s, AuthMessage m) throws DbException {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m);
// The message must be expected in the current state // The message must be expected in the current state
boolean senderIsAlice = senderIsAlice(s, m); boolean senderIsAlice = senderIsAlice(s, m);
if (s.getState() != AWAIT_AUTHS) { if (s.getState() != AWAIT_AUTHS) {
if (senderIsAlice && s.getState() != AWAIT_AUTH_A) if (senderIsAlice && s.getState() != AWAIT_AUTH_A)
return abort(txn, s); return abort(txn, s, m);
else if (!senderIsAlice && s.getState() != AWAIT_AUTH_B) else if (!senderIsAlice && s.getState() != AWAIT_AUTH_B)
return abort(txn, s); return abort(txn, s, m);
} }
// Forward AUTH message // Forward AUTH message
@@ -506,14 +506,14 @@ class IntroducerProtocolEngine
IntroducerSession s, ActivateMessage m) throws DbException { IntroducerSession s, ActivateMessage m) throws DbException {
// The dependency, if any, must be the last remote message // The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s, m);
// The message must be expected in the current state // The message must be expected in the current state
boolean senderIsAlice = senderIsAlice(s, m); boolean senderIsAlice = senderIsAlice(s, m);
if (s.getState() != AWAIT_ACTIVATES) { if (s.getState() != AWAIT_ACTIVATES) {
if (senderIsAlice && s.getState() != AWAIT_ACTIVATE_A) if (senderIsAlice && s.getState() != AWAIT_ACTIVATE_A)
return abort(txn, s); return abort(txn, s, m);
else if (!senderIsAlice && s.getState() != AWAIT_ACTIVATE_B) else if (!senderIsAlice && s.getState() != AWAIT_ACTIVATE_B)
return abort(txn, s); return abort(txn, s, m);
} }
// Forward ACTIVATE message // Forward ACTIVATE message
@@ -588,21 +588,31 @@ class IntroducerProtocolEngine
s.getRequestTimestamp(), introduceeA, introduceeB); s.getRequestTimestamp(), introduceeA, introduceeB);
} }
private IntroducerSession abort(Transaction txn, IntroducerSession s) private IntroducerSession abort(Transaction txn, IntroducerSession s,
throws DbException { AbstractIntroductionMessage lastRemoteMessage) throws DbException {
// Broadcast abort event for testing // Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId())); txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Record the message that triggered the abort
Introducee introduceeA = s.getIntroduceeA();
Introducee introduceeB = s.getIntroduceeB();
if (senderIsAlice(s, lastRemoteMessage)) {
introduceeA = new Introducee(introduceeA,
lastRemoteMessage.getMessageId());
} else {
introduceeB = new Introducee(introduceeB,
lastRemoteMessage.getMessageId());
}
// Send an ABORT message to both introducees // Send an ABORT message to both introducees
long timestampA = long timestampA = getTimestampForInvisibleMessage(s, introduceeA);
getTimestampForInvisibleMessage(s, s.getIntroduceeA()); Message sentA = sendAbortMessage(txn, introduceeA, timestampA);
Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA); long timestampB = getTimestampForInvisibleMessage(s, introduceeB);
long timestampB = Message sentB = sendAbortMessage(txn, introduceeB, timestampB);
getTimestampForInvisibleMessage(s, s.getIntroduceeB());
Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB);
// Reset the session back to initial state // Reset the session back to initial state
Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA); introduceeA = new Introducee(introduceeA, sentA);
Introducee introduceeB = new Introducee(s.getIntroduceeB(), sentB); introduceeB = new Introducee(introduceeB, sentB);
return new IntroducerSession(s.getSessionId(), START, return new IntroducerSession(s.getSessionId(), START,
s.getRequestTimestamp(), introduceeA, introduceeB); s.getRequestTimestamp(), introduceeA, introduceeB);
} }