mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Merge branch '1585-new-messaging-client' into 'master'
Add support for image attachments to messaging client Closes #1585 See merge request briar/briar!1133
This commit is contained in:
@@ -25,7 +25,10 @@ public interface ClientHelper {
|
|||||||
throws DbException, FormatException;
|
throws DbException, FormatException;
|
||||||
|
|
||||||
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
|
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
|
||||||
boolean shared) throws DbException, FormatException;
|
boolean shared, boolean temporary)
|
||||||
|
throws DbException, FormatException;
|
||||||
|
|
||||||
|
Message createMessage(GroupId g, long timestamp, byte[] body);
|
||||||
|
|
||||||
Message createMessage(GroupId g, long timestamp, BdfList body)
|
Message createMessage(GroupId g, long timestamp, BdfList body)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
@@ -108,7 +111,7 @@ public interface ClientHelper {
|
|||||||
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
||||||
|
|
||||||
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
|
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
|
|
||||||
TransportProperties parseAndValidateTransportProperties(
|
TransportProperties parseAndValidateTransportProperties(
|
||||||
BdfDictionary properties) throws FormatException;
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|||||||
@@ -85,15 +85,21 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(Message m, BdfDictionary metadata,
|
public void addLocalMessage(Message m, BdfDictionary metadata,
|
||||||
boolean shared) throws DbException, FormatException {
|
boolean shared) throws DbException, FormatException {
|
||||||
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared));
|
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared,
|
||||||
|
false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(Transaction txn, Message m,
|
public void addLocalMessage(Transaction txn, Message m,
|
||||||
BdfDictionary metadata, boolean shared)
|
BdfDictionary metadata, boolean shared, boolean temporary)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared,
|
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared,
|
||||||
false);
|
temporary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
||||||
|
return messageFactory.createMessage(g, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
meta.put("transportId", t.getString());
|
meta.put("transportId", t.getString());
|
||||||
meta.put("version", version);
|
meta.put("version", version);
|
||||||
meta.put("local", local);
|
meta.put("local", local);
|
||||||
clientHelper.addLocalMessage(txn, m, meta, shared);
|
clientHelper.addLocalMessage(txn, m, meta, shared, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
try {
|
try {
|
||||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
|
logException(LOG, INFO, e);
|
||||||
invalidateMessage(txn, m.getId());
|
invalidateMessage(txn, m.getId());
|
||||||
return new DeliveryResult(false, false);
|
return new DeliveryResult(false, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
||||||
meta.put(MSG_KEY_LOCAL, true);
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -637,7 +637,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(timestamp));
|
will(returnValue(timestamp));
|
||||||
oneOf(clientHelper).createMessage(g, timestamp, body);
|
oneOf(clientHelper).createMessage(g, timestamp, body);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, shared);
|
oneOf(clientHelper).addLocalMessage(txn, message, meta, shared,
|
||||||
|
false);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.briarproject.bramble.test;
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
||||||
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
DefaultBatteryManagerModule.class,
|
DefaultBatteryManagerModule.class,
|
||||||
@@ -13,4 +15,20 @@ import dagger.Module;
|
|||||||
TestSecureRandomModule.class
|
TestSecureRandomModule.class
|
||||||
})
|
})
|
||||||
public class BrambleCoreIntegrationTestModule {
|
public class BrambleCoreIntegrationTestModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
FeatureFlags provideFeatureFlags() {
|
||||||
|
return new FeatureFlags() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableImageAttachments() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableRemoteContacts() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
localUpdateBody);
|
localUpdateBody);
|
||||||
will(returnValue(localUpdate));
|
will(returnValue(localUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
|
||||||
localUpdateMeta, true);
|
localUpdateMeta, true, false);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// No visibilities have changed
|
// No visibilities have changed
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
||||||
}});
|
}});
|
||||||
@@ -567,7 +567,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
@@ -640,7 +640,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.Pair;
|
|||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
@@ -46,7 +45,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||||
@@ -65,10 +63,9 @@ import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
|||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.android.view.ImagePreview;
|
import org.briarproject.briar.android.view.ImagePreview;
|
||||||
import org.briarproject.briar.android.view.TextAttachmentController;
|
import org.briarproject.briar.android.view.TextAttachmentController;
|
||||||
import org.briarproject.briar.android.view.TextAttachmentController.AttachImageListener;
|
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentListener;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
@@ -83,7 +80,6 @@ import org.briarproject.briar.api.introduction.IntroductionManager;
|
|||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
|
|
||||||
@@ -94,7 +90,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -129,6 +124,7 @@ import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
|||||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||||
@@ -136,8 +132,8 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
|
|||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ConversationActivity extends BriarActivity
|
public class ConversationActivity extends BriarActivity
|
||||||
implements EventListener, ConversationListener, SendListener,
|
implements EventListener, ConversationListener, TextCache,
|
||||||
TextCache, AttachmentCache, AttachImageListener {
|
AttachmentCache, AttachmentListener {
|
||||||
|
|
||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
@@ -152,9 +148,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
ConnectionRegistry connectionRegistry;
|
ConnectionRegistry connectionRegistry;
|
||||||
@Inject
|
@Inject
|
||||||
@CryptoExecutor
|
|
||||||
Executor cryptoExecutor;
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@Inject
|
@Inject
|
||||||
FeatureFlags featureFlags;
|
FeatureFlags featureFlags;
|
||||||
@@ -169,10 +162,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
volatile EventBus eventBus;
|
volatile EventBus eventBus;
|
||||||
@Inject
|
@Inject
|
||||||
volatile SettingsManager settingsManager;
|
|
||||||
@Inject
|
|
||||||
volatile PrivateMessageFactory privateMessageFactory;
|
|
||||||
@Inject
|
|
||||||
volatile IntroductionManager introductionManager;
|
volatile IntroductionManager introductionManager;
|
||||||
@Inject
|
@Inject
|
||||||
volatile ForumSharingManager forumSharingManager;
|
volatile ForumSharingManager forumSharingManager;
|
||||||
@@ -267,10 +256,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (featureFlags.shouldEnableImageAttachments()) {
|
if (featureFlags.shouldEnableImageAttachments()) {
|
||||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||||
sendController = new TextAttachmentController(textInputView,
|
sendController = new TextAttachmentController(textInputView,
|
||||||
imagePreview, this, this, viewModel);
|
imagePreview, this, viewModel);
|
||||||
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||||
if (hasSupport != null && hasSupport) {
|
if (hasSupport != null && hasSupport) {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// TODO: remove cast when removing feature flag
|
||||||
((TextAttachmentController) sendController)
|
((TextAttachmentController) sendController)
|
||||||
.setImagesSupported();
|
.setImagesSupported();
|
||||||
}
|
}
|
||||||
@@ -305,7 +294,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
Snackbar.LENGTH_SHORT)
|
Snackbar.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
|
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// TODO: remove cast when removing feature flag
|
||||||
((TextAttachmentController) sendController).onImageReceived(data);
|
((TextAttachmentController) sendController).onImageReceived(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,7 +443,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (text == null) {
|
if (text == null) {
|
||||||
LOG.info("Eagerly loading text for latest message");
|
LOG.info("Eagerly loading text for latest message");
|
||||||
text = messagingManager.getMessageText(id);
|
text = messagingManager.getMessageText(id);
|
||||||
textCache.put(id, text);
|
textCache.put(id, requireNonNull(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the message has a single image, load its size - for multiple
|
// If the message has a single image, load its size - for multiple
|
||||||
@@ -478,8 +467,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
textInputView.setReady(true);
|
textInputView.setReady(true);
|
||||||
// start observing onboarding after enabling
|
// start observing onboarding after enabling
|
||||||
viewModel.showImageOnboarding().observeEvent(this,
|
if (featureFlags.shouldEnableImageAttachments()) {
|
||||||
this::showImageOnboarding);
|
viewModel.showImageOnboarding().observeEvent(this,
|
||||||
|
this::showImageOnboarding);
|
||||||
|
}
|
||||||
List<ConversationItem> items = createItems(headers);
|
List<ConversationItem> items = createItems(headers);
|
||||||
adapter.addAll(items);
|
adapter.addAll(items);
|
||||||
list.showData();
|
list.showData();
|
||||||
@@ -515,7 +506,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
long start = now();
|
long start = now();
|
||||||
String text = messagingManager.getMessageText(m);
|
String text = messagingManager.getMessageText(m);
|
||||||
logDuration(LOG, "Loading text", start);
|
logDuration(LOG, "Loading text", start);
|
||||||
displayMessageText(m, text);
|
displayMessageText(m, requireNonNull(text));
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -660,6 +651,14 @@ public class ConversationActivity extends BriarActivity
|
|||||||
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
|
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTooManyAttachments() {
|
||||||
|
String format = getResources().getString(
|
||||||
|
R.string.messaging_too_many_attachments_toast);
|
||||||
|
String warning = String.format(format, MAX_ATTACHMENTS_PER_MESSAGE);
|
||||||
|
Toast.makeText(this, warning, LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text,
|
public void onSendClick(@Nullable String text,
|
||||||
List<AttachmentHeader> attachmentHeaders) {
|
List<AttachmentHeader> attachmentHeaders) {
|
||||||
@@ -729,7 +728,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showImageOnboarding() {
|
private void showImageOnboarding() {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// TODO: remove cast when removing feature flag
|
||||||
((TextAttachmentController) sendController)
|
((TextAttachmentController) sendController)
|
||||||
.showImageOnboarding(this, () ->
|
.showImageOnboarding(this, () ->
|
||||||
viewModel.onImageOnboardingSeen());
|
viewModel.onImageOnboardingSeen());
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
|
|
||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(ConversationViewModel.class.getName());
|
getLogger(ConversationViewModel.class.getName());
|
||||||
|
|
||||||
private static final String SHOW_ONBOARDING_IMAGE =
|
private static final String SHOW_ONBOARDING_IMAGE =
|
||||||
"showOnboardingImage";
|
"showOnboardingImage";
|
||||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||||
@@ -181,12 +182,17 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
void sendMessage(@Nullable String text,
|
void sendMessage(@Nullable String text,
|
||||||
List<AttachmentHeader> attachmentHeaders, long timestamp) {
|
List<AttachmentHeader> headers, long timestamp) {
|
||||||
// messagingGroupId is loaded with the contact
|
// messagingGroupId is loaded with the contact
|
||||||
observeForeverOnce(messagingGroupId, groupId -> {
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
if (groupId == null) throw new IllegalStateException();
|
requireNonNull(groupId);
|
||||||
createMessage(groupId, text, attachmentHeaders, timestamp);
|
observeForeverOnce(imageSupport, hasImageSupport -> {
|
||||||
|
requireNonNull(hasImageSupport);
|
||||||
|
createMessage(groupId, text, headers, timestamp,
|
||||||
|
hasImageSupport);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,21 +276,24 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createMessage(GroupId groupId, @Nullable String text,
|
private void createMessage(GroupId groupId, @Nullable String text,
|
||||||
List<AttachmentHeader> attachments, long timestamp) {
|
List<AttachmentHeader> headers, long timestamp,
|
||||||
|
boolean hasImageSupport) {
|
||||||
try {
|
try {
|
||||||
// TODO remove when text can be null in the backend
|
PrivateMessage pm;
|
||||||
String msgText = text == null ? "null" : text;
|
if (hasImageSupport) {
|
||||||
PrivateMessage pm = privateMessageFactory
|
pm = privateMessageFactory.createPrivateMessage(groupId,
|
||||||
.createPrivateMessage(groupId, timestamp, msgText,
|
timestamp, text, headers);
|
||||||
attachments);
|
} else {
|
||||||
storeMessage(pm, msgText, attachments);
|
pm = privateMessageFactory.createLegacyPrivateMessage(
|
||||||
|
groupId, timestamp, requireNonNull(text));
|
||||||
|
}
|
||||||
|
storeMessage(pm);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(PrivateMessage m, @Nullable String text,
|
private void storeMessage(PrivateMessage m) {
|
||||||
List<AttachmentHeader> attachments) {
|
|
||||||
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -295,7 +304,7 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||||
message.getId(), message.getGroupId(),
|
message.getId(), message.getGroupId(),
|
||||||
message.getTimestamp(), true, true, false, false,
|
message.getTimestamp(), true, true, false, false,
|
||||||
text != null, attachments);
|
m.hasText(), m.getAttachmentHeaders());
|
||||||
// TODO add text to cache when available here
|
// TODO add text to cache when available here
|
||||||
addedHeader.postEvent(h);
|
addedHeader.postEvent(h);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ import static android.support.v4.content.ContextCompat.getColor;
|
|||||||
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ public class TextAttachmentController extends TextSendController
|
|||||||
implements ImagePreviewListener {
|
implements ImagePreviewListener {
|
||||||
|
|
||||||
private final ImagePreview imagePreview;
|
private final ImagePreview imagePreview;
|
||||||
private final AttachImageListener imageListener;
|
private final AttachmentListener attachmentListener;
|
||||||
private final CompositeSendButton sendButton;
|
private final CompositeSendButton sendButton;
|
||||||
private final AttachmentManager attachmentManager;
|
private final AttachmentManager attachmentManager;
|
||||||
|
|
||||||
@@ -62,10 +62,10 @@ public class TextAttachmentController extends TextSendController
|
|||||||
private boolean loadingUris = false;
|
private boolean loadingUris = false;
|
||||||
|
|
||||||
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
||||||
SendListener listener, AttachImageListener imageListener,
|
AttachmentListener attachmentListener,
|
||||||
AttachmentManager attachmentManager) {
|
AttachmentManager attachmentManager) {
|
||||||
super(v, listener, false);
|
super(v, attachmentListener, false);
|
||||||
this.imageListener = imageListener;
|
this.attachmentListener = attachmentListener;
|
||||||
this.imagePreview = imagePreview;
|
this.imagePreview = imagePreview;
|
||||||
this.attachmentManager = attachmentManager;
|
this.attachmentManager = attachmentManager;
|
||||||
this.imagePreview.setImagePreviewListener(this);
|
this.imagePreview.setImagePreviewListener(this);
|
||||||
@@ -124,8 +124,8 @@ public class TextAttachmentController extends TextSendController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Intent intent = getAttachFileIntent();
|
Intent intent = getAttachFileIntent();
|
||||||
if (imageListener.getLifecycle().getCurrentState() != DESTROYED) {
|
if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) {
|
||||||
requireNonNull(imageListener).onAttachImage(intent);
|
attachmentListener.onAttachImage(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,30 +144,38 @@ public class TextAttachmentController extends TextSendController
|
|||||||
* returned by the Activity started with {@link #getAttachFileIntent()}.
|
* returned by the Activity started with {@link #getAttachFileIntent()}.
|
||||||
* <p>
|
* <p>
|
||||||
* This method must be called at most once per call to
|
* This method must be called at most once per call to
|
||||||
* {@link AttachImageListener#onAttachImage(Intent)}.
|
* {@link AttachmentListener#onAttachImage(Intent)}.
|
||||||
* Normally, this is true if called from
|
* Normally, this is true if called from
|
||||||
* {@link Activity#onActivityResult(int, int, Intent)} since this is called
|
* {@link Activity#onActivityResult(int, int, Intent)} since this is called
|
||||||
* at most once per call to {@link Activity#startActivityForResult(Intent, int)}.
|
* at most once per call to
|
||||||
|
* {@link Activity#startActivityForResult(Intent, int)}.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("JavadocReference")
|
||||||
public void onImageReceived(@Nullable Intent resultData) {
|
public void onImageReceived(@Nullable Intent resultData) {
|
||||||
if (resultData == null) return;
|
if (resultData == null) return;
|
||||||
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
|
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
|
||||||
|
List<Uri> newUris = new ArrayList<>();
|
||||||
if (resultData.getData() != null) {
|
if (resultData.getData() != null) {
|
||||||
imageUris.add(resultData.getData());
|
newUris.add(resultData.getData());
|
||||||
onNewUris(false);
|
onNewUris(false, newUris);
|
||||||
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
||||||
ClipData clipData = resultData.getClipData();
|
ClipData clipData = resultData.getClipData();
|
||||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||||
imageUris.add(clipData.getItemAt(i).getUri());
|
newUris.add(clipData.getItemAt(i).getUri());
|
||||||
}
|
}
|
||||||
onNewUris(false);
|
onNewUris(false, newUris);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewUris(boolean restart) {
|
private void onNewUris(boolean restart, List<Uri> newUris) {
|
||||||
if (imageUris.isEmpty()) return;
|
if (newUris.isEmpty()) return;
|
||||||
if (loadingUris) throw new AssertionError();
|
if (loadingUris) throw new AssertionError();
|
||||||
loadingUris = true;
|
loadingUris = true;
|
||||||
|
if (newUris.size() > MAX_ATTACHMENTS_PER_MESSAGE) {
|
||||||
|
newUris = newUris.subList(0, MAX_ATTACHMENTS_PER_MESSAGE);
|
||||||
|
attachmentListener.onTooManyAttachments();
|
||||||
|
}
|
||||||
|
imageUris.addAll(newUris);
|
||||||
updateViewState();
|
updateViewState();
|
||||||
textInput.setHint(R.string.image_caption_hint);
|
textInput.setHint(R.string.image_caption_hint);
|
||||||
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
|
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
|
||||||
@@ -175,7 +183,7 @@ public class TextAttachmentController extends TextSendController
|
|||||||
// store attachments and show preview when successful
|
// store attachments and show preview when successful
|
||||||
LiveData<AttachmentResult> result =
|
LiveData<AttachmentResult> result =
|
||||||
attachmentManager.storeAttachments(imageUris, restart);
|
attachmentManager.storeAttachments(imageUris, restart);
|
||||||
result.observe(imageListener, new Observer<AttachmentResult>() {
|
result.observe(attachmentListener, new Observer<AttachmentResult>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(@Nullable AttachmentResult attachmentResult) {
|
public void onChanged(@Nullable AttachmentResult attachmentResult) {
|
||||||
if (attachmentResult == null) {
|
if (attachmentResult == null) {
|
||||||
@@ -240,8 +248,7 @@ public class TextAttachmentController extends TextSendController
|
|||||||
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
||||||
SavedState state = (SavedState) inState;
|
SavedState state = (SavedState) inState;
|
||||||
if (!imageUris.isEmpty()) throw new AssertionError();
|
if (!imageUris.isEmpty()) throw new AssertionError();
|
||||||
if (state.imageUris != null) imageUris.addAll(state.imageUris);
|
if (state.imageUris != null) onNewUris(true, state.imageUris);
|
||||||
onNewUris(true);
|
|
||||||
return state.getSuperState();
|
return state.getSuperState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,8 +323,11 @@ public class TextAttachmentController extends TextSendController
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface AttachImageListener extends LifecycleOwner {
|
@UiThread
|
||||||
void onAttachImage(Intent intent);
|
public interface AttachmentListener extends SendListener, LifecycleOwner {
|
||||||
}
|
|
||||||
|
|
||||||
|
void onAttachImage(Intent intent);
|
||||||
|
|
||||||
|
void onTooManyAttachments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ public class TextSendController implements TextInputListener {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
public interface SendListener {
|
public interface SendListener {
|
||||||
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
|
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@
|
|||||||
<string name="dialog_message_no_image_support">Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon.</string>
|
<string name="dialog_message_no_image_support">Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon.</string>
|
||||||
<string name="dialog_title_image_support">You can now send images to this contact</string>
|
<string name="dialog_title_image_support">You can now send images to this contact</string>
|
||||||
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
|
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
|
||||||
|
<string name="messaging_too_many_attachments_toast">Only the first %d images will be sent</string>
|
||||||
|
|
||||||
<!-- Adding Contacts -->
|
<!-- Adding Contacts -->
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,30 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public abstract class ThreadedMessage extends PrivateMessage {
|
public abstract class ThreadedMessage {
|
||||||
|
|
||||||
|
private final Message message;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final MessageId parent;
|
private final MessageId parent;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
|
|
||||||
public ThreadedMessage(Message message, @Nullable MessageId parent,
|
public ThreadedMessage(Message message, @Nullable MessageId parent,
|
||||||
Author author) {
|
Author author) {
|
||||||
super(message);
|
this.message = message;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Message getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MessageId getParent() {
|
public MessageId getParent() {
|
||||||
return parent;
|
return parent;
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ public abstract class ConversationMessageReceivedEvent<H extends ConversationMes
|
|||||||
private final H messageHeader;
|
private final H messageHeader;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
|
|
||||||
public ConversationMessageReceivedEvent(H messageHeader, ContactId contactId) {
|
public ConversationMessageReceivedEvent(H messageHeader,
|
||||||
|
ContactId contactId) {
|
||||||
this.messageHeader = messageHeader;
|
this.messageHeader = messageHeader;
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ public interface MessagingConstants {
|
|||||||
*/
|
*/
|
||||||
int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of attachments per private message.
|
||||||
|
*/
|
||||||
|
int MAX_ATTACHMENTS_PER_MESSAGE = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of an attachment's content type in UTF-8 bytes.
|
||||||
|
*/
|
||||||
|
int MAX_CONTENT_TYPE_BYTES = 50;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The supported mime types for image attachments.
|
* The supported mime types for image attachments.
|
||||||
*/
|
*/
|
||||||
@@ -22,6 +32,6 @@ public interface MessagingConstants {
|
|||||||
* The maximum allowed size of image attachments.
|
* The maximum allowed size of image attachments.
|
||||||
* TODO: Different limit for GIFs?
|
* TODO: Different limit for GIFs?
|
||||||
*/
|
*/
|
||||||
int MAX_IMAGE_SIZE = MAX_MESSAGE_BODY_LENGTH; // 6 * 1024 * 1024;
|
int MAX_IMAGE_SIZE = MAX_MESSAGE_BODY_LENGTH - 100; // 6 * 1024 * 1024;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import org.briarproject.briar.api.conversation.ConversationManager.ConversationC
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface MessagingManager extends ConversationClient {
|
public interface MessagingManager extends ConversationClient {
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the messaging client.
|
* The current minor version of the messaging client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 0;
|
int MINOR_VERSION = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a local private message.
|
* Stores a local private message.
|
||||||
@@ -59,8 +61,10 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
GroupId getConversationId(ContactId c) throws DbException;
|
GroupId getConversationId(ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text of the private message with the given ID.
|
* Returns the text of the private message with the given ID, or null if
|
||||||
|
* the private message has no text.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
String getMessageText(MessageId m) throws DbException;
|
String getMessageText(MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,20 +3,56 @@ package org.briarproject.briar.api.messaging;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class PrivateMessage {
|
public class PrivateMessage {
|
||||||
|
|
||||||
private final Message message;
|
private final Message message;
|
||||||
|
private final boolean legacyFormat, hasText;
|
||||||
|
private final List<AttachmentHeader> attachmentHeaders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for private messages in the legacy format, which does not
|
||||||
|
* support attachments.
|
||||||
|
*/
|
||||||
public PrivateMessage(Message message) {
|
public PrivateMessage(Message message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
legacyFormat = true;
|
||||||
|
hasText = true;
|
||||||
|
attachmentHeaders = emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for private messages in the current format, which supports
|
||||||
|
* attachments.
|
||||||
|
*/
|
||||||
|
public PrivateMessage(Message message, boolean hasText,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
|
this.message = message;
|
||||||
|
this.hasText = hasText;
|
||||||
|
this.attachmentHeaders = headers;
|
||||||
|
legacyFormat = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message getMessage() {
|
public Message getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLegacyFormat() {
|
||||||
|
return legacyFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasText() {
|
||||||
|
return hasText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AttachmentHeader> getAttachmentHeaders() {
|
||||||
|
return attachmentHeaders;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface PrivateMessageFactory {
|
public interface PrivateMessageFactory {
|
||||||
|
|
||||||
|
PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp,
|
||||||
|
String text) throws FormatException;
|
||||||
|
|
||||||
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
String text, List<AttachmentHeader> attachments)
|
@Nullable String text, List<AttachmentHeader> headers)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
|
|||||||
|
|
||||||
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
||||||
boolean local, boolean read, boolean sent, boolean seen,
|
boolean local, boolean read, boolean sent, boolean seen,
|
||||||
boolean hasText, List<AttachmentHeader> attachmentHeaders) {
|
boolean hasText, List<AttachmentHeader> headers) {
|
||||||
super(id, groupId, timestamp, local, read, sent, seen);
|
super(id, groupId, timestamp, local, read, sent, seen);
|
||||||
this.hasText = hasText;
|
this.hasText = hasText;
|
||||||
this.attachmentHeaders = attachmentHeaders;
|
this.attachmentHeaders = headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasText() {
|
public boolean hasText() {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.briarproject.briar.api.messaging.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when a new attachment is received.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentReceivedEvent extends Event {
|
||||||
|
|
||||||
|
private final MessageId messageId;
|
||||||
|
private final ContactId contactId;
|
||||||
|
|
||||||
|
public AttachmentReceivedEvent(MessageId messageId, ContactId contactId) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.contactId = contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageId getMessageId() {
|
||||||
|
return messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactId getContactId() {
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.api.test;
|
package org.briarproject.briar.api.test;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
@@ -24,9 +23,4 @@ public interface TestDataCreator {
|
|||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
Contact addContact(String name) throws DbException;
|
Contact addContact(String name) throws DbException;
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void addPrivateMessage(Contact contact, String text, long time,
|
|
||||||
boolean local) throws DbException, FormatException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
meta.put(KEY_AUTHOR, clientHelper.toList(p.getAuthor()));
|
meta.put(KEY_AUTHOR, clientHelper.toList(p.getAuthor()));
|
||||||
meta.put(KEY_READ, true);
|
meta.put(KEY_READ, true);
|
||||||
meta.put(KEY_RSS_FEED, b.isRssFeed());
|
meta.put(KEY_RSS_FEED, b.isRssFeed());
|
||||||
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
|
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true,
|
||||||
|
false);
|
||||||
|
|
||||||
// broadcast event about new post
|
// broadcast event about new post
|
||||||
MessageId postId = p.getMessage().getId();
|
MessageId postId = p.getMessage().getId();
|
||||||
@@ -279,7 +280,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
meta.put(KEY_AUTHOR, clientHelper.toList(author));
|
meta.put(KEY_AUTHOR, clientHelper.toList(author));
|
||||||
|
|
||||||
// Send comment
|
// Send comment
|
||||||
clientHelper.addLocalMessage(txn, message, meta, true);
|
clientHelper.addLocalMessage(txn, message, meta, true, false);
|
||||||
|
|
||||||
// broadcast event
|
// broadcast event
|
||||||
BlogPostHeader h = getPostHeaderFromMetadata(txn, groupId,
|
BlogPostHeader h = getPostHeaderFromMetadata(txn, groupId,
|
||||||
@@ -377,7 +378,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
meta.put(KEY_TIME_RECEIVED, header.getTimeReceived());
|
meta.put(KEY_TIME_RECEIVED, header.getTimeReceived());
|
||||||
|
|
||||||
// Send wrapped message and store metadata
|
// Send wrapped message and store metadata
|
||||||
clientHelper.addLocalMessage(txn, wrappedMessage, meta, true);
|
clientHelper.addLocalMessage(txn, wrappedMessage, meta, true, false);
|
||||||
return wrappedMessage.getId();
|
return wrappedMessage.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
meta.put(KEY_AUTHOR, clientHelper.toList(a));
|
meta.put(KEY_AUTHOR, clientHelper.toList(a));
|
||||||
meta.put(KEY_LOCAL, true);
|
meta.put(KEY_LOCAL, true);
|
||||||
meta.put(MSG_KEY_READ, true);
|
meta.put(MSG_KEY_READ, true);
|
||||||
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
|
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true,
|
||||||
|
false);
|
||||||
messageTracker.trackOutgoingMessage(txn, p.getMessage());
|
messageTracker.trackOutgoingMessage(txn, p.getMessage());
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
.encodeMetadata(type, sessionId, m.getTimestamp(), true, true,
|
.encodeMetadata(type, sessionId, m.getTimestamp(), true, true,
|
||||||
visibleInConversation);
|
visibleInConversation);
|
||||||
try {
|
try {
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
@@ -177,8 +177,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
boolean isInvalidDependency(@Nullable MessageId lastRemoteMessageId,
|
boolean isInvalidDependency(@Nullable MessageId lastRemoteMessageId,
|
||||||
@Nullable MessageId dependency) {
|
@Nullable MessageId dependency) {
|
||||||
if (dependency == null) return lastRemoteMessageId != null;
|
if (dependency == null) return lastRemoteMessageId != null;
|
||||||
return lastRemoteMessageId == null ||
|
return !dependency.equals(lastRemoteMessageId);
|
||||||
!dependency.equals(lastRemoteMessageId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long getLocalTimestamp(long localTimestamp, long requestTimestamp) {
|
long getLocalTimestamp(long localTimestamp, long requestTimestamp) {
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link InputStream} that wraps another InputStream, counting the bytes
|
||||||
|
* read and only allowing a limited number of bytes to be read.
|
||||||
|
*/
|
||||||
|
@NotThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class CountingInputStream extends InputStream {
|
||||||
|
|
||||||
|
private final InputStream delegate;
|
||||||
|
private final long maxBytesToRead;
|
||||||
|
|
||||||
|
private long bytesRead = 0;
|
||||||
|
|
||||||
|
CountingInputStream(InputStream delegate, long maxBytesToRead) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.maxBytesToRead = maxBytesToRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getBytesRead() {
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return delegate.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
delegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (bytesRead == maxBytesToRead) return -1;
|
||||||
|
int i = delegate.read();
|
||||||
|
if (i != -1) bytesRead++;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (len == 0) return 0;
|
||||||
|
if (bytesRead == maxBytesToRead) return -1;
|
||||||
|
if (bytesRead + len > maxBytesToRead)
|
||||||
|
len = (int) (maxBytesToRead - bytesRead);
|
||||||
|
int read = delegate.read(b, off, len);
|
||||||
|
if (read != -1) bytesRead += read;
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
|
interface MessageTypes {
|
||||||
|
|
||||||
|
int PRIVATE_MESSAGE = 0;
|
||||||
|
int ATTACHMENT = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
|
interface MessagingConstants {
|
||||||
|
|
||||||
|
// Metadata keys for groups
|
||||||
|
String GROUP_KEY_CONTACT_ID = "contactId";
|
||||||
|
|
||||||
|
// Metadata keys for messages
|
||||||
|
String MSG_KEY_TIMESTAMP = "timestamp";
|
||||||
|
String MSG_KEY_LOCAL = "local";
|
||||||
|
String MSG_KEY_MSG_TYPE = "messageType";
|
||||||
|
String MSG_KEY_CONTENT_TYPE = "contentType";
|
||||||
|
String MSG_KEY_DESCRIPTOR_LENGTH = "descriptorLength";
|
||||||
|
String MSG_KEY_HAS_TEXT = "hasText";
|
||||||
|
String MSG_KEY_ATTACHMENT_HEADERS = "attachmentHeaders";
|
||||||
|
}
|
||||||
@@ -11,20 +11,23 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||||
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
|
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
@@ -32,15 +35,16 @@ import org.briarproject.briar.api.messaging.FileTooBigException;
|
|||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||||
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
||||||
import org.briarproject.briar.client.ConversationClientImpl;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -48,28 +52,57 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.GROUP_KEY_CONTACT_ID;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MessagingManagerImpl extends ConversationClientImpl
|
class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
||||||
implements MessagingManager, OpenDatabaseHook, ContactHook,
|
ConversationClient, OpenDatabaseHook, ContactHook,
|
||||||
ClientVersioningHook {
|
ClientVersioningHook {
|
||||||
|
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private final MetadataParser metadataParser;
|
||||||
|
private final MessageTracker messageTracker;
|
||||||
private final ClientVersioningManager clientVersioningManager;
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
private final MessageFactory messageFactory;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MetadataParser metadataParser, MessageTracker messageTracker,
|
MetadataParser metadataParser, MessageTracker messageTracker,
|
||||||
ContactGroupFactory contactGroupFactory,
|
ContactGroupFactory contactGroupFactory) {
|
||||||
MessageFactory messageFactory) {
|
this.db = db;
|
||||||
super(db, clientHelper, metadataParser, messageTracker);
|
this.clientHelper = clientHelper;
|
||||||
|
this.metadataParser = metadataParser;
|
||||||
|
this.messageTracker = messageTracker;
|
||||||
this.clientVersioningManager = clientVersioningManager;
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
this.messageFactory = messageFactory;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupCount getGroupCount(Transaction txn, ContactId contactId)
|
||||||
|
throws DbException {
|
||||||
|
Contact contact = db.getContact(txn, contactId);
|
||||||
|
GroupId groupId = getContactGroup(contact).getId();
|
||||||
|
return messageTracker.getGroupCount(txn, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadFlag(GroupId g, MessageId m, boolean read)
|
||||||
|
throws DbException {
|
||||||
|
messageTracker.setReadFlag(g, m, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -94,14 +127,14 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary d = new BdfDictionary();
|
||||||
d.put("contactId", c.getId().getInt());
|
d.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
||||||
try {
|
try {
|
||||||
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
|
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
// Initialize the group count with current time
|
// Initialize the group count with current time
|
||||||
initializeGroupCount(txn, g.getId());
|
messageTracker.initializeGroupCount(txn, g.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -124,24 +157,66 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
BdfDictionary meta) throws DbException, FormatException {
|
throws DbException, InvalidMessageException {
|
||||||
|
try {
|
||||||
|
BdfDictionary metaDict = metadataParser.parse(meta);
|
||||||
|
// Message type is null for version 0.0 private messages
|
||||||
|
Long messageType = metaDict.getOptionalLong(MSG_KEY_MSG_TYPE);
|
||||||
|
if (messageType == null) {
|
||||||
|
incomingPrivateMessage(txn, m, metaDict, true, emptyList());
|
||||||
|
} else if (messageType == PRIVATE_MESSAGE) {
|
||||||
|
boolean hasText = metaDict.getBoolean(MSG_KEY_HAS_TEXT);
|
||||||
|
List<AttachmentHeader> headers =
|
||||||
|
parseAttachmentHeaders(metaDict);
|
||||||
|
incomingPrivateMessage(txn, m, metaDict, hasText, headers);
|
||||||
|
} else if (messageType == ATTACHMENT) {
|
||||||
|
incomingAttachment(txn, m);
|
||||||
|
} else {
|
||||||
|
throw new InvalidMessageException();
|
||||||
|
}
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
// Don't share message
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incomingPrivateMessage(Transaction txn, Message m,
|
||||||
|
BdfDictionary meta, boolean hasText, List<AttachmentHeader> headers)
|
||||||
|
throws DbException, FormatException {
|
||||||
GroupId groupId = m.getGroupId();
|
GroupId groupId = m.getGroupId();
|
||||||
long timestamp = meta.getLong("timestamp");
|
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
|
||||||
boolean local = meta.getBoolean("local");
|
boolean local = meta.getBoolean(MSG_KEY_LOCAL);
|
||||||
boolean read = meta.getBoolean(MSG_KEY_READ);
|
boolean read = meta.getBoolean(MSG_KEY_READ);
|
||||||
PrivateMessageHeader header =
|
PrivateMessageHeader header =
|
||||||
new PrivateMessageHeader(m.getId(), groupId, timestamp, local,
|
new PrivateMessageHeader(m.getId(), groupId, timestamp, local,
|
||||||
read, false, false, true, emptyList());
|
read, false, false, hasText, headers);
|
||||||
ContactId contactId = getContactId(txn, groupId);
|
ContactId contactId = getContactId(txn, groupId);
|
||||||
PrivateMessageReceivedEvent event =
|
PrivateMessageReceivedEvent event =
|
||||||
new PrivateMessageReceivedEvent(header, contactId);
|
new PrivateMessageReceivedEvent(header, contactId);
|
||||||
txn.attach(event);
|
txn.attach(event);
|
||||||
messageTracker.trackIncomingMessage(txn, m);
|
messageTracker.trackIncomingMessage(txn, m);
|
||||||
|
}
|
||||||
|
|
||||||
// don't share message
|
private List<AttachmentHeader> parseAttachmentHeaders(BdfDictionary meta)
|
||||||
return false;
|
throws FormatException {
|
||||||
|
BdfList attachmentHeaders = meta.getList(MSG_KEY_ATTACHMENT_HEADERS);
|
||||||
|
int length = attachmentHeaders.size();
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>(length);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
BdfList header = attachmentHeaders.getList(i);
|
||||||
|
MessageId id = new MessageId(header.getRaw(0));
|
||||||
|
String contentType = header.getString(1);
|
||||||
|
headers.add(new AttachmentHeader(id, contentType));
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incomingAttachment(Transaction txn, Message m)
|
||||||
|
throws DbException {
|
||||||
|
ContactId contactId = getContactId(txn, m.getGroupId());
|
||||||
|
txn.attach(new AttachmentReceivedEvent(m.getId(), contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -149,14 +224,30 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put("timestamp", m.getMessage().getTimestamp());
|
meta.put(MSG_KEY_TIMESTAMP, m.getMessage().getTimestamp());
|
||||||
meta.put("local", true);
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
meta.put("read", true);
|
meta.put(MSG_KEY_READ, true);
|
||||||
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
|
if (!m.isLegacyFormat()) {
|
||||||
|
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
|
||||||
|
meta.put(MSG_KEY_HAS_TEXT, m.hasText());
|
||||||
|
BdfList headers = new BdfList();
|
||||||
|
for (AttachmentHeader a : m.getAttachmentHeaders()) {
|
||||||
|
headers.add(
|
||||||
|
BdfList.of(a.getMessageId(), a.getContentType()));
|
||||||
|
}
|
||||||
|
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
|
||||||
|
}
|
||||||
|
// Mark attachments as shared and permanent now we're ready to send
|
||||||
|
for (AttachmentHeader a : m.getAttachmentHeaders()) {
|
||||||
|
db.setMessageShared(txn, a.getMessageId());
|
||||||
|
db.setMessagePermanent(txn, a.getMessageId());
|
||||||
|
}
|
||||||
|
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
|
||||||
|
false);
|
||||||
messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new AssertionError(e);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
@@ -164,22 +255,27 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||||
String contentType, InputStream is)
|
String contentType, InputStream in)
|
||||||
throws DbException, IOException {
|
throws DbException, IOException {
|
||||||
// TODO add real implementation
|
// TODO: Support large messages
|
||||||
byte[] body = new byte[MAX_MESSAGE_BODY_LENGTH];
|
ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
|
||||||
try {
|
byte[] descriptor =
|
||||||
IoUtils.read(is, body);
|
clientHelper.toByteArray(BdfList.of(ATTACHMENT, contentType));
|
||||||
} catch (EOFException ignored) {
|
bodyOut.write(descriptor);
|
||||||
}
|
copyAndClose(in, bodyOut);
|
||||||
if (is.available() > 0) throw new FileTooBigException();
|
if (bodyOut.size() > MAX_MESSAGE_BODY_LENGTH)
|
||||||
is.close();
|
throw new FileTooBigException();
|
||||||
try {
|
byte[] body = bodyOut.toByteArray();
|
||||||
Thread.sleep(1000);
|
BdfDictionary meta = new BdfDictionary();
|
||||||
} catch (InterruptedException ignored) {
|
meta.put(MSG_KEY_TIMESTAMP, timestamp);
|
||||||
}
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
Message m = messageFactory.createMessage(groupId, timestamp, body);
|
meta.put(MSG_KEY_MSG_TYPE, ATTACHMENT);
|
||||||
clientHelper.addLocalMessage(m, new BdfDictionary(), false);
|
meta.put(MSG_KEY_CONTENT_TYPE, contentType);
|
||||||
|
meta.put(MSG_KEY_DESCRIPTOR_LENGTH, descriptor.length);
|
||||||
|
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
||||||
|
// Mark attachments as temporary, not shared until we're ready to send
|
||||||
|
db.transaction(false, txn ->
|
||||||
|
clientHelper.addLocalMessage(txn, m, meta, false, true));
|
||||||
return new AttachmentHeader(m.getId(), contentType);
|
return new AttachmentHeader(m.getId(), contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,11 +338,23 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
BdfDictionary meta = metadata.get(id);
|
BdfDictionary meta = metadata.get(id);
|
||||||
if (meta == null) continue;
|
if (meta == null) continue;
|
||||||
try {
|
try {
|
||||||
long timestamp = meta.getLong("timestamp");
|
// Message type is null for version 0.0 private messages
|
||||||
boolean local = meta.getBoolean("local");
|
Long messageType = meta.getOptionalLong(MSG_KEY_MSG_TYPE);
|
||||||
boolean read = meta.getBoolean("read");
|
if (messageType != null && messageType != PRIVATE_MESSAGE)
|
||||||
headers.add(new PrivateMessageHeader(id, g, timestamp, local,
|
continue;
|
||||||
read, s.isSent(), s.isSeen(), true, emptyList()));
|
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
|
||||||
|
boolean local = meta.getBoolean(MSG_KEY_LOCAL);
|
||||||
|
boolean read = meta.getBoolean(MSG_KEY_READ);
|
||||||
|
if (messageType == null) {
|
||||||
|
headers.add(new PrivateMessageHeader(id, g, timestamp,
|
||||||
|
local, read, s.isSent(), s.isSeen(), true,
|
||||||
|
emptyList()));
|
||||||
|
} else {
|
||||||
|
boolean hasText = meta.getBoolean(MSG_KEY_HAS_TEXT);
|
||||||
|
headers.add(new PrivateMessageHeader(id, g, timestamp,
|
||||||
|
local, read, s.isSent(), s.isSeen(), hasText,
|
||||||
|
parseAttachmentHeaders(meta)));
|
||||||
|
}
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -257,19 +365,26 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
@Override
|
@Override
|
||||||
public String getMessageText(MessageId m) throws DbException {
|
public String getMessageText(MessageId m) throws DbException {
|
||||||
try {
|
try {
|
||||||
// 0: private message text
|
BdfList body = clientHelper.getMessageAsList(m);
|
||||||
return clientHelper.getMessageAsList(m).getString(0);
|
if (body.size() == 1) return body.getString(0); // Legacy format
|
||||||
|
else return body.getOptionalString(1);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Attachment getAttachment(MessageId mId) throws DbException {
|
public Attachment getAttachment(MessageId m) throws DbException {
|
||||||
// TODO add real implementation
|
// TODO: Support large messages
|
||||||
Message m = clientHelper.getMessage(mId);
|
byte[] body = clientHelper.getMessage(m).getBody();
|
||||||
byte[] bytes = m.getBody();
|
try {
|
||||||
return new Attachment(new ByteArrayInputStream(bytes));
|
BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(m);
|
||||||
|
int offset = meta.getLong(MSG_KEY_DESCRIPTOR_LENGTH).intValue();
|
||||||
|
return new Attachment(new ByteArrayInputStream(body, offset,
|
||||||
|
body.length - offset));
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -278,7 +393,7 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
int minorVersion = clientVersioningManager
|
int minorVersion = clientVersioningManager
|
||||||
.getClientMinorVersion(txn, c, CLIENT_ID, 0);
|
.getClientMinorVersion(txn, c, CLIENT_ID, 0);
|
||||||
// support was added in 0.1
|
// support was added in 0.1
|
||||||
return minorVersion == 1;
|
return minorVersion > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package org.briarproject.briar.messaging;
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
@@ -19,7 +20,6 @@ import dagger.Provides;
|
|||||||
|
|
||||||
import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID;
|
import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION;
|
import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingManager.MINOR_VERSION;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class MessagingModule {
|
public class MessagingModule {
|
||||||
@@ -42,10 +42,10 @@ public class MessagingModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
PrivateMessageValidator getValidator(ValidationManager validationManager,
|
PrivateMessageValidator getValidator(ValidationManager validationManager,
|
||||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
PrivateMessageValidator validator = new PrivateMessageValidator(
|
PrivateMessageValidator validator = new PrivateMessageValidator(
|
||||||
clientHelper, metadataEncoder, clock);
|
bdfReaderFactory, metadataEncoder, clock);
|
||||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||||
validator);
|
validator);
|
||||||
return validator;
|
return validator;
|
||||||
@@ -57,14 +57,17 @@ public class MessagingModule {
|
|||||||
ContactManager contactManager, ValidationManager validationManager,
|
ContactManager contactManager, ValidationManager validationManager,
|
||||||
ConversationManager conversationManager,
|
ConversationManager conversationManager,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MessagingManagerImpl messagingManager) {
|
FeatureFlags featureFlags, MessagingManagerImpl messagingManager) {
|
||||||
lifecycleManager.registerOpenDatabaseHook(messagingManager);
|
lifecycleManager.registerOpenDatabaseHook(messagingManager);
|
||||||
contactManager.registerContactHook(messagingManager);
|
contactManager.registerContactHook(messagingManager);
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||||
messagingManager);
|
messagingManager);
|
||||||
conversationManager.registerConversationClient(messagingManager);
|
conversationManager.registerConversationClient(messagingManager);
|
||||||
|
// Advertise the current or previous minor version depending on the
|
||||||
|
// feature flag
|
||||||
|
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 1 : 0;
|
||||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
MINOR_VERSION, messagingManager);
|
minorVersion, messagingManager);
|
||||||
return messagingManager;
|
return messagingManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -30,15 +32,36 @@ class PrivateMessageFactoryImpl implements PrivateMessageFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
public PrivateMessage createLegacyPrivateMessage(GroupId groupId,
|
||||||
String text, List<AttachmentHeader> attachments)
|
long timestamp, String text) throws FormatException {
|
||||||
throws FormatException {
|
|
||||||
// Validate the arguments
|
// Validate the arguments
|
||||||
if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH))
|
if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
// Serialise the message
|
// Serialise the message
|
||||||
BdfList message = BdfList.of(text);
|
BdfList body = BdfList.of(text);
|
||||||
Message m = clientHelper.createMessage(groupId, timestamp, message);
|
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
||||||
return new PrivateMessage(m);
|
return new PrivateMessage(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
|
@Nullable String text, List<AttachmentHeader> headers)
|
||||||
|
throws FormatException {
|
||||||
|
// Validate the arguments
|
||||||
|
if (text == null) {
|
||||||
|
if (headers.isEmpty()) throw new IllegalArgumentException();
|
||||||
|
} else if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
// Serialise the attachment headers
|
||||||
|
BdfList attachmentList = new BdfList();
|
||||||
|
for (AttachmentHeader a : headers) {
|
||||||
|
attachmentList.add(
|
||||||
|
BdfList.of(a.getMessageId(), a.getContentType()));
|
||||||
|
}
|
||||||
|
// Serialise the message
|
||||||
|
BdfList body = BdfList.of(PRIVATE_MESSAGE, text, attachmentList);
|
||||||
|
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
||||||
|
return new PrivateMessage(m, text != null, headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,103 @@
|
|||||||
package org.briarproject.briar.messaging;
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||||
import org.briarproject.bramble.api.client.BdfMessageValidator;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageContext;
|
||||||
|
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_BYTES;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class PrivateMessageValidator extends BdfMessageValidator {
|
class PrivateMessageValidator implements MessageValidator {
|
||||||
|
|
||||||
PrivateMessageValidator(ClientHelper clientHelper,
|
private final BdfReaderFactory bdfReaderFactory;
|
||||||
|
private final MetadataEncoder metadataEncoder;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
super(clientHelper, metadataEncoder, clock);
|
this.bdfReaderFactory = bdfReaderFactory;
|
||||||
|
this.metadataEncoder = metadataEncoder;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
public MessageContext validateMessage(Message m, Group g)
|
||||||
|
throws InvalidMessageException {
|
||||||
|
// Reject the message if it's too far in the future
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||||
|
throw new InvalidMessageException(
|
||||||
|
"Timestamp is too far in the future");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// TODO: Support large messages
|
||||||
|
InputStream in = new ByteArrayInputStream(m.getBody());
|
||||||
|
CountingInputStream countIn =
|
||||||
|
new CountingInputStream(in, MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
BdfReader reader = bdfReaderFactory.createReader(countIn);
|
||||||
|
BdfList list = reader.readList();
|
||||||
|
long bytesRead = countIn.getBytesRead();
|
||||||
|
BdfMessageContext context;
|
||||||
|
if (list.size() == 1) {
|
||||||
|
// Legacy private message
|
||||||
|
if (!reader.eof()) throw new FormatException();
|
||||||
|
context = validateLegacyPrivateMessage(m, list);
|
||||||
|
} else {
|
||||||
|
// Private message or attachment
|
||||||
|
int messageType = list.getLong(0).intValue();
|
||||||
|
if (messageType == PRIVATE_MESSAGE) {
|
||||||
|
if (!reader.eof()) throw new FormatException();
|
||||||
|
context = validatePrivateMessage(m, list);
|
||||||
|
} else if (messageType == ATTACHMENT) {
|
||||||
|
context = validateAttachment(m, list, bytesRead);
|
||||||
|
} else {
|
||||||
|
throw new InvalidMessageException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Metadata meta = metadataEncoder.encode(context.getDictionary());
|
||||||
|
return new MessageContext(meta, context.getDependencies());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfMessageContext validateLegacyPrivateMessage(Message m,
|
||||||
BdfList body) throws FormatException {
|
BdfList body) throws FormatException {
|
||||||
// Private message text
|
// Private message text
|
||||||
checkSize(body, 1);
|
checkSize(body, 1);
|
||||||
@@ -37,9 +105,54 @@ class PrivateMessageValidator extends BdfMessageValidator {
|
|||||||
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
// Return the metadata
|
// Return the metadata
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put("timestamp", m.getTimestamp());
|
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
|
||||||
meta.put("local", false);
|
meta.put(MSG_KEY_LOCAL, false);
|
||||||
meta.put(MSG_KEY_READ, false);
|
meta.put(MSG_KEY_READ, false);
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BdfMessageContext validatePrivateMessage(Message m, BdfList body)
|
||||||
|
throws FormatException {
|
||||||
|
// Message type, optional private message text, attachment headers
|
||||||
|
checkSize(body, 3);
|
||||||
|
String text = body.getOptionalString(1);
|
||||||
|
checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
|
BdfList headers = body.getList(2);
|
||||||
|
if (text == null) checkSize(headers, 1, MAX_ATTACHMENTS_PER_MESSAGE);
|
||||||
|
else checkSize(headers, 0, MAX_ATTACHMENTS_PER_MESSAGE);
|
||||||
|
for (int i = 0; i < headers.size(); i++) {
|
||||||
|
BdfList header = headers.getList(i);
|
||||||
|
// Message ID, content type
|
||||||
|
checkSize(header, 2);
|
||||||
|
byte[] id = header.getRaw(0);
|
||||||
|
checkLength(id, UniqueId.LENGTH);
|
||||||
|
String contentType = header.getString(1);
|
||||||
|
checkLength(contentType, 1, MAX_CONTENT_TYPE_BYTES);
|
||||||
|
}
|
||||||
|
// Return the metadata
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
|
||||||
|
meta.put(MSG_KEY_LOCAL, false);
|
||||||
|
meta.put(MSG_KEY_READ, false);
|
||||||
|
meta.put(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE);
|
||||||
|
meta.put(MSG_KEY_HAS_TEXT, text != null);
|
||||||
|
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
|
||||||
|
return new BdfMessageContext(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfMessageContext validateAttachment(Message m, BdfList descriptor,
|
||||||
|
long descriptorLength) throws FormatException {
|
||||||
|
// Message type, content type
|
||||||
|
checkSize(descriptor, 2);
|
||||||
|
String contentType = descriptor.getString(1);
|
||||||
|
checkLength(contentType, 1, MAX_CONTENT_TYPE_BYTES);
|
||||||
|
// Return the metadata
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
|
||||||
|
meta.put(MSG_KEY_LOCAL, false);
|
||||||
|
meta.put(MSG_KEY_MSG_TYPE, ATTACHMENT);
|
||||||
|
meta.put(MSG_KEY_DESCRIPTOR_LENGTH, descriptorLength);
|
||||||
|
meta.put(MSG_KEY_CONTENT_TYPE, contentType);
|
||||||
|
return new BdfMessageContext(meta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
meta.put(KEY_TYPE, JOIN.getInt());
|
meta.put(KEY_TYPE, JOIN.getInt());
|
||||||
meta.put(KEY_INITIAL_JOIN_MSG, creator);
|
meta.put(KEY_INITIAL_JOIN_MSG, creator);
|
||||||
addMessageMetadata(meta, m);
|
addMessageMetadata(meta, m);
|
||||||
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
|
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true, false);
|
||||||
messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
||||||
addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
|
addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
|
||||||
setPreviousMsgId(txn, m.getMessage().getGroupId(),
|
setPreviousMsgId(txn, m.getMessage().getGroupId(),
|
||||||
@@ -217,7 +217,8 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
meta.put(KEY_PARENT_MSG_ID, m.getParent());
|
meta.put(KEY_PARENT_MSG_ID, m.getParent());
|
||||||
addMessageMetadata(meta, m);
|
addMessageMetadata(meta, m);
|
||||||
GroupId g = m.getMessage().getGroupId();
|
GroupId g = m.getMessage().getGroupId();
|
||||||
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
|
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
|
||||||
|
false);
|
||||||
|
|
||||||
// track message
|
// track message
|
||||||
setPreviousMsgId(txn, g, m.getMessage().getId());
|
setPreviousMsgId(txn, g, m.getMessage().getId());
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
boolean isValidDependency(S session, @Nullable MessageId dependency) {
|
boolean isValidDependency(S session, @Nullable MessageId dependency) {
|
||||||
MessageId expected = session.getLastRemoteMessageId();
|
MessageId expected = session.getLastRemoteMessageId();
|
||||||
if (dependency == null) return expected == null;
|
if (dependency == null) return expected == null;
|
||||||
return expected != null && dependency.equals(expected);
|
return dependency.equals(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPrivateGroupVisibility(Transaction txn, S session,
|
void setPrivateGroupVisibility(Transaction txn, S session,
|
||||||
@@ -223,7 +223,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
.encodeMetadata(type, privateGroupId, m.getTimestamp(), true,
|
.encodeMetadata(type, privateGroupId, m.getTimestamp(), true,
|
||||||
true, visibleInConversation, false, false);
|
true, visibleInConversation, false, false);
|
||||||
try {
|
try {
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -567,7 +567,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
|
|||||||
.encodeMetadata(type, shareableId, m.getTimestamp(), true, true,
|
.encodeMetadata(type, shareableId, m.getTimestamp(), true, true,
|
||||||
visibleInConversation, false, false);
|
visibleInConversation, false, false);
|
||||||
try {
|
try {
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
@@ -627,7 +627,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
|
|||||||
@Nullable MessageId dependency) {
|
@Nullable MessageId dependency) {
|
||||||
MessageId expected = session.getLastRemoteMessageId();
|
MessageId expected = session.getLastRemoteMessageId();
|
||||||
if (dependency == null) return expected != null;
|
if (dependency == null) return expected != null;
|
||||||
return expected == null || !dependency.equals(expected);
|
return !dependency.equals(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getLocalTimestamp(Session session) {
|
private long getLocalTimestamp(Session session) {
|
||||||
|
|||||||
@@ -325,7 +325,8 @@ public class TestDataCreatorImpl implements TestDataCreator {
|
|||||||
|
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
|
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
|
||||||
|
false);
|
||||||
if (local) messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
if (local) messageTracker.trackOutgoingMessage(txn, m.getMessage());
|
||||||
else messageTracker.trackIncomingMessage(txn, m.getMessage());
|
else messageTracker.trackIncomingMessage(txn, m.getMessage());
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -334,13 +335,6 @@ public class TestDataCreatorImpl implements TestDataCreator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPrivateMessage(Contact contact, String text, long time,
|
|
||||||
boolean local) throws DbException, FormatException {
|
|
||||||
Group group = messagingManager.getContactGroup(contact);
|
|
||||||
createPrivateMessage(group.getId(), text, time, local);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createBlogPosts(List<Contact> contacts, int numBlogPosts)
|
private void createBlogPosts(List<Contact> contacts, int numBlogPosts)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
for (int i = 0; i < numBlogPosts; i++) {
|
for (int i = 0; i < numBlogPosts; i++) {
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(blog1));
|
will(returnValue(blog1));
|
||||||
oneOf(clientHelper).toList(localAuthor1);
|
oneOf(clientHelper).toList(localAuthor1);
|
||||||
will(returnValue(authorList1));
|
will(returnValue(authorList1));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
|
oneOf(clientHelper).addLocalMessage(txn, message, meta, true,
|
||||||
|
false);
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
@@ -340,7 +341,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(rssBlog));
|
will(returnValue(rssBlog));
|
||||||
oneOf(clientHelper).toList(rssLocalAuthor);
|
oneOf(clientHelper).toList(rssLocalAuthor);
|
||||||
will(returnValue(rssAuthorList));
|
will(returnValue(rssAuthorList));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, rssMessage, meta, true);
|
oneOf(clientHelper).addLocalMessage(txn, rssMessage, meta, true,
|
||||||
|
false);
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList);
|
oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList);
|
||||||
will(returnValue(rssLocalAuthor));
|
will(returnValue(rssLocalAuthor));
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
@@ -407,7 +409,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(authorList1));
|
will(returnValue(authorList1));
|
||||||
// Store the comment
|
// Store the comment
|
||||||
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
|
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
|
||||||
true);
|
true, false);
|
||||||
// Create the headers for the comment and its parent
|
// Create the headers for the comment and its parent
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
@@ -508,7 +510,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(authorList1));
|
will(returnValue(authorList1));
|
||||||
// Store the wrapped post
|
// Store the wrapped post
|
||||||
oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
|
oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
|
||||||
wrappedPostMeta, true);
|
wrappedPostMeta, true, false);
|
||||||
// Create the comment
|
// Create the comment
|
||||||
oneOf(blogPostFactory).createBlogComment(blog2.getId(),
|
oneOf(blogPostFactory).createBlogComment(blog2.getId(),
|
||||||
localAuthor2, comment, messageId, wrappedPostId);
|
localAuthor2, comment, messageId, wrappedPostId);
|
||||||
@@ -517,7 +519,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(authorList2));
|
will(returnValue(authorList2));
|
||||||
// Store the comment
|
// Store the comment
|
||||||
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
|
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
|
||||||
true);
|
true, false);
|
||||||
// Create the headers for the comment and the wrapped post
|
// Create the headers for the comment and the wrapped post
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
||||||
will(returnValue(localAuthor2));
|
will(returnValue(localAuthor2));
|
||||||
@@ -619,7 +621,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(rssAuthorList));
|
will(returnValue(rssAuthorList));
|
||||||
// Store the wrapped post
|
// Store the wrapped post
|
||||||
oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
|
oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
|
||||||
wrappedPostMeta, true);
|
wrappedPostMeta, true, false);
|
||||||
// Create the comment
|
// Create the comment
|
||||||
oneOf(blogPostFactory).createBlogComment(blog1.getId(),
|
oneOf(blogPostFactory).createBlogComment(blog1.getId(),
|
||||||
localAuthor1, comment, rssMessageId, wrappedPostId);
|
localAuthor1, comment, rssMessageId, wrappedPostId);
|
||||||
@@ -628,7 +630,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(authorList1));
|
will(returnValue(authorList1));
|
||||||
// Store the comment
|
// Store the comment
|
||||||
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
|
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
|
||||||
true);
|
true, false);
|
||||||
// Create the headers for the comment and the wrapped post
|
// Create the headers for the comment and the wrapped post
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
@@ -741,7 +743,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(rssAuthorList));
|
will(returnValue(rssAuthorList));
|
||||||
// Store the rewrapped post
|
// Store the rewrapped post
|
||||||
oneOf(clientHelper).addLocalMessage(txn, rewrappedPostMsg,
|
oneOf(clientHelper).addLocalMessage(txn, rewrappedPostMsg,
|
||||||
rewrappedPostMeta, true);
|
rewrappedPostMeta, true, false);
|
||||||
// Wrap the original comment for blog 2
|
// Wrap the original comment for blog 2
|
||||||
oneOf(clientHelper).getMessageAsList(txn, originalCommentId);
|
oneOf(clientHelper).getMessageAsList(txn, originalCommentId);
|
||||||
will(returnValue(originalCommentBody));
|
will(returnValue(originalCommentBody));
|
||||||
@@ -758,7 +760,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(authorList1));
|
will(returnValue(authorList1));
|
||||||
// Store the wrapped comment
|
// Store the wrapped comment
|
||||||
oneOf(clientHelper).addLocalMessage(txn, wrappedCommentMsg,
|
oneOf(clientHelper).addLocalMessage(txn, wrappedCommentMsg,
|
||||||
wrappedCommentMeta, true);
|
wrappedCommentMeta, true, false);
|
||||||
// Create the new comment
|
// Create the new comment
|
||||||
oneOf(blogPostFactory).createBlogComment(blog2.getId(),
|
oneOf(blogPostFactory).createBlogComment(blog2.getId(),
|
||||||
localAuthor2, localComment, originalCommentId,
|
localAuthor2, localComment, originalCommentId,
|
||||||
@@ -768,7 +770,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
will(returnValue(authorList2));
|
will(returnValue(authorList2));
|
||||||
// Store the new comment
|
// Store the new comment
|
||||||
oneOf(clientHelper).addLocalMessage(txn, localCommentMsg,
|
oneOf(clientHelper).addLocalMessage(txn, localCommentMsg,
|
||||||
localCommentMeta, true);
|
localCommentMeta, true, false);
|
||||||
// Create the headers for the new comment, the wrapped comment and
|
// Create the headers for the new comment, the wrapped comment and
|
||||||
// the rewrapped post
|
// the rewrapped post
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
||||||
|
|||||||
@@ -1075,8 +1075,8 @@ public class IntroductionIntegrationTest
|
|||||||
m.getPreviousMessageId(), m.getSessionId(),
|
m.getPreviousMessageId(), m.getSessionId(),
|
||||||
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
|
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
|
||||||
m.getTransportProperties());
|
m.getTransportProperties());
|
||||||
c0.getClientHelper()
|
c0.getClientHelper().addLocalMessage(txn, msg, new BdfDictionary(),
|
||||||
.addLocalMessage(txn, msg, new BdfDictionary(), true);
|
true, false);
|
||||||
Group group0 = getLocalGroup();
|
Group group0 = getLocalGroup();
|
||||||
BdfDictionary query = BdfDictionary.of(
|
BdfDictionary query = BdfDictionary.of(
|
||||||
new BdfEntry(SESSION_KEY_SESSION_ID, m.getSessionId())
|
new BdfEntry(SESSION_KEY_SESSION_ID, m.getSessionId())
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
public class CountingInputStreamTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
private final byte[] src = getRandomBytes(123);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCountsSingleByteReads() throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
// The limit is high enough to read the whole src array
|
||||||
|
CountingInputStream in =
|
||||||
|
new CountingInputStream(delegate, src.length + 1);
|
||||||
|
|
||||||
|
// No bytes should have been read initially
|
||||||
|
assertEquals(0L, in.getBytesRead());
|
||||||
|
// The reads should return the contents of the src array
|
||||||
|
for (int i = 0; i < src.length; i++) {
|
||||||
|
assertEquals(i, in.getBytesRead());
|
||||||
|
assertEquals(src[i] & 0xFF, in.read());
|
||||||
|
}
|
||||||
|
// The count should match the length of the src array
|
||||||
|
assertEquals(src.length, in.getBytesRead());
|
||||||
|
// Trying to read another byte should return EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
// Reading EOF shouldn't affect the count
|
||||||
|
assertEquals(src.length, in.getBytesRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCountsMultiByteReads() throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
// The limit is high enough to read the whole src array
|
||||||
|
CountingInputStream in =
|
||||||
|
new CountingInputStream(delegate, src.length + 1);
|
||||||
|
|
||||||
|
// No bytes should have been read initially
|
||||||
|
assertEquals(0L, in.getBytesRead());
|
||||||
|
// Copy the src array in random-sized pieces
|
||||||
|
byte[] dest = new byte[src.length];
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < dest.length) {
|
||||||
|
assertEquals(offset, in.getBytesRead());
|
||||||
|
int length = Math.min(random.nextInt(10), dest.length - offset);
|
||||||
|
assertEquals(length, in.read(dest, offset, length));
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
// The dest array should be a copy of the src array
|
||||||
|
assertArrayEquals(src, dest);
|
||||||
|
// The count should match the length of the src array
|
||||||
|
assertEquals(src.length, in.getBytesRead());
|
||||||
|
// Trying to read another byte should return EOF
|
||||||
|
assertEquals(-1, in.read(dest, 0, 1));
|
||||||
|
// Reading EOF shouldn't affect the count
|
||||||
|
assertEquals(src.length, in.getBytesRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCountsSkips() throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
// The limit is high enough to read the whole src array
|
||||||
|
CountingInputStream in =
|
||||||
|
new CountingInputStream(delegate, src.length + 1);
|
||||||
|
|
||||||
|
// No bytes should have been read initially
|
||||||
|
assertEquals(0L, in.getBytesRead());
|
||||||
|
// Skip the src array in random-sized pieces
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < src.length) {
|
||||||
|
assertEquals(offset, in.getBytesRead());
|
||||||
|
int length = Math.min(random.nextInt(10), src.length - offset);
|
||||||
|
assertEquals(length, in.skip(length));
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
// The count should match the length of the src array
|
||||||
|
assertEquals(src.length, in.getBytesRead());
|
||||||
|
// Trying to skip another byte should return zero
|
||||||
|
assertEquals(0, in.skip(1));
|
||||||
|
// Returning zero shouldn't affect the count
|
||||||
|
assertEquals(src.length, in.getBytesRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsEofWhenSingleByteReadReachesLimit()
|
||||||
|
throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
// The limit is one byte lower than the length of the src array
|
||||||
|
CountingInputStream in =
|
||||||
|
new CountingInputStream(delegate, src.length - 1);
|
||||||
|
|
||||||
|
// The reads should return the contents of the src array, except the
|
||||||
|
// last byte
|
||||||
|
for (int i = 0; i < src.length - 1; i++) {
|
||||||
|
assertEquals(src[i] & 0xFF, in.read());
|
||||||
|
}
|
||||||
|
// The count should match the limit
|
||||||
|
assertEquals(src.length - 1, in.getBytesRead());
|
||||||
|
// Trying to read another byte should return EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
// Reading EOF shouldn't affect the count
|
||||||
|
assertEquals(src.length - 1, in.getBytesRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsEofWhenMultiByteReadReachesLimit() throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
// The limit is one byte lower than the length of the src array
|
||||||
|
CountingInputStream in =
|
||||||
|
new CountingInputStream(delegate, src.length - 1);
|
||||||
|
|
||||||
|
// Copy the src array in random-sized pieces, except the last two bytes
|
||||||
|
byte[] dest = new byte[src.length];
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < dest.length - 2) {
|
||||||
|
int length = Math.min(random.nextInt(10), dest.length - 2 - offset);
|
||||||
|
assertEquals(length, in.read(dest, offset, length));
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
// Trying to read two bytes should only return one, reaching the limit
|
||||||
|
assertEquals(1, in.read(dest, offset, 2));
|
||||||
|
// The dest array should be a copy of the src array, except the last
|
||||||
|
// byte
|
||||||
|
for (int i = 0; i < src.length - 1; i++) assertEquals(src[i], dest[i]);
|
||||||
|
// The count should match the limit
|
||||||
|
assertEquals(src.length - 1, in.getBytesRead());
|
||||||
|
// Trying to read another byte should return EOF
|
||||||
|
assertEquals(-1, in.read(dest, 0, 1));
|
||||||
|
// Reading EOF shouldn't affect the count
|
||||||
|
assertEquals(src.length - 1, in.getBytesRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsZeroWhenSkipReachesLimit() throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
// The limit is one byte lower than the length of the src array
|
||||||
|
CountingInputStream in =
|
||||||
|
new CountingInputStream(delegate, src.length - 1);
|
||||||
|
|
||||||
|
// Skip the src array in random-sized pieces, except the last two bytes
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < src.length - 2) {
|
||||||
|
assertEquals(offset, in.getBytesRead());
|
||||||
|
int length = Math.min(random.nextInt(10), src.length - 2 - offset);
|
||||||
|
assertEquals(length, in.skip(length));
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
// Trying to skip two bytes should only skip one, reaching the limit
|
||||||
|
assertEquals(1, in.skip(2));
|
||||||
|
// The count should match the limit
|
||||||
|
assertEquals(src.length - 1, in.getBytesRead());
|
||||||
|
// Trying to skip another byte should return zero
|
||||||
|
assertEquals(0, in.skip(1));
|
||||||
|
// Returning zero shouldn't affect the count
|
||||||
|
assertEquals(src.length - 1, in.getBytesRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMarkIsNotSupported() {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
CountingInputStream in = new CountingInputStream(delegate, src.length);
|
||||||
|
assertFalse(in.markSupported());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IOException.class)
|
||||||
|
public void testResetIsNotSupported() throws Exception {
|
||||||
|
InputStream delegate = new ByteArrayInputStream(src);
|
||||||
|
CountingInputStream in = new CountingInputStream(delegate, src.length);
|
||||||
|
in.mark(src.length);
|
||||||
|
assertEquals(src.length, in.read(new byte[src.length]));
|
||||||
|
in.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,20 +8,25 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.forum.ForumPost;
|
import org.briarproject.briar.api.forum.ForumPost;
|
||||||
import org.briarproject.briar.api.forum.ForumPostFactory;
|
import org.briarproject.briar.api.forum.ForumPostFactory;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
import org.briarproject.briar.test.BriarTestCase;
|
import org.briarproject.briar.test.BriarTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH;
|
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_BYTES;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@@ -43,18 +48,40 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLegacyPrivateMessageFitsIntoRecord() throws Exception {
|
||||||
|
// Create a maximum-length private message
|
||||||
|
GroupId groupId = new GroupId(getRandomId());
|
||||||
|
long timestamp = Long.MAX_VALUE;
|
||||||
|
String text = getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
|
PrivateMessage message = privateMessageFactory
|
||||||
|
.createLegacyPrivateMessage(groupId, timestamp, text);
|
||||||
|
// Check the size of the serialised message
|
||||||
|
int length = message.getMessage().getRawLength();
|
||||||
|
assertTrue(length > UniqueId.LENGTH + 8
|
||||||
|
+ MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
|
assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPrivateMessageFitsIntoRecord() throws Exception {
|
public void testPrivateMessageFitsIntoRecord() throws Exception {
|
||||||
// Create a maximum-length private message
|
// Create a maximum-length private message
|
||||||
GroupId groupId = new GroupId(getRandomId());
|
GroupId groupId = new GroupId(getRandomId());
|
||||||
long timestamp = Long.MAX_VALUE;
|
long timestamp = Long.MAX_VALUE;
|
||||||
String text = getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
String text = getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
|
// Create the maximum number of maximum-length attachment headers
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>();
|
||||||
|
for (int i = 0; i < MAX_ATTACHMENTS_PER_MESSAGE; i++) {
|
||||||
|
headers.add(new AttachmentHeader(new MessageId(getRandomId()),
|
||||||
|
getRandomString(MAX_CONTENT_TYPE_BYTES)));
|
||||||
|
}
|
||||||
PrivateMessage message = privateMessageFactory.createPrivateMessage(
|
PrivateMessage message = privateMessageFactory.createPrivateMessage(
|
||||||
groupId, timestamp, text, emptyList());
|
groupId, timestamp, text, headers);
|
||||||
// Check the size of the serialised message
|
// Check the size of the serialised message
|
||||||
int length = message.getMessage().getRawLength();
|
int length = message.getMessage().getRawLength();
|
||||||
assertTrue(length > UniqueId.LENGTH + 8
|
assertTrue(length > UniqueId.LENGTH + 8
|
||||||
+ MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
+ MAX_PRIVATE_MESSAGE_TEXT_LENGTH + MAX_ATTACHMENTS_PER_MESSAGE
|
||||||
|
* (UniqueId.LENGTH + MAX_CONTENT_TYPE_BYTES));
|
||||||
assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
|
assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1,472 @@
|
|||||||
package org.briarproject.briar.messaging;
|
package org.briarproject.briar.messaging;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.test.ValidatorTestCase;
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageContext;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.jmock.Expectations;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
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.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_BYTES;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
|
||||||
|
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_ATTACHMENT_HEADERS;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_CONTENT_TYPE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_DESCRIPTOR_LENGTH;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_HAS_TEXT;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_LOCAL;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_MSG_TYPE;
|
||||||
|
import static org.briarproject.briar.messaging.MessagingConstants.MSG_KEY_TIMESTAMP;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
|
|
||||||
public class PrivateMessageValidatorTest extends ValidatorTestCase {
|
public class PrivateMessageValidatorTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
private final BdfReaderFactory bdfReaderFactory =
|
||||||
|
context.mock(BdfReaderFactory.class);
|
||||||
|
private final MetadataEncoder metadataEncoder =
|
||||||
|
context.mock(MetadataEncoder.class);
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final BdfReader reader = context.mock(BdfReader.class);
|
||||||
|
|
||||||
|
private final Group group = getGroup(getClientId(), 123);
|
||||||
|
private final Message message = getMessage(group.getId());
|
||||||
|
private final long now = message.getTimestamp() + 1000;
|
||||||
|
private final String text =
|
||||||
|
getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
|
private final BdfList attachmentHeader = getAttachmentHeader();
|
||||||
|
private final MessageId attachmentId = new MessageId(getRandomId());
|
||||||
|
private final String contentType = getRandomString(MAX_CONTENT_TYPE_BYTES);
|
||||||
|
private final BdfDictionary legacyMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_TIMESTAMP, message.getTimestamp()),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false),
|
||||||
|
new BdfEntry(MSG_KEY_READ, false)
|
||||||
|
);
|
||||||
|
private final BdfDictionary noAttachmentsMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_TIMESTAMP, message.getTimestamp()),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false),
|
||||||
|
new BdfEntry(MSG_KEY_READ, false),
|
||||||
|
new BdfEntry(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE),
|
||||||
|
new BdfEntry(MSG_KEY_HAS_TEXT, true),
|
||||||
|
new BdfEntry(MSG_KEY_ATTACHMENT_HEADERS, new BdfList())
|
||||||
|
);
|
||||||
|
private final BdfDictionary noTextMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_TIMESTAMP, message.getTimestamp()),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false),
|
||||||
|
new BdfEntry(MSG_KEY_READ, false),
|
||||||
|
new BdfEntry(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE),
|
||||||
|
new BdfEntry(MSG_KEY_HAS_TEXT, false),
|
||||||
|
new BdfEntry(MSG_KEY_ATTACHMENT_HEADERS,
|
||||||
|
BdfList.of(attachmentHeader))
|
||||||
|
);
|
||||||
|
private final BdfDictionary attachmentMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_TIMESTAMP, message.getTimestamp()),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false),
|
||||||
|
new BdfEntry(MSG_KEY_MSG_TYPE, ATTACHMENT),
|
||||||
|
// Descriptor length is zero as the test doesn't read from the
|
||||||
|
// counting input stream
|
||||||
|
new BdfEntry(MSG_KEY_DESCRIPTOR_LENGTH, 0L),
|
||||||
|
new BdfEntry(MSG_KEY_CONTENT_TYPE, contentType)
|
||||||
|
);
|
||||||
|
|
||||||
|
private final PrivateMessageValidator validator =
|
||||||
|
new PrivateMessageValidator(bdfReaderFactory, metadataEncoder,
|
||||||
|
clock);
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsFarFutureTimestamp() throws Exception {
|
||||||
|
expectCheckTimestamp(message.getTimestamp() - MAX_CLOCK_DIFFERENCE - 1);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
public void testRejectsTooShortBody() throws Exception {
|
public void testRejectsTooShortBody() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
expectCheckTimestamp(now);
|
||||||
metadataEncoder, clock);
|
expectParseList(new BdfList());
|
||||||
v.validateMessage(message, group, new BdfList());
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = InvalidMessageException.class)
|
||||||
public void testRejectsTooLongBody() throws Exception {
|
public void testRejectsTrailingDataForLegacyMessage() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
expectCheckTimestamp(now);
|
||||||
metadataEncoder, clock);
|
expectParseList(BdfList.of(text));
|
||||||
v.validateMessage(message, group, BdfList.of("", 123));
|
expectReadEof(false);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = InvalidMessageException.class)
|
||||||
public void testRejectsNullText() throws Exception {
|
public void testRejectsNullTextForLegacyMessage() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
testRejectsLegacyMessage(BdfList.of((String) null));
|
||||||
metadataEncoder, clock);
|
|
||||||
v.validateMessage(message, group, BdfList.of((String) null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = InvalidMessageException.class)
|
||||||
public void testRejectsNonStringText() throws Exception {
|
public void testRejectsNonStringTextForLegacyMessage() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
testRejectsLegacyMessage(BdfList.of(123));
|
||||||
metadataEncoder, clock);
|
|
||||||
v.validateMessage(message, group, BdfList.of(123));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = InvalidMessageException.class)
|
||||||
public void testRejectsTooLongText() throws Exception {
|
public void testRejectsTooLongTextForLegacyMessage() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
|
||||||
metadataEncoder, clock);
|
|
||||||
String invalidText =
|
String invalidText =
|
||||||
getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH + 1);
|
getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH + 1);
|
||||||
v.validateMessage(message, group, BdfList.of(invalidText));
|
|
||||||
|
testRejectsLegacyMessage(BdfList.of(invalidText));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAcceptsMaxLengthText() throws Exception {
|
public void testAcceptsMaxLengthTextForLegacyMessage() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
testAcceptsLegacyMessage(BdfList.of(text));
|
||||||
metadataEncoder, clock);
|
|
||||||
String text = getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
|
||||||
BdfMessageContext messageContext =
|
|
||||||
v.validateMessage(message, group, BdfList.of(text));
|
|
||||||
assertExpectedContext(messageContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAcceptsMinLengthText() throws Exception {
|
public void testAcceptsMinLengthTextForLegacyMessage() throws Exception {
|
||||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
testAcceptsLegacyMessage(BdfList.of(""));
|
||||||
metadataEncoder, clock);
|
|
||||||
BdfMessageContext messageContext =
|
|
||||||
v.validateMessage(message, group, BdfList.of(""));
|
|
||||||
assertExpectedContext(messageContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertExpectedContext(BdfMessageContext messageContext)
|
@Test(expected = InvalidMessageException.class)
|
||||||
throws FormatException {
|
public void testRejectsTrailingDataForPrivateMessage() throws Exception {
|
||||||
BdfDictionary meta = messageContext.getDictionary();
|
expectCheckTimestamp(now);
|
||||||
assertEquals(3, meta.size());
|
expectParseList(BdfList.of(PRIVATE_MESSAGE, text, new BdfList()));
|
||||||
assertEquals(timestamp, meta.getLong("timestamp").longValue());
|
expectReadEof(false);
|
||||||
assertFalse(meta.getBoolean("local"));
|
|
||||||
assertFalse(meta.getBoolean(MSG_KEY_READ));
|
validator.validateMessage(message, group);
|
||||||
assertEquals(0, messageContext.getDependencies().size());
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooShortBodyForPrivateMessage() throws Exception {
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongBodyForPrivateMessage() throws Exception {
|
||||||
|
testRejectsPrivateMessage(
|
||||||
|
BdfList.of(PRIVATE_MESSAGE, text, new BdfList(), 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNullTextWithoutAttachmentsForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
testRejectsPrivateMessage(
|
||||||
|
BdfList.of(PRIVATE_MESSAGE, null, new BdfList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsNullTextWithAttachmentsForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
testAcceptsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, null,
|
||||||
|
BdfList.of(attachmentHeader)), noTextMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNonStringTextForPrivateMessage() throws Exception {
|
||||||
|
testRejectsPrivateMessage(
|
||||||
|
BdfList.of(PRIVATE_MESSAGE, 123, new BdfList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongTextForPrivateMessage() throws Exception {
|
||||||
|
String invalidText =
|
||||||
|
getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH + 1);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(
|
||||||
|
BdfList.of(PRIVATE_MESSAGE, invalidText, new BdfList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsMaxLengthTextForPrivateMessage() throws Exception {
|
||||||
|
testAcceptsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
new BdfList()), noAttachmentsMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsMinLengthTextForPrivateMessage() throws Exception {
|
||||||
|
testAcceptsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, "",
|
||||||
|
new BdfList()), noAttachmentsMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNullAttachmentListForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNonListAttachmentListForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongAttachmentListForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidList = new BdfList();
|
||||||
|
for (int i = 0; i < MAX_ATTACHMENTS_PER_MESSAGE + 1; i++) {
|
||||||
|
invalidList.add(getAttachmentHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(
|
||||||
|
BdfList.of(PRIVATE_MESSAGE, text, invalidList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsMaxLengthAttachmentListForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList attachmentList = new BdfList();
|
||||||
|
for (int i = 0; i < MAX_ATTACHMENTS_PER_MESSAGE; i++) {
|
||||||
|
attachmentList.add(getAttachmentHeader());
|
||||||
|
}
|
||||||
|
BdfDictionary maxAttachmentsMeta = new BdfDictionary(noAttachmentsMeta);
|
||||||
|
maxAttachmentsMeta.put(MSG_KEY_ATTACHMENT_HEADERS, attachmentList);
|
||||||
|
|
||||||
|
testAcceptsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
attachmentList), maxAttachmentsMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNullAttachmentIdForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader = BdfList.of(null, contentType);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNonRawAttachmentIdForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader = BdfList.of(123, contentType);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooShortAttachmentIdForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader =
|
||||||
|
BdfList.of(getRandomBytes(UniqueId.LENGTH - 1), contentType);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongAttachmentIdForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader =
|
||||||
|
BdfList.of(getRandomBytes(UniqueId.LENGTH + 1), contentType);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNullContentTypeForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader = BdfList.of(attachmentId, null);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNonStringContentTypeForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader = BdfList.of(attachmentId, 123);
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooShortContentTypeForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader = BdfList.of(attachmentId, "");
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongContentTypeForPrivateMessage()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidHeader = BdfList.of(attachmentId,
|
||||||
|
getRandomString(MAX_CONTENT_TYPE_BYTES + 1));
|
||||||
|
|
||||||
|
testRejectsPrivateMessage(BdfList.of(PRIVATE_MESSAGE, text,
|
||||||
|
BdfList.of(invalidHeader)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooShortDescriptorWithoutTrailingDataForAttachment()
|
||||||
|
throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(BdfList.of(ATTACHMENT));
|
||||||
|
// Single-element list is interpreted as a legacy private message, so
|
||||||
|
// EOF is expected
|
||||||
|
expectReadEof(true);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooShortDescriptorWithTrailingDataForAttachment()
|
||||||
|
throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(BdfList.of(ATTACHMENT));
|
||||||
|
// Single-element list is interpreted as a legacy private message, so
|
||||||
|
// EOF is expected
|
||||||
|
expectReadEof(false);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongDescriptorForAttachment() throws Exception {
|
||||||
|
testRejectsAttachment(BdfList.of(ATTACHMENT, contentType, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNullContentTypeForAttachment() throws Exception {
|
||||||
|
testRejectsAttachment(BdfList.of(ATTACHMENT, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsNonStringContentTypeForAttachment()
|
||||||
|
throws Exception {
|
||||||
|
testRejectsAttachment(BdfList.of(ATTACHMENT, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooShortContentTypeForAttachment() throws Exception {
|
||||||
|
testRejectsAttachment(BdfList.of(ATTACHMENT, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsTooLongContentTypeForAttachment() throws Exception {
|
||||||
|
String invalidContentType = getRandomString(MAX_CONTENT_TYPE_BYTES + 1);
|
||||||
|
|
||||||
|
testRejectsAttachment(BdfList.of(ATTACHMENT, invalidContentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsValidDescriptorForAttachment() throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(BdfList.of(ATTACHMENT, contentType));
|
||||||
|
expectEncodeMetadata(attachmentMeta);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
public void testRejectsUnknownMessageType() throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(BdfList.of(ATTACHMENT + 1, contentType));
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRejectsLegacyMessage(BdfList body) throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(body);
|
||||||
|
expectReadEof(true);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testAcceptsLegacyMessage(BdfList body) throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(body);
|
||||||
|
expectReadEof(true);
|
||||||
|
expectEncodeMetadata(legacyMeta);
|
||||||
|
|
||||||
|
MessageContext result = validator.validateMessage(message, group);
|
||||||
|
assertEquals(0, result.getDependencies().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRejectsPrivateMessage(BdfList body) throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(body);
|
||||||
|
expectReadEof(true);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testAcceptsPrivateMessage(BdfList body, BdfDictionary meta)
|
||||||
|
throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(body);
|
||||||
|
expectReadEof(true);
|
||||||
|
expectEncodeMetadata(meta);
|
||||||
|
|
||||||
|
MessageContext result = validator.validateMessage(message, group);
|
||||||
|
assertEquals(0, result.getDependencies().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRejectsAttachment(BdfList descriptor) throws Exception {
|
||||||
|
expectCheckTimestamp(now);
|
||||||
|
expectParseList(descriptor);
|
||||||
|
|
||||||
|
validator.validateMessage(message, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectCheckTimestamp(long now) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectParseList(BdfList body) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(bdfReaderFactory).createReader(with(any(InputStream.class)));
|
||||||
|
will(returnValue(reader));
|
||||||
|
oneOf(reader).readList();
|
||||||
|
will(returnValue(body));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReadEof(boolean eof) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(reader).eof();
|
||||||
|
will(returnValue(eof));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectEncodeMetadata(BdfDictionary meta) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(metadataEncoder).encode(meta);
|
||||||
|
will(returnValue(new Metadata()));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList getAttachmentHeader() {
|
||||||
|
return BdfList.of(new MessageId(getRandomId()),
|
||||||
|
getRandomString(MAX_CONTENT_TYPE_BYTES));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
|||||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||||
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
import org.briarproject.bramble.test.TestTransportConnectionReader;
|
||||||
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
import org.briarproject.bramble.test.TestTransportConnectionWriter;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
|
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||||
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
||||||
import org.briarproject.briar.test.BriarTestCase;
|
import org.briarproject.briar.test.BriarTestCase;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@@ -27,9 +29,10 @@ import org.junit.Test;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID;
|
import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID;
|
||||||
@@ -84,10 +87,12 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
read(bob, write(alice, bobId), 2);
|
read(bob, write(alice, bobId), 2);
|
||||||
// Sync Bob's client versions and transport properties
|
// Sync Bob's client versions and transport properties
|
||||||
read(alice, write(bob, aliceId), 2);
|
read(alice, write(bob, aliceId), 2);
|
||||||
// Sync the private message
|
// Sync the private message and the attachment
|
||||||
read(bob, write(alice, bobId), 1);
|
read(bob, write(alice, bobId), 2);
|
||||||
// Bob should have received the private message
|
// Bob should have received the private message
|
||||||
assertTrue(listener.messageAdded);
|
assertTrue(listener.messageAdded);
|
||||||
|
// Bob should have received the attachment
|
||||||
|
assertTrue(listener.attachmentAdded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
|
private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
|
||||||
@@ -107,16 +112,20 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
|
|
||||||
private void sendMessage(SimplexMessagingIntegrationTestComponent device,
|
private void sendMessage(SimplexMessagingIntegrationTestComponent device,
|
||||||
ContactId contactId) throws Exception {
|
ContactId contactId) throws Exception {
|
||||||
// Send Bob a message
|
|
||||||
MessagingManager messagingManager = device.getMessagingManager();
|
MessagingManager messagingManager = device.getMessagingManager();
|
||||||
GroupId groupId = messagingManager.getConversationId(contactId);
|
GroupId groupId = messagingManager.getConversationId(contactId);
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
InputStream in = new ByteArrayInputStream(new byte[] {0, 1, 2, 3});
|
||||||
|
AttachmentHeader attachmentHeader = messagingManager.addLocalAttachment(
|
||||||
|
groupId, timestamp, "image/png", in);
|
||||||
PrivateMessageFactory privateMessageFactory =
|
PrivateMessageFactory privateMessageFactory =
|
||||||
device.getPrivateMessageFactory();
|
device.getPrivateMessageFactory();
|
||||||
PrivateMessage message = privateMessageFactory.createPrivateMessage(
|
PrivateMessage message = privateMessageFactory.createPrivateMessage(
|
||||||
groupId, System.currentTimeMillis(), "Hi!", emptyList());
|
groupId, timestamp, "Hi!", singletonList(attachmentHeader));
|
||||||
messagingManager.addLocalMessage(message);
|
messagingManager.addLocalMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
private void read(SimplexMessagingIntegrationTestComponent device,
|
private void read(SimplexMessagingIntegrationTestComponent device,
|
||||||
byte[] stream, int deliveries) throws Exception {
|
byte[] stream, int deliveries) throws Exception {
|
||||||
// Listen for message deliveries
|
// Listen for message deliveries
|
||||||
@@ -187,10 +196,15 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
private static class PrivateMessageListener implements EventListener {
|
private static class PrivateMessageListener implements EventListener {
|
||||||
|
|
||||||
private volatile boolean messageAdded = false;
|
private volatile boolean messageAdded = false;
|
||||||
|
private volatile boolean attachmentAdded = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof PrivateMessageReceivedEvent) messageAdded = true;
|
if (e instanceof PrivateMessageReceivedEvent) {
|
||||||
|
messageAdded = true;
|
||||||
|
} else if (e instanceof AttachmentReceivedEvent) {
|
||||||
|
attachmentAdded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
|
|||||||
oneOf(messageEncoder).encodeMetadata(type, privateGroupId,
|
oneOf(messageEncoder).encodeMetadata(type, privateGroupId,
|
||||||
message.getTimestamp(), true, true, visible, false, false);
|
message.getTimestamp(), true, true, visible, false, false);
|
||||||
will(returnValue(meta));
|
will(returnValue(meta));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
|
oneOf(clientHelper).addLocalMessage(txn, message, meta, true,
|
||||||
|
false);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.headless
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager
|
import org.briarproject.bramble.api.battery.BatteryManager
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig
|
import org.briarproject.bramble.api.db.DatabaseConfig
|
||||||
import org.briarproject.bramble.api.event.EventBus
|
import org.briarproject.bramble.api.event.EventBus
|
||||||
@@ -94,4 +95,9 @@ internal class HeadlessModule(private val appDir: File) {
|
|||||||
@Singleton
|
@Singleton
|
||||||
internal fun provideObjectMapper() = ObjectMapper()
|
internal fun provideObjectMapper() = ObjectMapper()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
internal fun provideFeatureFlags() = object : FeatureFlags {
|
||||||
|
override fun shouldEnableImageAttachments() = false
|
||||||
|
override fun shouldEnableRemoteContacts() = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ internal class OutputEvent(val name: String, val data: JsonDict) {
|
|||||||
val type = "event"
|
val type = "event"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ConversationMessageReceivedEvent<*>.output(text: String): JsonDict {
|
internal fun ConversationMessageReceivedEvent<*>.output(text: String?): JsonDict {
|
||||||
check(messageHeader is PrivateMessageHeader)
|
check(messageHeader is PrivateMessageHeader)
|
||||||
return (messageHeader as PrivateMessageHeader).output(contactId, text)
|
return (messageHeader as PrivateMessageHeader).output(contactId, text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,16 +67,16 @@ constructor(
|
|||||||
override fun write(ctx: Context): Context {
|
override fun write(ctx: Context): Context {
|
||||||
val contact = getContact(ctx)
|
val contact = getContact(ctx)
|
||||||
|
|
||||||
val message = ctx.getFromJson(objectMapper, "text")
|
val text = ctx.getFromJson(objectMapper, "text")
|
||||||
if (utf8IsTooLong(message, MAX_PRIVATE_MESSAGE_TEXT_LENGTH))
|
if (utf8IsTooLong(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH))
|
||||||
throw BadRequestResponse("Message text is too long")
|
throw BadRequestResponse("Message text is too long")
|
||||||
|
|
||||||
val group = messagingManager.getContactGroup(contact)
|
val group = messagingManager.getContactGroup(contact)
|
||||||
val now = clock.currentTimeMillis()
|
val now = clock.currentTimeMillis()
|
||||||
val m = privateMessageFactory.createPrivateMessage(group.id, now, message, emptyList())
|
val m = privateMessageFactory.createLegacyPrivateMessage(group.id, now, text)
|
||||||
|
|
||||||
messagingManager.addLocalMessage(m)
|
messagingManager.addLocalMessage(m)
|
||||||
return ctx.json(m.output(contact.id, message))
|
return ctx.json(m.output(contact.id, text))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun eventOccurred(e: Event) {
|
override fun eventOccurred(e: Event) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.headless
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig
|
import org.briarproject.bramble.api.db.DatabaseConfig
|
||||||
import org.briarproject.bramble.api.plugin.PluginConfig
|
import org.briarproject.bramble.api.plugin.PluginConfig
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory
|
||||||
@@ -61,4 +62,9 @@ internal class HeadlessTestModule(private val appDir: File) {
|
|||||||
@Singleton
|
@Singleton
|
||||||
internal fun provideObjectMapper() = ObjectMapper()
|
internal fun provideObjectMapper() = ObjectMapper()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
internal fun provideFeatureFlags() = object : FeatureFlags {
|
||||||
|
override fun shouldEnableImageAttachments() = false
|
||||||
|
override fun shouldEnableRemoteContacts() = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,11 +115,10 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
|||||||
every { messagingManager.getContactGroup(contact) } returns group
|
every { messagingManager.getContactGroup(contact) } returns group
|
||||||
every { clock.currentTimeMillis() } returns timestamp
|
every { clock.currentTimeMillis() } returns timestamp
|
||||||
every {
|
every {
|
||||||
privateMessageFactory.createPrivateMessage(
|
privateMessageFactory.createLegacyPrivateMessage(
|
||||||
group.id,
|
group.id,
|
||||||
timestamp,
|
timestamp,
|
||||||
text,
|
text
|
||||||
emptyList()
|
|
||||||
)
|
)
|
||||||
} returns privateMessage
|
} returns privateMessage
|
||||||
every { messagingManager.addLocalMessage(privateMessage) } just runs
|
every { messagingManager.addLocalMessage(privateMessage) } just runs
|
||||||
@@ -169,7 +168,12 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
|||||||
val event = PrivateMessageReceivedEvent(header, contact.id)
|
val event = PrivateMessageReceivedEvent(header, contact.id)
|
||||||
|
|
||||||
every { messagingManager.getMessageText(message.id) } returns text
|
every { messagingManager.getMessageText(message.id) } returns text
|
||||||
every { webSocketController.sendEvent(EVENT_CONVERSATION_MESSAGE, event.output(text)) } just runs
|
every {
|
||||||
|
webSocketController.sendEvent(
|
||||||
|
EVENT_CONVERSATION_MESSAGE,
|
||||||
|
event.output(text)
|
||||||
|
)
|
||||||
|
} just runs
|
||||||
|
|
||||||
controller.eventOccurred(event)
|
controller.eventOccurred(event)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user