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:
Torsten Grote
2021-07-06 17:25:27 -03:00
182 changed files with 7584 additions and 1098 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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;
}
/**

View File

@@ -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

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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)

View File

@@ -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());
}
}
}
}

View File

@@ -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();