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();
noContactsDialog.show(fm, "NoContactsDialog");
} else {
startActivity(new Intent(this, WritePrivateMessageActivity.class));
startActivity(new Intent(this,
WritePrivateMessageActivity.class));
}
} else if(view == shareButton) {
String apkPath = getPackageCodePath();

View File

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

View File

@@ -66,6 +66,7 @@ implements OnItemSelectedListener, OnClickListener {
private volatile LocalAuthor localAuthor = null;
private volatile ContactId contactId = null;
private volatile MessageId parentId = null;
private volatile long timestamp = -1;
@Override
public void onCreate(Bundle state) {
@@ -76,6 +77,7 @@ implements OnItemSelectedListener, OnClickListener {
if(id != -1) contactId = new ContactId(id);
byte[] b = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
if(b != null) parentId = new MessageId(b);
timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(state != null) {
id = state.getInt("net.sf.briar.CONTACT_ID", -1);
@@ -262,8 +264,11 @@ implements OnItemSelectedListener, OnClickListener {
public void run() {
try {
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,
"text/plain", body);
"text/plain", time, body);
long now = System.currentTimeMillis();
db.addLocalPrivateMessage(m, contactId);
long duration = System.currentTimeMillis() - now;

View File

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

View File

@@ -74,6 +74,7 @@ implements OnItemSelectedListener, OnClickListener {
private volatile LocalAuthor localAuthor = null;
private volatile Group group = null;
private volatile MessageId parentId = null;
private volatile long timestamp = -1;
@Override
public void onCreate(Bundle state) {
@@ -84,6 +85,7 @@ implements OnItemSelectedListener, OnClickListener {
if(b != null) groupId = new GroupId(b);
b = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
if(b != null) parentId = new MessageId(b);
timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(state != null) {
b = state.getByteArray("net.sf.briar.LOCAL_AUTHOR_ID");
@@ -318,15 +320,18 @@ implements OnItemSelectedListener, OnClickListener {
// FIXME: This should happen on a CryptoExecutor thread
private Message createMessage(byte[] body) throws IOException,
GeneralSecurityException {
// Don't use an earlier timestamp than the parent
long time = System.currentTimeMillis();
time = Math.max(time, timestamp + 1);
if(localAuthor == null) {
return messageFactory.createAnonymousMessage(parentId, group,
"text/plain", body);
"text/plain", time, body);
} else {
KeyParser keyParser = crypto.getSignatureKeyParser();
byte[] authorKeyBytes = localAuthor.getPrivateKey();
PrivateKey authorKey = keyParser.parsePrivateKey(authorKeyBytes);
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. */
Message createPrivateMessage(MessageId parent, String contentType,
byte[] body) throws IOException, GeneralSecurityException;
long timestamp, byte[] body) throws IOException,
GeneralSecurityException;
/** Creates an anonymous group message. */
Message createAnonymousMessage(MessageId parent, Group group,
String contentType, byte[] body) throws IOException,
String contentType, long timestamp, byte[] body) throws IOException,
GeneralSecurityException;
/** Creates a pseudonymous group message. */
Message createPseudonymousMessage(MessageId parent, Group group,
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 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.MessageDigest;
import net.sf.briar.api.crypto.PrivateKey;
@@ -39,39 +38,40 @@ class MessageFactoryImpl implements MessageFactory {
private final SecureRandom random;
private final MessageDigest messageDigest;
private final WriterFactory writerFactory;
private final Clock clock;
@Inject
MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory,
Clock clock) {
MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory) {
signature = crypto.getSignature();
random = crypto.getSecureRandom();
messageDigest = crypto.getMessageDigest();
this.writerFactory = writerFactory;
this.clock = clock;
}
public Message createPrivateMessage(MessageId parent, String contentType,
byte[] body) throws IOException, GeneralSecurityException {
return createMessage(parent, null, null, null, contentType, body);
long timestamp, byte[] body) throws IOException,
GeneralSecurityException {
return createMessage(parent, null, null, null, contentType, timestamp,
body);
}
public Message createAnonymousMessage(MessageId parent, Group group,
String contentType, byte[] body) throws IOException,
String contentType, long timestamp, byte[] body) throws IOException,
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,
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,
body);
timestamp, body);
}
private Message createMessage(MessageId parent, Group group, Author author,
PrivateKey privateKey, String contentType, byte[] body)
throws IOException, GeneralSecurityException {
PrivateKey privateKey, String contentType, long timestamp,
byte[] body) throws IOException, GeneralSecurityException {
// Validate the arguments
if((author == null) != (privateKey == null))
throw new IllegalArgumentException();
@@ -102,7 +102,6 @@ class MessageFactoryImpl implements MessageFactory {
if(author == null) w.writeNull();
else writeAuthor(w, author);
w.writeString(contentType);
long timestamp = clock.currentTimeMillis();
w.writeIntAny(timestamp);
byte[] salt = new byte[MESSAGE_SALT_LENGTH];
random.nextBytes(salt);

View File

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

View File

@@ -131,9 +131,10 @@ public class ConstantsTest extends BriarTestCase {
PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
String contentType =
TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH);
long timestamp = Long.MAX_VALUE;
byte[] body = new byte[MAX_BODY_LENGTH];
Message message = messageFactory.createPseudonymousMessage(parent,
group, author, privateKey, contentType, body);
group, author, privateKey, contentType, timestamp, body);
// Check the size of the serialised message
int length = message.getSerialised().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());
// Send Bob a message
String contentType = "text/plain";
long timestamp = System.currentTimeMillis();
byte[] body = "Hi Bob!".getBytes("UTF-8");
MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
Message message = messageFactory.createPrivateMessage(null, contentType,
body);
timestamp, body);
db.addLocalPrivateMessage(message, contactId);
// Create an outgoing simplex connection
ByteArrayOutputStream out = new ByteArrayOutputStream();