mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 21:59:54 +01:00
Merge branch '1802-sync-via-removable-storage' into offline-testing
# Conflicts: # bramble-api/src/main/java/org/briarproject/bramble/api/FeatureFlags.java # bramble-core/build.gradle # bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java # bramble-core/witness.gradle # bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java # briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java # briar-android/src/main/java/org/briarproject/briar/android/AppModule.java # briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java # briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java # briar-android/src/main/res/values/strings.xml # briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt # briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt
This commit is contained in:
@@ -40,6 +40,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
|
||||
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
||||
@@ -124,8 +125,8 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
Group ourGroup = getOurGroup(txn);
|
||||
if (m.getGroupId().equals(ourGroup.getId())) {
|
||||
throw new InvalidMessageException(
|
||||
@@ -144,7 +145,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
||||
// We've already received a newer update - delete this one
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
return false; // don't broadcast update
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
}
|
||||
ContactId contactId = getContactId(txn, m.getGroupId());
|
||||
@@ -155,7 +156,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook,
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||
@@ -109,8 +111,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList list,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList list, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
GroupId groupId = m.getGroupId();
|
||||
MessageType type = getMessageType(meta);
|
||||
@@ -138,7 +141,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
txn.attach(event);
|
||||
|
||||
// shares message and its dependencies
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
} else if (type == WRAPPED_COMMENT) {
|
||||
// Check that the original message ID in the dependency's metadata
|
||||
// matches the original parent ID of the wrapped comment
|
||||
@@ -153,7 +156,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
}
|
||||
}
|
||||
// don't share message until parent arrives
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,6 +45,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
|
||||
@@ -75,8 +76,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
|
||||
@@ -86,8 +88,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
new ForumPostReceivedEvent(m.getGroupId(), header, text);
|
||||
txn.attach(event);
|
||||
|
||||
// share message
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
@@ -48,6 +49,8 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
|
||||
@@ -154,7 +157,8 @@ class IntroduceeProtocolEngine
|
||||
case REMOTE_ACCEPTED:
|
||||
case AWAIT_AUTH:
|
||||
case AWAIT_ACTIVATE:
|
||||
return abort(txn, session); // Invalid in these states
|
||||
// Invalid in these states
|
||||
return abort(txn, session, m.getMessageId());
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -174,7 +178,8 @@ class IntroduceeProtocolEngine
|
||||
case REMOTE_ACCEPTED:
|
||||
case AWAIT_AUTH:
|
||||
case AWAIT_ACTIVATE:
|
||||
return abort(txn, session); // Invalid in these states
|
||||
// Invalid in these states
|
||||
return abort(txn, session, m.getMessageId());
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -194,7 +199,8 @@ class IntroduceeProtocolEngine
|
||||
case REMOTE_ACCEPTED:
|
||||
case AWAIT_AUTH:
|
||||
case AWAIT_ACTIVATE:
|
||||
return abort(txn, session); // Invalid in these states
|
||||
// Invalid in these states
|
||||
return abort(txn, session, m.getMessageId());
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -213,7 +219,8 @@ class IntroduceeProtocolEngine
|
||||
case LOCAL_ACCEPTED:
|
||||
case REMOTE_ACCEPTED:
|
||||
case AWAIT_ACTIVATE:
|
||||
return abort(txn, session); // Invalid in these states
|
||||
// Invalid in these states
|
||||
return abort(txn, session, m.getMessageId());
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -232,7 +239,8 @@ class IntroduceeProtocolEngine
|
||||
case LOCAL_ACCEPTED:
|
||||
case REMOTE_ACCEPTED:
|
||||
case AWAIT_AUTH:
|
||||
return abort(txn, session); // Invalid in these states
|
||||
// Invalid in these states
|
||||
return abort(txn, session, m.getMessageId());
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -248,7 +256,7 @@ class IntroduceeProtocolEngine
|
||||
IntroduceeSession s, RequestMessage m) throws DbException {
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
markMessageVisibleInUi(txn, m.getMessageId());
|
||||
@@ -343,10 +351,10 @@ class IntroduceeProtocolEngine
|
||||
IntroduceeSession s, AcceptMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
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
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
|
||||
// Determine next state
|
||||
IntroduceeState state =
|
||||
@@ -365,10 +373,10 @@ class IntroduceeProtocolEngine
|
||||
IntroduceeSession s, DeclineMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
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
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
|
||||
// Mark the response visible in the UI
|
||||
markMessageVisibleInUi(txn, m.getMessageId());
|
||||
@@ -398,10 +406,10 @@ class IntroduceeProtocolEngine
|
||||
throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
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
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
|
||||
// Move to START state
|
||||
return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),
|
||||
@@ -423,7 +431,7 @@ class IntroduceeProtocolEngine
|
||||
signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey());
|
||||
} catch (GeneralSecurityException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, s.getLastRemoteMessageId());
|
||||
}
|
||||
if (s.getState() != AWAIT_AUTH) throw new AssertionError();
|
||||
long localTimestamp = getTimestampForInvisibleMessage(s);
|
||||
@@ -436,42 +444,42 @@ class IntroduceeProtocolEngine
|
||||
IntroduceeSession s, AuthMessage m) throws DbException {
|
||||
// The dependency, if any, must be the last remote message
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
try {
|
||||
crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId());
|
||||
crypto.verifySignature(m.getSignature(), s);
|
||||
} catch (GeneralSecurityException e) {
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
}
|
||||
long timestamp = Math.min(s.getLocal().acceptTimestamp,
|
||||
s.getRemote().acceptTimestamp);
|
||||
if (timestamp == -1) throw new AssertionError();
|
||||
if (timestamp < MIN_REASONABLE_TIME_MS) {
|
||||
LOG.warning("Timestamp is too old");
|
||||
return abort(txn, s, m.getMessageId());
|
||||
}
|
||||
|
||||
Map<TransportId, KeySetId> keys = null;
|
||||
try {
|
||||
contactManager.addContact(txn, s.getRemote().author,
|
||||
localAuthor.getId(), false);
|
||||
ContactId contactId = contactManager.addContact(txn,
|
||||
s.getRemote().author, localAuthor.getId(), false);
|
||||
|
||||
// Only add transport properties and keys when the contact was added
|
||||
// This will be changed once we have a way to reset state for peers
|
||||
// that were contacts already at some point in the past.
|
||||
Contact c = contactManager.getContact(txn,
|
||||
s.getRemote().author.getId(), localAuthor.getId());
|
||||
|
||||
// add the keys to the new contact
|
||||
keys = keyManager.addRotationKeys(txn, c.getId(),
|
||||
keys = keyManager.addRotationKeys(txn, contactId,
|
||||
new SecretKey(s.getMasterKey()), timestamp,
|
||||
s.getLocal().alice, false);
|
||||
|
||||
// add signed transport properties for the contact
|
||||
transportPropertyManager.addRemoteProperties(txn, c.getId(),
|
||||
s.getRemote().transportProperties);
|
||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||
requireNonNull(s.getRemote().transportProperties));
|
||||
} catch (ContactExistsException e) {
|
||||
// Ignore this, because the other introducee might have deleted us.
|
||||
// So we still want updated transport properties
|
||||
// and new transport keys.
|
||||
// Ignore this, because the other introducee might have deleted us
|
||||
}
|
||||
|
||||
// send ACTIVATE message with a MAC
|
||||
@@ -487,13 +495,13 @@ class IntroduceeProtocolEngine
|
||||
IntroduceeSession s, ActivateMessage m) throws DbException {
|
||||
// The dependency, if any, must be the last remote message
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
|
||||
// Validate MAC
|
||||
try {
|
||||
crypto.verifyActivateMac(m.getMac(), s);
|
||||
} catch (GeneralSecurityException e) {
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m.getMessageId());
|
||||
}
|
||||
|
||||
// We might not have added transport keys
|
||||
@@ -522,8 +530,8 @@ class IntroduceeProtocolEngine
|
||||
s.getLocalTimestamp(), m.getMessageId());
|
||||
}
|
||||
|
||||
private IntroduceeSession abort(Transaction txn, IntroduceeSession s)
|
||||
throws DbException {
|
||||
private IntroduceeSession abort(Transaction txn, IntroduceeSession s,
|
||||
@Nullable MessageId lastRemoteMessageId) throws DbException {
|
||||
// Mark the request message unavailable to answer
|
||||
markRequestsUnavailableToAnswer(txn, s);
|
||||
|
||||
@@ -536,7 +544,7 @@ class IntroduceeProtocolEngine
|
||||
|
||||
// Reset the session back to initial state
|
||||
return IntroduceeSession.clear(s, START, sent.getId(),
|
||||
sent.getTimestamp(), s.getLastRemoteMessageId());
|
||||
sent.getTimestamp(), lastRemoteMessageId);
|
||||
}
|
||||
|
||||
private boolean isInvalidDependency(IntroduceeSession s,
|
||||
|
||||
@@ -116,8 +116,8 @@ class IntroduceeSession extends Session<IntroduceeState>
|
||||
|
||||
static IntroduceeSession awaitActivate(IntroduceeSession s, AuthMessage m,
|
||||
Message sent, @Nullable Map<TransportId, KeySetId> transportKeys) {
|
||||
Local local = new Local(s.local, sent.getId(), sent.getTimestamp());
|
||||
Remote remote = new Remote(s.remote, m.getMessageId());
|
||||
Local local = Local.clear(s.local, sent.getId(), sent.getTimestamp());
|
||||
Remote remote = Remote.clear(s.remote, m.getMessageId());
|
||||
return new IntroduceeSession(s.getSessionId(), AWAIT_ACTIVATE,
|
||||
s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
|
||||
remote, null, transportKeys);
|
||||
@@ -228,11 +228,15 @@ class IntroduceeSession extends Session<IntroduceeState>
|
||||
this.ephemeralPrivateKey = ephemeralPrivateKey;
|
||||
}
|
||||
|
||||
private Local(Local s, @Nullable MessageId lastMessageId,
|
||||
long lastMessageTimestamp) {
|
||||
this(s.alice, lastMessageId, lastMessageTimestamp,
|
||||
s.ephemeralPublicKey, s.ephemeralPrivateKey,
|
||||
s.transportProperties, s.acceptTimestamp, s.macKey);
|
||||
/**
|
||||
* Returns a copy of the given Local, updating the last message ID
|
||||
* and timestamp and clearing the ephemeral keys.
|
||||
*/
|
||||
private static Local clear(Local s,
|
||||
@Nullable MessageId lastMessageId, long lastMessageTimestamp) {
|
||||
return new Local(s.alice, lastMessageId, lastMessageTimestamp,
|
||||
null, null, s.transportProperties, s.acceptTimestamp,
|
||||
s.macKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,10 +253,23 @@ class IntroduceeSession extends Session<IntroduceeState>
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the given Remote, updating the last message ID.
|
||||
*/
|
||||
private Remote(Remote s, @Nullable MessageId lastMessageId) {
|
||||
this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey,
|
||||
s.transportProperties, s.acceptTimestamp, s.macKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the given Remote, updating the last message ID
|
||||
* and clearing the ephemeral keys.
|
||||
*/
|
||||
private static Remote clear(Remote s,
|
||||
@Nullable MessageId lastMessageId) {
|
||||
return new Remote(s.alice, s.author, lastMessageId, null,
|
||||
s.transportProperties, s.acceptTimestamp, s.macKey);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ class IntroducerProtocolEngine
|
||||
@Override
|
||||
public IntroducerSession onRequestMessage(Transaction txn,
|
||||
IntroducerSession s, RequestMessage m) throws DbException {
|
||||
return abort(txn, s); // Invalid in this role
|
||||
return abort(txn, s, m); // Invalid in this role
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,7 +136,7 @@ class IntroducerProtocolEngine
|
||||
case AWAIT_ACTIVATES:
|
||||
case AWAIT_ACTIVATE_A:
|
||||
case AWAIT_ACTIVATE_B:
|
||||
return abort(txn, s); // Invalid in these states
|
||||
return abort(txn, s, m); // Invalid in these states
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -160,7 +160,7 @@ class IntroducerProtocolEngine
|
||||
case AWAIT_ACTIVATES:
|
||||
case AWAIT_ACTIVATE_A:
|
||||
case AWAIT_ACTIVATE_B:
|
||||
return abort(txn, s); // Invalid in these states
|
||||
return abort(txn, s, m); // Invalid in these states
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -183,7 +183,7 @@ class IntroducerProtocolEngine
|
||||
case AWAIT_ACTIVATES:
|
||||
case AWAIT_ACTIVATE_A:
|
||||
case AWAIT_ACTIVATE_B:
|
||||
return abort(txn, s); // Invalid in these states
|
||||
return abort(txn, s, m); // Invalid in these states
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -206,7 +206,7 @@ class IntroducerProtocolEngine
|
||||
case AWAIT_AUTHS:
|
||||
case AWAIT_AUTH_A:
|
||||
case AWAIT_AUTH_B:
|
||||
return abort(txn, s); // Invalid in these states
|
||||
return abort(txn, s, m); // Invalid in these states
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -244,17 +244,17 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s, AcceptMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
boolean senderIsAlice = senderIsAlice(s, m);
|
||||
if (s.getState() != AWAIT_RESPONSES) {
|
||||
if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
}
|
||||
|
||||
// Mark the response visible in the UI
|
||||
@@ -309,16 +309,16 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s, AcceptMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
boolean senderIsAlice = senderIsAlice(s, m);
|
||||
if (senderIsAlice && s.getState() != B_DECLINED)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
else if (!senderIsAlice && s.getState() != A_DECLINED)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
|
||||
// Mark the response visible in the UI
|
||||
markMessageVisibleInUi(txn, m.getMessageId());
|
||||
@@ -362,17 +362,17 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s, DeclineMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
boolean senderIsAlice = senderIsAlice(s, m);
|
||||
if (s.getState() != AWAIT_RESPONSES) {
|
||||
if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
}
|
||||
|
||||
// Mark the response visible in the UI
|
||||
@@ -419,16 +419,16 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s, DeclineMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
boolean senderIsAlice = senderIsAlice(s, m);
|
||||
if (senderIsAlice && s.getState() != B_DECLINED)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
else if (!senderIsAlice && s.getState() != A_DECLINED)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
|
||||
// Mark the response visible in the UI
|
||||
markMessageVisibleInUi(txn, m.getMessageId());
|
||||
@@ -470,14 +470,14 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s, AuthMessage m) throws DbException {
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
boolean senderIsAlice = senderIsAlice(s, m);
|
||||
if (s.getState() != AWAIT_AUTHS) {
|
||||
if (senderIsAlice && s.getState() != AWAIT_AUTH_A)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
else if (!senderIsAlice && s.getState() != AWAIT_AUTH_B)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
}
|
||||
|
||||
// Forward AUTH message
|
||||
@@ -506,14 +506,14 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s, ActivateMessage m) throws DbException {
|
||||
// The dependency, if any, must be the last remote message
|
||||
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
|
||||
boolean senderIsAlice = senderIsAlice(s, m);
|
||||
if (s.getState() != AWAIT_ACTIVATES) {
|
||||
if (senderIsAlice && s.getState() != AWAIT_ACTIVATE_A)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
else if (!senderIsAlice && s.getState() != AWAIT_ACTIVATE_B)
|
||||
return abort(txn, s);
|
||||
return abort(txn, s, m);
|
||||
}
|
||||
|
||||
// Forward ACTIVATE message
|
||||
@@ -588,21 +588,31 @@ class IntroducerProtocolEngine
|
||||
s.getRequestTimestamp(), introduceeA, introduceeB);
|
||||
}
|
||||
|
||||
private IntroducerSession abort(Transaction txn, IntroducerSession s)
|
||||
throws DbException {
|
||||
private IntroducerSession abort(Transaction txn, IntroducerSession s,
|
||||
AbstractIntroductionMessage lastRemoteMessage) throws DbException {
|
||||
// Broadcast abort event for testing
|
||||
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
|
||||
long timestampA =
|
||||
getTimestampForInvisibleMessage(s, s.getIntroduceeA());
|
||||
Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA);
|
||||
long timestampB =
|
||||
getTimestampForInvisibleMessage(s, s.getIntroduceeB());
|
||||
Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB);
|
||||
long timestampA = getTimestampForInvisibleMessage(s, introduceeA);
|
||||
Message sentA = sendAbortMessage(txn, introduceeA, timestampA);
|
||||
long timestampB = getTimestampForInvisibleMessage(s, introduceeB);
|
||||
Message sentB = sendAbortMessage(txn, introduceeB, timestampB);
|
||||
|
||||
// Reset the session back to initial state
|
||||
Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA);
|
||||
Introducee introduceeB = new Introducee(s.getIntroduceeB(), sentB);
|
||||
introduceeA = new Introducee(introduceeA, sentA);
|
||||
introduceeB = new Introducee(introduceeB, sentB);
|
||||
return new IntroducerSession(s.getSessionId(), START,
|
||||
s.getRequestTimestamp(), introduceeA, introduceeB);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
@@ -171,8 +172,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary bdfMeta)
|
||||
throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// set the clean-up timer that will be started when message gets read
|
||||
@@ -213,7 +215,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
||||
|
||||
@@ -63,6 +63,7 @@ import static java.util.Collections.emptyList;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
@@ -172,8 +173,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
BdfDictionary metaDict = metadataParser.parse(meta);
|
||||
// Message type is null for version 0.0 private messages
|
||||
@@ -193,8 +194,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
// Don't share message
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private void incomingPrivateMessage(Transaction txn, Message m,
|
||||
|
||||
@@ -55,6 +55,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
||||
@@ -518,18 +519,19 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
MessageType type =
|
||||
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
||||
switch (type) {
|
||||
case JOIN:
|
||||
handleJoinMessage(txn, m, meta);
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
case POST:
|
||||
handleGroupMessage(txn, m, meta);
|
||||
return true;
|
||||
return ACCEPT_SHARE;
|
||||
default:
|
||||
// the validator should only let valid types pass
|
||||
throw new RuntimeException("Unknown MessageType");
|
||||
|
||||
@@ -54,6 +54,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||
@@ -147,8 +148,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary bdfMeta)
|
||||
throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// set the clean-up timer that will be started when message gets read
|
||||
@@ -171,7 +173,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private SessionId getSessionId(GroupId privateGroupId) {
|
||||
|
||||
@@ -49,6 +49,7 @@ import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.sharing.MessageType.ABORT;
|
||||
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
|
||||
@@ -133,8 +134,8 @@ abstract class SharingManagerImpl<S extends Shareable>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary d) throws DbException, FormatException {
|
||||
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary d) throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(d);
|
||||
// set the clean-up timer that will be started when message gets read
|
||||
@@ -157,7 +158,7 @@ abstract class SharingManagerImpl<S extends Shareable>
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,7 @@ import javax.annotation.Nullable;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
@@ -59,7 +60,6 @@ import static org.briarproject.briar.api.avatar.AvatarManager.MAJOR_VERSION;
|
||||
import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -196,7 +196,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
null);
|
||||
expectGetContactId(txn, contactGroupId, contact.getId());
|
||||
|
||||
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
assertEquals(1, txn.getActions().size());
|
||||
Event event = ((EventAction) txn.getActions().get(0)).getEvent();
|
||||
AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event;
|
||||
@@ -230,7 +231,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
||||
expectGetContactId(txn, contactGroupId, contact.getId());
|
||||
|
||||
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
|
||||
// event to broadcast
|
||||
assertEquals(1, txn.getActions().size());
|
||||
@@ -260,7 +262,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
expectFindLatest(txn, contactGroupId, latestMsgId, latest);
|
||||
|
||||
assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
avatarManager.incomingMessage(txn, contactMsg, meta));
|
||||
|
||||
// no event to broadcast
|
||||
assertEquals(0, txn.getActions().size());
|
||||
@@ -271,7 +274,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase {
|
||||
throws DbException, InvalidMessageException {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
expectGetOurGroup(txn);
|
||||
avatarManager.incomingMessage(txn, ourMsg, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
avatarManager.incomingMessage(txn, ourMsg, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
@@ -184,7 +185,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
||||
will(returnValue(verifiedInfo));
|
||||
}});
|
||||
|
||||
blogManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_SHARE,
|
||||
blogManager.incomingMessage(txn, message, body, meta));
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertEquals(1, txn.getActions().size());
|
||||
@@ -225,7 +227,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
||||
will(returnValue(rssLocalAuthor));
|
||||
}});
|
||||
|
||||
blogManager.incomingMessage(txn, rssMessage, body, meta);
|
||||
assertEquals(ACCEPT_SHARE,
|
||||
blogManager.incomingMessage(txn, rssMessage, body, meta));
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertEquals(1, txn.getActions().size());
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||
import org.briarproject.briar.api.identity.AuthorManager;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
|
||||
import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.START;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class IntroduceeProtocolEngineTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ContactManager contactManager =
|
||||
context.mock(ContactManager.class);
|
||||
private final ContactGroupFactory contactGroupFactory =
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final MessageTracker messageTracker =
|
||||
context.mock(MessageTracker.class);
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final AuthorManager authorManager =
|
||||
context.mock(AuthorManager.class);
|
||||
private final MessageParser messageParser =
|
||||
context.mock(MessageParser.class);
|
||||
private final MessageEncoder messageEncoder =
|
||||
context.mock(MessageEncoder.class);
|
||||
private final IntroductionCrypto crypto =
|
||||
context.mock(IntroductionCrypto.class);
|
||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
||||
private final TransportPropertyManager transportPropertyManager =
|
||||
context.mock(TransportPropertyManager.class);
|
||||
private final ClientVersioningManager clientVersioningManager =
|
||||
context.mock(ClientVersioningManager.class);
|
||||
private final AutoDeleteManager autoDeleteManager =
|
||||
context.mock(AutoDeleteManager.class);
|
||||
private final ConversationManager conversationManager =
|
||||
context.mock(ConversationManager.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
|
||||
private final Author introducer = getAuthor();
|
||||
private final Author remoteIntroducee = getAuthor();
|
||||
private final LocalAuthor localIntroducee = getLocalAuthor();
|
||||
private final ContactId contactId = getContactId();
|
||||
private final GroupId contactGroupId = new GroupId(getRandomId());
|
||||
private final SessionId sessionId = new SessionId(getRandomId());
|
||||
private final boolean alice = new Random().nextBoolean();
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final long requestTimestamp = now - 12345;
|
||||
private final long localAcceptTimestamp = requestTimestamp + 123;
|
||||
private final long remoteAuthTimestamp = localAcceptTimestamp + 123;
|
||||
private final MessageId lastLocalMessageId = new MessageId(getRandomId());
|
||||
private final MessageId lastRemoteMessageId = new MessageId(getRandomId());
|
||||
private final PublicKey localPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey localPrivateKey = getAgreementPrivateKey();
|
||||
private final PublicKey remotePublicKey = getAgreementPublicKey();
|
||||
private final SecretKey localMacKey = getSecretKey();
|
||||
private final SecretKey remoteMacKey = getSecretKey();
|
||||
private final SecretKey masterKey = getSecretKey();
|
||||
private final byte[] remoteMac = getRandomBytes(MAC_BYTES);
|
||||
private final byte[] localMac = getRandomBytes(MAC_BYTES);
|
||||
private final byte[] remoteSignature = getRandomBytes(MAX_SIGNATURE_BYTES);
|
||||
|
||||
private final IntroduceeProtocolEngine engine =
|
||||
new IntroduceeProtocolEngine(db, clientHelper, contactManager,
|
||||
contactGroupFactory, messageTracker, identityManager,
|
||||
authorManager, messageParser, messageEncoder, crypto,
|
||||
keyManager, transportPropertyManager,
|
||||
clientVersioningManager, autoDeleteManager,
|
||||
conversationManager, clock);
|
||||
|
||||
@Test
|
||||
public void testDoesNotAbortSessionIfTimestampIsMaxAge() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
long remoteAcceptTimestamp = MIN_REASONABLE_TIME_MS;
|
||||
IntroduceeSession session =
|
||||
createAwaitAuthSession(remoteAcceptTimestamp);
|
||||
|
||||
AuthMessage authMessage = new AuthMessage(new MessageId(getRandomId()),
|
||||
contactGroupId, remoteAuthTimestamp, lastRemoteMessageId,
|
||||
sessionId, remoteMac, remoteSignature);
|
||||
|
||||
Message activateMessage = getMessage(contactGroupId, 1234, now);
|
||||
BdfDictionary activateMeta = new BdfDictionary();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Verify the auth message
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localIntroducee));
|
||||
oneOf(crypto).verifyAuthMac(remoteMac, session,
|
||||
localIntroducee.getId());
|
||||
oneOf(crypto).verifySignature(remoteSignature, session);
|
||||
// Add the contact
|
||||
oneOf(contactManager).addContact(txn, remoteIntroducee,
|
||||
localIntroducee.getId(), false);
|
||||
will(returnValue(contactId));
|
||||
oneOf(keyManager).addRotationKeys(txn, contactId, masterKey,
|
||||
remoteAcceptTimestamp, alice, false);
|
||||
will(returnValue(emptyMap()));
|
||||
oneOf(transportPropertyManager).addRemoteProperties(txn, contactId,
|
||||
emptyMap());
|
||||
// Send the activate message
|
||||
oneOf(crypto).activateMac(session);
|
||||
will(returnValue(localMac));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(messageEncoder).encodeActivateMessage(contactGroupId, now,
|
||||
lastLocalMessageId, sessionId, localMac);
|
||||
will(returnValue(activateMessage));
|
||||
oneOf(messageEncoder).encodeMetadata(ACTIVATE, sessionId, now,
|
||||
true, true, false, NO_AUTO_DELETE_TIMER, false);
|
||||
will(returnValue(activateMeta));
|
||||
oneOf(clientHelper).addLocalMessage(txn, activateMessage,
|
||||
activateMeta, true, false);
|
||||
}});
|
||||
|
||||
IntroduceeSession after =
|
||||
engine.onAuthMessage(txn, session, authMessage);
|
||||
|
||||
assertEquals(AWAIT_ACTIVATE, after.getState());
|
||||
assertNull(after.getMasterKey());
|
||||
assertEquals(Collections.<TransportId, KeySetId>emptyMap(),
|
||||
after.getTransportKeys());
|
||||
|
||||
IntroduceeSession.Local afterLocal = after.getLocal();
|
||||
assertEquals(activateMessage.getId(), afterLocal.lastMessageId);
|
||||
assertEquals(now, afterLocal.lastMessageTimestamp);
|
||||
assertNull(afterLocal.ephemeralPublicKey);
|
||||
assertNull(afterLocal.ephemeralPrivateKey);
|
||||
assertArrayEquals(localMacKey.getBytes(), afterLocal.macKey);
|
||||
|
||||
IntroduceeSession.Remote afterRemote = after.getRemote();
|
||||
assertEquals(authMessage.getMessageId(), afterRemote.lastMessageId);
|
||||
assertNull(afterRemote.ephemeralPublicKey);
|
||||
assertArrayEquals(remoteMacKey.getBytes(), afterRemote.macKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbortsSessionIfTimestampIsTooOld() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
long remoteAcceptTimestamp = MIN_REASONABLE_TIME_MS - 1;
|
||||
IntroduceeSession session =
|
||||
createAwaitAuthSession(remoteAcceptTimestamp);
|
||||
|
||||
AuthMessage authMessage = new AuthMessage(new MessageId(getRandomId()),
|
||||
contactGroupId, remoteAuthTimestamp, lastRemoteMessageId,
|
||||
sessionId, remoteMac, remoteSignature);
|
||||
|
||||
BdfDictionary query = new BdfDictionary();
|
||||
Message abortMessage = getMessage(contactGroupId, 123, now);
|
||||
BdfDictionary abortMeta = new BdfDictionary();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Verify the auth message
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localIntroducee));
|
||||
oneOf(crypto).verifyAuthMac(remoteMac, session,
|
||||
localIntroducee.getId());
|
||||
oneOf(crypto).verifySignature(remoteSignature, session);
|
||||
// Abort the session
|
||||
oneOf(messageParser).getRequestsAvailableToAnswerQuery(sessionId);
|
||||
will(returnValue(query));
|
||||
oneOf(clientHelper).getMessageIds(txn, contactGroupId, query);
|
||||
will(returnValue(emptyList()));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(messageEncoder).encodeAbortMessage(contactGroupId, now,
|
||||
lastLocalMessageId, sessionId);
|
||||
will(returnValue(abortMessage));
|
||||
oneOf(messageEncoder).encodeMetadata(ABORT, sessionId, now,
|
||||
true, true, false, NO_AUTO_DELETE_TIMER, false);
|
||||
will(returnValue(abortMeta));
|
||||
oneOf(clientHelper).addLocalMessage(txn, abortMessage, abortMeta,
|
||||
true, false);
|
||||
}});
|
||||
|
||||
IntroduceeSession after =
|
||||
engine.onAuthMessage(txn, session, authMessage);
|
||||
|
||||
assertEquals(START, after.getState());
|
||||
assertNull(after.getMasterKey());
|
||||
assertNull(after.getTransportKeys());
|
||||
|
||||
IntroduceeSession.Local afterLocal = after.getLocal();
|
||||
assertEquals(abortMessage.getId(), afterLocal.lastMessageId);
|
||||
assertEquals(now, afterLocal.lastMessageTimestamp);
|
||||
assertNull(afterLocal.ephemeralPublicKey);
|
||||
assertNull(afterLocal.ephemeralPrivateKey);
|
||||
assertNull(afterLocal.macKey);
|
||||
|
||||
IntroduceeSession.Remote afterRemote = after.getRemote();
|
||||
assertEquals(authMessage.getMessageId(), afterRemote.lastMessageId);
|
||||
assertNull(afterRemote.ephemeralPublicKey);
|
||||
assertNull(afterRemote.macKey);
|
||||
}
|
||||
|
||||
private IntroduceeSession createAwaitAuthSession(
|
||||
long remoteAcceptTimestamp) {
|
||||
IntroduceeSession.Local local = new IntroduceeSession.Local(alice,
|
||||
lastLocalMessageId, localAcceptTimestamp, localPublicKey,
|
||||
localPrivateKey, emptyMap(), localAcceptTimestamp,
|
||||
localMacKey.getBytes());
|
||||
IntroduceeSession.Remote remote = new IntroduceeSession.Remote(!alice,
|
||||
remoteIntroducee, lastRemoteMessageId, remotePublicKey,
|
||||
emptyMap(), remoteAcceptTimestamp, remoteMacKey.getBytes());
|
||||
return new IntroduceeSession(sessionId,
|
||||
AWAIT_AUTH, requestTimestamp, contactGroupId, introducer,
|
||||
local, remote, masterKey.getBytes(), emptyMap());
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@ import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
||||
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
||||
@@ -71,7 +73,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndRead() throws Exception {
|
||||
public void testWriteAndReadWithLazyRetransmission() throws Exception {
|
||||
testWriteAndRead(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndReadWithEagerRetransmission() throws Exception {
|
||||
testWriteAndRead(true);
|
||||
}
|
||||
|
||||
private void testWriteAndRead(boolean eager) throws Exception {
|
||||
// Create the identities
|
||||
Identity aliceIdentity =
|
||||
alice.getIdentityManager().createIdentity("Alice");
|
||||
@@ -86,16 +97,21 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
bob.getEventBus().addListener(listener);
|
||||
// Alice sends a private message to Bob
|
||||
sendMessage(alice, bobId);
|
||||
// Sync Alice's client versions and transport properties
|
||||
read(bob, write(alice, bobId), 2);
|
||||
// Sync Bob's client versions and transport properties
|
||||
read(alice, write(bob, aliceId), 2);
|
||||
// Sync the private message and the attachment
|
||||
read(bob, write(alice, bobId), 2);
|
||||
// Sync Alice's client versions
|
||||
read(bob, write(alice, bobId, eager, 1), 1);
|
||||
// Sync Bob's client versions
|
||||
read(alice, write(bob, aliceId, eager, 1), 1);
|
||||
// Sync Alice's second client versioning update (with the active flag
|
||||
// raised), the private message and the attachment
|
||||
read(bob, write(alice, bobId, eager, 3), 3);
|
||||
// Bob should have received the private message
|
||||
assertTrue(listener.messageAdded);
|
||||
// Bob should have received the attachment
|
||||
assertTrue(listener.attachmentAdded);
|
||||
// Sync messages from Alice to Bob again. If using eager
|
||||
// retransmission, the three unacked messages should be sent again.
|
||||
// They're all duplicates, so no further deliveries should occur
|
||||
read(bob, write(alice, bobId, eager, eager ? 3 : 0), 0);
|
||||
}
|
||||
|
||||
private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
|
||||
@@ -149,15 +165,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
private byte[] write(SimplexMessagingIntegrationTestComponent device,
|
||||
ContactId contactId) throws Exception {
|
||||
ContactId contactId, boolean eager, int transmissions)
|
||||
throws Exception {
|
||||
// Listen for message transmissions
|
||||
MessageTransmissionListener listener =
|
||||
new MessageTransmissionListener(transmissions);
|
||||
device.getEventBus().addListener(listener);
|
||||
// Write the outgoing stream
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestTransportConnectionWriter writer =
|
||||
new TestTransportConnectionWriter(out);
|
||||
new TestTransportConnectionWriter(out, eager);
|
||||
device.getConnectionManager().manageOutgoingConnection(contactId,
|
||||
SIMPLEX_TRANSPORT_ID, writer);
|
||||
// Wait for the writer to be disposed
|
||||
writer.getDisposedLatch().await(TIMEOUT_MS, MILLISECONDS);
|
||||
// Check that the expected number of messages were sent
|
||||
assertTrue(listener.sent.await(TIMEOUT_MS, MILLISECONDS));
|
||||
// Clean up the listener
|
||||
device.getEventBus().removeListener(listener);
|
||||
// Return the contents of the stream
|
||||
return out.toByteArray();
|
||||
}
|
||||
@@ -178,6 +203,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private static class MessageTransmissionListener implements EventListener {
|
||||
|
||||
private final CountDownLatch sent;
|
||||
|
||||
private MessageTransmissionListener(int transmissions) {
|
||||
sent = new CountDownLatch(transmissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessagesSentEvent) {
|
||||
MessagesSentEvent m = (MessagesSentEvent) e;
|
||||
for (MessageId ignored : m.getMessageIds()) sent.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private static class MessageDeliveryListener implements EventListener {
|
||||
|
||||
@@ -191,7 +234,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
||||
if (m.getState().equals(DELIVERED)) delivered.countDown();
|
||||
if (!m.isLocal() && m.getState().equals(DELIVERED)) {
|
||||
delivered.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import javax.annotation.Nullable;
|
||||
import static java.util.Arrays.asList;
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
@@ -275,43 +276,50 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testIncomingFirstInviteMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.INVITEE, INVITE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingFirstJoinMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.PEER, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingInviteMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, INVITE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingJoinMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingJoinMessageForCreator() throws Exception {
|
||||
expectIncomingMessage(Role.CREATOR, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingLeaveMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, LEAVE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingAbortMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, ABORT);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager
|
||||
.incomingMessage(txn, message, body, meta));
|
||||
}
|
||||
|
||||
private void expectFirstIncomingMessage(Role role, MessageType type)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package org.briarproject.briar.test;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
@@ -10,13 +7,8 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.BdfStringUtils;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -25,11 +17,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
||||
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
||||
import org.briarproject.bramble.test.BrambleIntegrationTest;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||
import org.briarproject.briar.api.blog.BlogFactory;
|
||||
@@ -43,48 +31,19 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.Executors.newSingleThreadExecutor;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BriarIntegrationTest<C extends BriarIntegrationTestComponent>
|
||||
extends BriarTestCase {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(BriarIntegrationTest.class.getName());
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
protected final static int TIMEOUT = 15000;
|
||||
extends BrambleIntegrationTest<C> {
|
||||
|
||||
@Nullable
|
||||
protected ContactId contactId1From2, contactId2From1;
|
||||
@@ -94,7 +53,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
contact2From0;
|
||||
protected LocalAuthor author0, author1, author2;
|
||||
protected ContactManager contactManager0, contactManager1, contactManager2;
|
||||
protected IdentityManager identityManager0, identityManager1,
|
||||
private IdentityManager identityManager0, identityManager1,
|
||||
identityManager2;
|
||||
protected DatabaseComponent db0, db1, db2;
|
||||
protected MessageTracker messageTracker0, messageTracker1, messageTracker2;
|
||||
@@ -123,18 +82,8 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
@Inject
|
||||
protected ForumPostFactory forumPostFactory;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile Waiter validationWaiter;
|
||||
private volatile Waiter deliveryWaiter;
|
||||
private volatile Waiter ackWaiter;
|
||||
private volatile boolean expectAck = false;
|
||||
|
||||
protected C c0, c1, c2;
|
||||
|
||||
private final Semaphore messageSemaphore = new Semaphore(0);
|
||||
private final AtomicInteger deliveryCounter = new AtomicInteger(0);
|
||||
private final AtomicInteger validationCounter = new AtomicInteger(0);
|
||||
private final AtomicInteger ackCounter = new AtomicInteger(0);
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final String AUTHOR0 = "Author 0";
|
||||
private final String AUTHOR1 = "Author 1";
|
||||
@@ -148,8 +97,9 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
protected final File t2Dir = new File(testDir, AUTHOR2);
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
assertTrue(testDir.mkdirs());
|
||||
super.setUp();
|
||||
createComponents();
|
||||
|
||||
identityManager0 = c0.getIdentityManager();
|
||||
@@ -165,14 +115,6 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
db1 = c1.getDatabaseComponent();
|
||||
db2 = c2.getDatabaseComponent();
|
||||
|
||||
// initialize waiters fresh for each test
|
||||
validationWaiter = new Waiter();
|
||||
deliveryWaiter = new Waiter();
|
||||
ackWaiter = new Waiter();
|
||||
deliveryCounter.set(0);
|
||||
validationCounter.set(0);
|
||||
ackCounter.set(0);
|
||||
|
||||
createAndRegisterIdentities();
|
||||
startLifecycles();
|
||||
listenToEvents();
|
||||
@@ -195,68 +137,9 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
}
|
||||
|
||||
private void listenToEvents() {
|
||||
Listener listener0 = new Listener(c0);
|
||||
c0.getEventBus().addListener(listener0);
|
||||
Listener listener1 = new Listener(c1);
|
||||
c1.getEventBus().addListener(listener1);
|
||||
Listener listener2 = new Listener(c2);
|
||||
c2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
private class Listener implements EventListener {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
private final Executor executor;
|
||||
|
||||
private Listener(C c) {
|
||||
clientHelper = c.getClientHelper();
|
||||
executor = newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent event = (MessageStateChangedEvent) e;
|
||||
if (!event.isLocal()) {
|
||||
if (event.getState() == DELIVERED) {
|
||||
LOG.info("Delivered new message "
|
||||
+ event.getMessageId());
|
||||
deliveryCounter.addAndGet(1);
|
||||
loadAndLogMessage(event.getMessageId());
|
||||
deliveryWaiter.resume();
|
||||
} else if (event.getState() == INVALID ||
|
||||
event.getState() == PENDING) {
|
||||
LOG.info("Validated new " + event.getState().name() +
|
||||
" message " + event.getMessageId());
|
||||
validationCounter.addAndGet(1);
|
||||
loadAndLogMessage(event.getMessageId());
|
||||
validationWaiter.resume();
|
||||
}
|
||||
}
|
||||
} else if (e instanceof MessagesAckedEvent && expectAck) {
|
||||
MessagesAckedEvent event = (MessagesAckedEvent) e;
|
||||
ackCounter.addAndGet(event.getMessageIds().size());
|
||||
for (MessageId m : event.getMessageIds()) {
|
||||
loadAndLogMessage(m);
|
||||
ackWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAndLogMessage(MessageId id) {
|
||||
executor.execute(() -> {
|
||||
if (DEBUG) {
|
||||
try {
|
||||
BdfList body = clientHelper.getMessageAsList(id);
|
||||
LOG.info("Contents of " + id + ":\n"
|
||||
+ BdfStringUtils.toString(body));
|
||||
} catch (DbException | FormatException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
messageSemaphore.release();
|
||||
});
|
||||
}
|
||||
addEventListener(c0);
|
||||
addEventListener(c1);
|
||||
addEventListener(c2);
|
||||
}
|
||||
|
||||
private void createAndRegisterIdentities() {
|
||||
@@ -335,9 +218,10 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
}
|
||||
|
||||
@After
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
stopLifecycles();
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void stopLifecycles() throws InterruptedException {
|
||||
@@ -402,122 +286,6 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
sendAcks(c1, c2, contactId2From1, num);
|
||||
}
|
||||
|
||||
protected void syncMessage(BriarIntegrationTestComponent fromComponent,
|
||||
BriarIntegrationTestComponent toComponent, ContactId toId, int num,
|
||||
boolean valid) throws Exception {
|
||||
syncMessage(fromComponent, toComponent, toId, num, 0, valid ? 0 : num,
|
||||
valid ? num : 0);
|
||||
}
|
||||
|
||||
protected void syncMessage(BriarIntegrationTestComponent fromComponent,
|
||||
BriarIntegrationTestComponent toComponent, ContactId toId,
|
||||
int numNew, int numDupes, int numPendingOrInvalid, int numDelivered)
|
||||
throws Exception {
|
||||
|
||||
// Debug output
|
||||
String from =
|
||||
fromComponent.getIdentityManager().getLocalAuthor().getName();
|
||||
String to = toComponent.getIdentityManager().getLocalAuthor().getName();
|
||||
LOG.info("TEST: Sending " + (numNew + numDupes) + " message(s) from "
|
||||
+ from + " to " + to);
|
||||
|
||||
// Listen for messages being sent
|
||||
waitForEvents(fromComponent);
|
||||
SendListener sendListener = new SendListener();
|
||||
fromComponent.getEventBus().addListener(sendListener);
|
||||
|
||||
// Write the messages to a transport stream
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestTransportConnectionWriter writer =
|
||||
new TestTransportConnectionWriter(out);
|
||||
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
||||
SIMPLEX_TRANSPORT_ID, writer);
|
||||
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
||||
|
||||
// Check that the expected number of messages were sent
|
||||
waitForEvents(fromComponent);
|
||||
fromComponent.getEventBus().removeListener(sendListener);
|
||||
assertEquals("Messages sent", numNew + numDupes,
|
||||
sendListener.sent.size());
|
||||
|
||||
// Read the messages from the transport stream
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
TestTransportConnectionReader reader =
|
||||
new TestTransportConnectionReader(in);
|
||||
toComponent.getConnectionManager().manageIncomingConnection(
|
||||
SIMPLEX_TRANSPORT_ID, reader);
|
||||
|
||||
if (numPendingOrInvalid > 0) {
|
||||
validationWaiter.await(TIMEOUT, numPendingOrInvalid);
|
||||
}
|
||||
assertEquals("Messages validated", numPendingOrInvalid,
|
||||
validationCounter.getAndSet(0));
|
||||
|
||||
if (numDelivered > 0) {
|
||||
deliveryWaiter.await(TIMEOUT, numDelivered);
|
||||
}
|
||||
assertEquals("Messages delivered", numDelivered,
|
||||
deliveryCounter.getAndSet(0));
|
||||
|
||||
try {
|
||||
messageSemaphore.tryAcquire(numNew, TIMEOUT, MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for messages");
|
||||
Thread.currentThread().interrupt();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendAcks(BriarIntegrationTestComponent fromComponent,
|
||||
BriarIntegrationTestComponent toComponent, ContactId toId, int num)
|
||||
throws Exception {
|
||||
// Debug output
|
||||
String from =
|
||||
fromComponent.getIdentityManager().getLocalAuthor().getName();
|
||||
String to = toComponent.getIdentityManager().getLocalAuthor().getName();
|
||||
LOG.info("TEST: Sending " + num + " ACKs from " + from + " to " + to);
|
||||
|
||||
expectAck = true;
|
||||
|
||||
// Listen for messages being sent (none should be sent)
|
||||
waitForEvents(fromComponent);
|
||||
SendListener sendListener = new SendListener();
|
||||
fromComponent.getEventBus().addListener(sendListener);
|
||||
|
||||
// start outgoing connection
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestTransportConnectionWriter writer =
|
||||
new TestTransportConnectionWriter(out);
|
||||
fromComponent.getConnectionManager().manageOutgoingConnection(toId,
|
||||
SIMPLEX_TRANSPORT_ID, writer);
|
||||
writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS);
|
||||
|
||||
// Check that no messages were sent
|
||||
waitForEvents(fromComponent);
|
||||
fromComponent.getEventBus().removeListener(sendListener);
|
||||
assertEquals("Messages sent", 0, sendListener.sent.size());
|
||||
|
||||
// handle incoming connection
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
TestTransportConnectionReader reader =
|
||||
new TestTransportConnectionReader(in);
|
||||
toComponent.getConnectionManager().manageIncomingConnection(
|
||||
SIMPLEX_TRANSPORT_ID, reader);
|
||||
|
||||
ackWaiter.await(TIMEOUT, num);
|
||||
assertEquals("ACKs delivered", num, ackCounter.getAndSet(0));
|
||||
assertEquals("No messages delivered", 0, deliveryCounter.get());
|
||||
try {
|
||||
messageSemaphore.tryAcquire(num, TIMEOUT, MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for messages");
|
||||
Thread.currentThread().interrupt();
|
||||
fail();
|
||||
} finally {
|
||||
expectAck = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeAllContacts() throws DbException {
|
||||
contactManager0.removeContact(contactId1From0);
|
||||
contactManager0.removeContact(contactId2From0);
|
||||
@@ -562,40 +330,4 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
|
||||
db.transaction(false, txn -> db.setMessageShared(txn, messageId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a marker event and waits for it to be delivered, which
|
||||
* indicates that all previously broadcast events have been delivered.
|
||||
*/
|
||||
public static void waitForEvents(BriarIntegrationTestComponent component)
|
||||
throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MarkerEvent marker = new MarkerEvent();
|
||||
EventBus eventBus = component.getEventBus();
|
||||
eventBus.addListener(new EventListener() {
|
||||
@Override
|
||||
public void eventOccurred(@Nonnull Event e) {
|
||||
if (e == marker) {
|
||||
latch.countDown();
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
eventBus.broadcast(marker);
|
||||
if (!latch.await(1, MINUTES)) fail();
|
||||
}
|
||||
|
||||
private static class MarkerEvent extends Event {
|
||||
}
|
||||
|
||||
private static class SendListener implements EventListener {
|
||||
|
||||
private final Set<MessageId> sent = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessagesSentEvent) {
|
||||
sent.addAll(((MessagesSentEvent) e).getMessageIds());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,14 @@ package org.briarproject.briar.test;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
|
||||
import org.briarproject.bramble.test.BrambleIntegrationTestComponent;
|
||||
import org.briarproject.bramble.test.TimeTravel;
|
||||
import org.briarproject.briar.api.attachment.AttachmentReader;
|
||||
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
|
||||
@@ -67,7 +64,7 @@ import dagger.Component;
|
||||
SharingModule.class
|
||||
})
|
||||
public interface BriarIntegrationTestComponent
|
||||
extends BrambleCoreIntegrationTestEagerSingletons {
|
||||
extends BrambleIntegrationTestComponent {
|
||||
|
||||
void inject(BriarIntegrationTest<BriarIntegrationTestComponent> init);
|
||||
|
||||
@@ -95,16 +92,10 @@ public interface BriarIntegrationTestComponent
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
|
||||
EventBus getEventBus();
|
||||
|
||||
IdentityManager getIdentityManager();
|
||||
|
||||
AttachmentReader getAttachmentReader();
|
||||
|
||||
AvatarManager getAvatarManager();
|
||||
|
||||
ClientHelper getClientHelper();
|
||||
|
||||
ContactManager getContactManager();
|
||||
|
||||
ConversationManager getConversationManager();
|
||||
@@ -139,8 +130,6 @@ public interface BriarIntegrationTestComponent
|
||||
|
||||
BlogFactory getBlogFactory();
|
||||
|
||||
ConnectionManager getConnectionManager();
|
||||
|
||||
AutoDeleteManager getAutoDeleteManager();
|
||||
|
||||
Clock getClock();
|
||||
|
||||
Reference in New Issue
Block a user