When replying to a message, don't use an earlier timestamp.

This produces a saner user experience when devices have differing
clocks.
This commit is contained in:
akwizgran
2013-12-11 16:25:00 +00:00
parent ba9ea9da1c
commit 1d4213e9c6
10 changed files with 46 additions and 27 deletions

View File

@@ -244,7 +244,8 @@ NoContactsDialog.Listener {
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
noContactsDialog.show(fm, "NoContactsDialog"); noContactsDialog.show(fm, "NoContactsDialog");
} else { } else {
startActivity(new Intent(this, WritePrivateMessageActivity.class)); startActivity(new Intent(this,
WritePrivateMessageActivity.class));
} }
} else if(view == shareButton) { } else if(view == shareButton) {
String apkPath = getPackageCodePath(); String apkPath = getPackageCodePath();

View File

@@ -60,6 +60,7 @@ implements OnClickListener {
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Inject private volatile LifecycleManager lifecycleManager; @Inject private volatile LifecycleManager lifecycleManager;
private volatile MessageId messageId = null; private volatile MessageId messageId = null;
private volatile long timestamp = -1;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
@@ -79,7 +80,7 @@ implements OnClickListener {
messageId = new MessageId(b); messageId = new MessageId(b);
String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE"); String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
if(contentType == null) throw new IllegalStateException(); if(contentType == null) throw new IllegalStateException();
long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1); timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(timestamp == -1) throw new IllegalStateException(); if(timestamp == -1) throw new IllegalStateException();
if(state == null) { if(state == null) {
@@ -262,6 +263,7 @@ implements OnClickListener {
Intent i = new Intent(this, WritePrivateMessageActivity.class); Intent i = new Intent(this, WritePrivateMessageActivity.class);
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt()); i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes()); i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes());
i.putExtra("net.sf.briar.TIMESTAMP", timestamp);
startActivity(i); startActivity(i);
setResult(RESULT_REPLY); setResult(RESULT_REPLY);
finish(); finish();

View File

@@ -66,6 +66,7 @@ implements OnItemSelectedListener, OnClickListener {
private volatile LocalAuthor localAuthor = null; private volatile LocalAuthor localAuthor = null;
private volatile ContactId contactId = null; private volatile ContactId contactId = null;
private volatile MessageId parentId = null; private volatile MessageId parentId = null;
private volatile long timestamp = -1;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
@@ -76,6 +77,7 @@ implements OnItemSelectedListener, OnClickListener {
if(id != -1) contactId = new ContactId(id); if(id != -1) contactId = new ContactId(id);
byte[] b = i.getByteArrayExtra("net.sf.briar.PARENT_ID"); byte[] b = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
if(b != null) parentId = new MessageId(b); if(b != null) parentId = new MessageId(b);
timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(state != null) { if(state != null) {
id = state.getInt("net.sf.briar.CONTACT_ID", -1); id = state.getInt("net.sf.briar.CONTACT_ID", -1);
@@ -262,8 +264,11 @@ implements OnItemSelectedListener, OnClickListener {
public void run() { public void run() {
try { try {
lifecycleManager.waitForDatabase(); lifecycleManager.waitForDatabase();
// Don't use an earlier timestamp than the parent
long time = System.currentTimeMillis();
time = Math.max(time, timestamp + 1);
Message m = messageFactory.createPrivateMessage(parentId, Message m = messageFactory.createPrivateMessage(parentId,
"text/plain", body); "text/plain", time, body);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
db.addLocalPrivateMessage(m, contactId); db.addLocalPrivateMessage(m, contactId);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;

View File

@@ -60,6 +60,7 @@ implements OnClickListener {
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Inject private volatile LifecycleManager lifecycleManager; @Inject private volatile LifecycleManager lifecycleManager;
private volatile MessageId messageId = null; private volatile MessageId messageId = null;
private volatile long timestamp = -1;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
@@ -78,7 +79,7 @@ implements OnClickListener {
String authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME"); String authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME");
String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE"); String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
if(contentType == null) throw new IllegalStateException(); if(contentType == null) throw new IllegalStateException();
long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1); timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(timestamp == -1) throw new IllegalStateException(); if(timestamp == -1) throw new IllegalStateException();
if(state == null) { if(state == null) {
@@ -266,6 +267,7 @@ implements OnClickListener {
Intent i = new Intent(this, WriteGroupPostActivity.class); Intent i = new Intent(this, WriteGroupPostActivity.class);
i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes()); i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes());
i.putExtra("net.sf.briar.TIMESTAMP", timestamp);
startActivity(i); startActivity(i);
setResult(RESULT_REPLY); setResult(RESULT_REPLY);
finish(); finish();

View File

@@ -74,6 +74,7 @@ implements OnItemSelectedListener, OnClickListener {
private volatile LocalAuthor localAuthor = null; private volatile LocalAuthor localAuthor = null;
private volatile Group group = null; private volatile Group group = null;
private volatile MessageId parentId = null; private volatile MessageId parentId = null;
private volatile long timestamp = -1;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
@@ -84,6 +85,7 @@ implements OnItemSelectedListener, OnClickListener {
if(b != null) groupId = new GroupId(b); if(b != null) groupId = new GroupId(b);
b = i.getByteArrayExtra("net.sf.briar.PARENT_ID"); b = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
if(b != null) parentId = new MessageId(b); if(b != null) parentId = new MessageId(b);
timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(state != null) { if(state != null) {
b = state.getByteArray("net.sf.briar.LOCAL_AUTHOR_ID"); b = state.getByteArray("net.sf.briar.LOCAL_AUTHOR_ID");
@@ -318,15 +320,18 @@ implements OnItemSelectedListener, OnClickListener {
// FIXME: This should happen on a CryptoExecutor thread // FIXME: This should happen on a CryptoExecutor thread
private Message createMessage(byte[] body) throws IOException, private Message createMessage(byte[] body) throws IOException,
GeneralSecurityException { GeneralSecurityException {
// Don't use an earlier timestamp than the parent
long time = System.currentTimeMillis();
time = Math.max(time, timestamp + 1);
if(localAuthor == null) { if(localAuthor == null) {
return messageFactory.createAnonymousMessage(parentId, group, return messageFactory.createAnonymousMessage(parentId, group,
"text/plain", body); "text/plain", time, body);
} else { } else {
KeyParser keyParser = crypto.getSignatureKeyParser(); KeyParser keyParser = crypto.getSignatureKeyParser();
byte[] authorKeyBytes = localAuthor.getPrivateKey(); byte[] authorKeyBytes = localAuthor.getPrivateKey();
PrivateKey authorKey = keyParser.parsePrivateKey(authorKeyBytes); PrivateKey authorKey = keyParser.parsePrivateKey(authorKeyBytes);
return messageFactory.createPseudonymousMessage(parentId, return messageFactory.createPseudonymousMessage(parentId,
group, localAuthor, authorKey, "text/plain", body); group, localAuthor, authorKey, "text/plain", time, body);
} }
} }

View File

@@ -10,15 +10,17 @@ public interface MessageFactory {
/** Creates a private message. */ /** Creates a private message. */
Message createPrivateMessage(MessageId parent, String contentType, Message createPrivateMessage(MessageId parent, String contentType,
byte[] body) throws IOException, GeneralSecurityException; long timestamp, byte[] body) throws IOException,
GeneralSecurityException;
/** Creates an anonymous group message. */ /** Creates an anonymous group message. */
Message createAnonymousMessage(MessageId parent, Group group, Message createAnonymousMessage(MessageId parent, Group group,
String contentType, byte[] body) throws IOException, String contentType, long timestamp, byte[] body) throws IOException,
GeneralSecurityException; GeneralSecurityException;
/** Creates a pseudonymous group message. */ /** Creates a pseudonymous group message. */
Message createPseudonymousMessage(MessageId parent, Group group, Message createPseudonymousMessage(MessageId parent, Group group,
Author author, PrivateKey privateKey, String contentType, Author author, PrivateKey privateKey, String contentType,
byte[] body) throws IOException, GeneralSecurityException; long timestamp, byte[] body) throws IOException,
GeneralSecurityException;
} }

View File

@@ -17,7 +17,6 @@ import java.security.SecureRandom;
import javax.inject.Inject; import javax.inject.Inject;
import net.sf.briar.api.Author; import net.sf.briar.api.Author;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest; import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.crypto.PrivateKey; import net.sf.briar.api.crypto.PrivateKey;
@@ -39,39 +38,40 @@ class MessageFactoryImpl implements MessageFactory {
private final SecureRandom random; private final SecureRandom random;
private final MessageDigest messageDigest; private final MessageDigest messageDigest;
private final WriterFactory writerFactory; private final WriterFactory writerFactory;
private final Clock clock;
@Inject @Inject
MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory, MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory) {
Clock clock) {
signature = crypto.getSignature(); signature = crypto.getSignature();
random = crypto.getSecureRandom(); random = crypto.getSecureRandom();
messageDigest = crypto.getMessageDigest(); messageDigest = crypto.getMessageDigest();
this.writerFactory = writerFactory; this.writerFactory = writerFactory;
this.clock = clock;
} }
public Message createPrivateMessage(MessageId parent, String contentType, public Message createPrivateMessage(MessageId parent, String contentType,
byte[] body) throws IOException, GeneralSecurityException { long timestamp, byte[] body) throws IOException,
return createMessage(parent, null, null, null, contentType, body); GeneralSecurityException {
return createMessage(parent, null, null, null, contentType, timestamp,
body);
} }
public Message createAnonymousMessage(MessageId parent, Group group, public Message createAnonymousMessage(MessageId parent, Group group,
String contentType, byte[] body) throws IOException, String contentType, long timestamp, byte[] body) throws IOException,
GeneralSecurityException { GeneralSecurityException {
return createMessage(parent, group, null, null, contentType, body); return createMessage(parent, group, null, null, contentType, timestamp,
body);
} }
public Message createPseudonymousMessage(MessageId parent, Group group, public Message createPseudonymousMessage(MessageId parent, Group group,
Author author, PrivateKey privateKey, String contentType, Author author, PrivateKey privateKey, String contentType,
byte[] body) throws IOException, GeneralSecurityException { long timestamp, byte[] body) throws IOException,
GeneralSecurityException {
return createMessage(parent, group, author, privateKey, contentType, return createMessage(parent, group, author, privateKey, contentType,
body); timestamp, body);
} }
private Message createMessage(MessageId parent, Group group, Author author, private Message createMessage(MessageId parent, Group group, Author author,
PrivateKey privateKey, String contentType, byte[] body) PrivateKey privateKey, String contentType, long timestamp,
throws IOException, GeneralSecurityException { byte[] body) throws IOException, GeneralSecurityException {
// Validate the arguments // Validate the arguments
if((author == null) != (privateKey == null)) if((author == null) != (privateKey == null))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -102,7 +102,6 @@ class MessageFactoryImpl implements MessageFactory {
if(author == null) w.writeNull(); if(author == null) w.writeNull();
else writeAuthor(w, author); else writeAuthor(w, author);
w.writeString(contentType); w.writeString(contentType);
long timestamp = clock.currentTimeMillis();
w.writeIntAny(timestamp); w.writeIntAny(timestamp);
byte[] salt = new byte[MESSAGE_SALT_LENGTH]; byte[] salt = new byte[MESSAGE_SALT_LENGTH];
random.nextBytes(salt); random.nextBytes(salt);

View File

@@ -71,6 +71,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
private final Message message, message1; private final Message message, message1;
private final String authorName = "Alice"; private final String authorName = "Alice";
private final String contentType = "text/plain"; private final String contentType = "text/plain";
private final long timestamp = System.currentTimeMillis();
private final String messageBody = "Hello world"; private final String messageBody = "Hello world";
private final Collection<MessageId> messageIds; private final Collection<MessageId> messageIds;
private final TransportId transportId; private final TransportId transportId;
@@ -104,9 +105,9 @@ public class ProtocolIntegrationTest extends BriarTestCase {
// Create two messages to the group: one anonymous, one pseudonymous // Create two messages to the group: one anonymous, one pseudonymous
MessageFactory messageFactory = i.getInstance(MessageFactory.class); MessageFactory messageFactory = i.getInstance(MessageFactory.class);
message = messageFactory.createAnonymousMessage(null, group, message = messageFactory.createAnonymousMessage(null, group,
contentType, messageBody.getBytes("UTF-8")); contentType, timestamp, messageBody.getBytes("UTF-8"));
message1 = messageFactory.createPseudonymousMessage(null, group, message1 = messageFactory.createPseudonymousMessage(null, group,
author, authorKeyPair.getPrivate(), contentType, author, authorKeyPair.getPrivate(), contentType, timestamp,
messageBody.getBytes("UTF-8")); messageBody.getBytes("UTF-8"));
messageIds = Arrays.asList(message.getId(), message1.getId()); messageIds = Arrays.asList(message.getId(), message1.getId());
// Create some transport properties // Create some transport properties

View File

@@ -131,9 +131,10 @@ public class ConstantsTest extends BriarTestCase {
PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate(); PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
String contentType = String contentType =
TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH); TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH);
long timestamp = Long.MAX_VALUE;
byte[] body = new byte[MAX_BODY_LENGTH]; byte[] body = new byte[MAX_BODY_LENGTH];
Message message = messageFactory.createPseudonymousMessage(parent, Message message = messageFactory.createPseudonymousMessage(parent,
group, author, privateKey, contentType, body); group, author, privateKey, contentType, timestamp, body);
// Check the size of the serialised message // Check the size of the serialised message
int length = message.getSerialised().length; int length = message.getSerialised().length;
assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH

View File

@@ -123,10 +123,11 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
km.endpointAdded(ep, LATENCY, initialSecret.clone()); km.endpointAdded(ep, LATENCY, initialSecret.clone());
// Send Bob a message // Send Bob a message
String contentType = "text/plain"; String contentType = "text/plain";
long timestamp = System.currentTimeMillis();
byte[] body = "Hi Bob!".getBytes("UTF-8"); byte[] body = "Hi Bob!".getBytes("UTF-8");
MessageFactory messageFactory = alice.getInstance(MessageFactory.class); MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
Message message = messageFactory.createPrivateMessage(null, contentType, Message message = messageFactory.createPrivateMessage(null, contentType,
body); timestamp, body);
db.addLocalPrivateMessage(message, contactId); db.addLocalPrivateMessage(message, contactId);
// Create an outgoing simplex connection // Create an outgoing simplex connection
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();