Ensure responses shown after requests, clarify wording, reuse transactions

When devices' clocks are out of sync, it is possible that a response is
shown before the request. This commit makes sure that the timestamp of
responses is always later than the last message in the conversation.

Some wording could be misunderstood to thing introductions were
successful even though they were not. That has been clarified.

A new database transaction was created when getting contacts and local
transport properties. This has been changed to re-use the existing
transaction.

Also addresses minor issues found in review.
This commit is contained in:
Torsten Grote
2016-04-04 12:50:43 -03:00
parent 4b7a32a5ee
commit 90d984ee52
17 changed files with 83 additions and 52 deletions

View File

@@ -80,6 +80,6 @@
</RelativeLayout>
<View style="@style/Divider.ContactListDevider"/>
<View style="@style/Divider.ContactList"/>
</LinearLayout>

View File

@@ -34,8 +34,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignEnd="@+id/acceptButton"
android:layout_alignRight="@+id/acceptButton"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/acceptButton"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"

View File

@@ -23,7 +23,6 @@
android:id="@+id/introductionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="175dp"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"

View File

@@ -14,7 +14,6 @@
android:id="@+id/noticeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="80dp"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"

View File

@@ -6,7 +6,7 @@
<item
android:id="@+id/action_introduction"
android:icon="@drawable/introduction_white"
android:title="@string/make_introduction"
android:title="@string/introduction_button"
app:showAsAction="never"/>
<item

View File

@@ -141,8 +141,9 @@
<string name="transport_lan">Wi-Fi</string>
<string name="no_data">No data</string>
<string name="unknown_app">an unknown app</string>
<string name="make_introduction">Make Introduction</string>
<string name="introduction_activity_title">Select contact</string>
<!-- Introduction Client -->
<string name="introduction_activity_title">Select Contact</string>
<string name="introduction_message_title">Introduce Contacts</string>
<string name="introduction_message_text">You can compose a message that will be sent to %1$s and %2$s along with your introduction:</string>
<string name="introduction_message_hint">Type message (optional)</string>
@@ -151,10 +152,10 @@
<string name="introduction_response_error">Error when responding to introduction</string>
<string name="introduction_warn_different_identities_title">Warning: Different Identities</string>
<string name="introduction_warn_different_identities_text">You are trying to introduce two contacts that you have added with different identities. This might reveal that both identities are yours.</string>
<string name="introduction_request_sent">You have introduced %1$s to %2$s.</string>
<string name="introduction_request_sent">You have asked to introduce %1$s to %2$s.</string>
<string name="introduction_request_received">%1$s introduced you to %2$s. Do you want to add %2$s to your contact list?</string>
<string name="introduction_request_exists_received">%1$s introduced you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string>
<string name="introduction_request_answered_received">%1$s introduced you to %2$s.</string>
<string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string>
<string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string>
<string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string>
<string name="introduction_response_accepted_received">%1$s accepted to be introduced to %2$s.</string>

View File

@@ -99,7 +99,7 @@
<item name="android:layout_height">1px</item>
</style>
<style name="Divider.ContactListDevider" parent="Divider">
<style name="Divider.ContactList" parent="Divider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">2dp</item>
<item name="android:layout_marginLeft">@dimen/margin_large</item>

View File

@@ -661,11 +661,13 @@ public class ConversationActivity extends BriarActivity
runOnDbThread(new Runnable() {
@Override
public void run() {
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try {
if (accept) {
introductionManager.acceptIntroduction(contactId, sessionId);
introductionManager.acceptIntroduction(contactId, sessionId, timestamp);
} else {
introductionManager.declineIntroduction(contactId, sessionId);
introductionManager.declineIntroduction(contactId, sessionId, timestamp);
}
loadMessages();
} catch (DbException e) {

View File

@@ -180,7 +180,8 @@ public class IntroductionMessageFragment extends BaseFragment {
// actually make the introduction
try {
introductionManager.makeIntroduction(c1, c2, msg);
long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, msg, timestamp);
introductionWasMade = true;
postIntroduction(false);
} catch (DbException e) {

View File

@@ -21,20 +21,23 @@ public interface IntroductionManager {
/**
* sends two initial introduction messages
*/
void makeIntroduction(Contact c1, Contact c2, String msg)
void makeIntroduction(Contact c1, Contact c2, String msg,
final long timestamp)
throws DbException, FormatException;
/**
* Accept an introduction that had been made
*/
void acceptIntroduction(final ContactId contactId,
final SessionId sessionId) throws DbException, FormatException;
final SessionId sessionId, final long timestamp)
throws DbException, FormatException;
/**
* Decline an introduction that had been made
*/
void declineIntroduction(final ContactId contactId,
final SessionId sessionId) throws DbException, FormatException;
final SessionId sessionId, final long timestamp)
throws DbException, FormatException;
/**
* Get all introduction messages for the contact with this contactId

View File

@@ -20,6 +20,14 @@ public interface TransportPropertyManager {
Map<TransportId, TransportProperties> getLocalProperties()
throws DbException;
/**
* Returns the local transport properties for all transports.
* <br/>
* Read-Only
* */
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
throws DbException;
/** Returns the local transport properties for the given transport. */
TransportProperties getLocalProperties(TransportId t) throws DbException;

View File

@@ -109,6 +109,7 @@ public class IntroduceeEngine
msg.put(E_PUBLIC_KEY, localState.getRaw(OUR_PUBLIC_KEY));
msg.put(TRANSPORT, localAction.getDictionary(TRANSPORT));
}
msg.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
messages.add(msg);
logAction(currentState, localState, msg);

View File

@@ -52,6 +52,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.api.introduction.IntroductionConstants.INTRODUCER;
import static org.briarproject.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY;
@@ -159,9 +160,10 @@ class IntroduceeManager {
}
public void acceptIntroduction(Transaction txn, final ContactId contactId,
final SessionId sessionId) throws DbException, FormatException {
final SessionId sessionId, final long timestamp)
throws DbException, FormatException {
Contact c = contactManager.getContact(contactId);
Contact c = db.getContact(txn, contactId);
Group g = introductionManager.getIntroductionGroup(c);
BdfDictionary state = introductionManager
@@ -173,7 +175,7 @@ class IntroduceeManager {
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
Map<TransportId, TransportProperties> transportProperties =
transportPropertyManager.getLocalProperties();
transportPropertyManager.getLocalProperties(txn);
// update session state for later
state.put(ACCEPT, true);
@@ -186,6 +188,7 @@ class IntroduceeManager {
localAction.put(TYPE, TYPE_RESPONSE);
localAction.put(TRANSPORT,
encodeTransportProperties(transportProperties));
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
IntroduceeEngine engine = new IntroduceeEngine();
@@ -193,9 +196,10 @@ class IntroduceeManager {
}
public void declineIntroduction(Transaction txn, final ContactId contactId,
final SessionId sessionId) throws DbException, FormatException {
final SessionId sessionId, final long timestamp)
throws DbException, FormatException {
Contact c = contactManager.getContact(contactId);
Contact c = db.getContact(txn, contactId);
Group g = introductionManager.getIntroductionGroup(c);
BdfDictionary state = introductionManager
@@ -207,6 +211,7 @@ class IntroduceeManager {
// define action
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_RESPONSE);
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
IntroduceeEngine engine = new IntroduceeEngine();

View File

@@ -56,6 +56,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_1
import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_2;
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
@@ -104,6 +105,7 @@ public class IntroducerEngine
if (localAction.containsKey(MSG)) {
msg1.put(MSG, localAction.getString(MSG));
}
msg1.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
messages.add(msg1);
logLocalAction(currentState, localState, msg1);
BdfDictionary msg2 = new BdfDictionary();
@@ -115,6 +117,7 @@ public class IntroducerEngine
if (localAction.containsKey(MSG)) {
msg2.put(MSG, localAction.getString(MSG));
}
msg2.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
messages.add(msg2);
logLocalAction(currentState, localState, msg2);

View File

@@ -30,6 +30,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID
import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_2;
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY1;
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY2;
@@ -38,6 +39,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRO
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
@@ -99,7 +101,7 @@ class IntroducerManager {
}
public void makeIntroduction(Transaction txn, Contact c1, Contact c2,
String msg) throws DbException, FormatException {
String msg, long timestamp) throws DbException, FormatException {
// TODO check for existing session with those contacts?
// deny new introduction under which conditions?
@@ -115,6 +117,7 @@ class IntroducerManager {
}
localAction.put(PUBLIC_KEY1, c1.getAuthor().getPublicKey());
localAction.put(PUBLIC_KEY2, c2.getAuthor().getPublicKey());
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
IntroducerEngine engine = new IntroducerEngine();

View File

@@ -44,7 +44,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
@@ -268,12 +267,13 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
}
@Override
public void makeIntroduction(Contact c1, Contact c2, String msg)
public void makeIntroduction(Contact c1, Contact c2, String msg,
final long timestamp)
throws DbException, FormatException {
Transaction txn = db.startTransaction(false);
try {
introducerManager.makeIntroduction(txn, c1, c2, msg);
introducerManager.makeIntroduction(txn, c1, c2, msg, timestamp);
txn.setComplete();
} finally {
db.endTransaction(txn);
@@ -282,11 +282,13 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
@Override
public void acceptIntroduction(final ContactId contactId,
final SessionId sessionId) throws DbException, FormatException {
final SessionId sessionId, final long timestamp)
throws DbException, FormatException {
Transaction txn = db.startTransaction(false);
try {
introduceeManager.acceptIntroduction(txn, contactId, sessionId);
introduceeManager
.acceptIntroduction(txn, contactId, sessionId, timestamp);
txn.setComplete();
} finally {
db.endTransaction(txn);
@@ -295,11 +297,13 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
@Override
public void declineIntroduction(final ContactId contactId,
final SessionId sessionId) throws DbException, FormatException {
final SessionId sessionId, final long timestamp)
throws DbException, FormatException {
Transaction txn = db.startTransaction(false);
try {
introduceeManager.declineIntroduction(txn, contactId, sessionId);
introduceeManager
.declineIntroduction(txn, contactId, sessionId, timestamp);
txn.setComplete();
} finally {
db.endTransaction(txn);
@@ -501,9 +505,10 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
byte[] body = clientHelper.toByteArray(bdfList);
GroupId groupId = new GroupId(message.getRaw(GROUP_ID));
Group group = db.getGroup(txn, groupId);
long timestamp = System.currentTimeMillis();
long timestamp =
message.getLong(MESSAGE_TIME, System.currentTimeMillis());
message.put(MESSAGE_TIME, timestamp);
Metadata metadata = metadataEncoder.encode(message);
messageQueueManager

View File

@@ -108,6 +108,27 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return Collections.unmodifiableMap(local);
}
@Override
public Map<TransportId, TransportProperties> getLocalProperties(
Transaction txn) throws DbException {
try {
Map<TransportId, TransportProperties> local =
new HashMap<TransportId, TransportProperties>();
// Find the latest local update for each transport
Map<TransportId, LatestUpdate> latest = findLatest(txn,
localGroup.getId(), true);
// Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
BdfList message = clientHelper.getMessageAsList(txn,
e.getValue().messageId);
local.put(e.getKey(), parseProperties(message));
}
return local;
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public TransportProperties getLocalProperties(TransportId t)
throws DbException {
@@ -212,26 +233,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
}
private Map<TransportId, TransportProperties> getLocalProperties(
Transaction txn) throws DbException {
try {
Map<TransportId, TransportProperties> local =
new HashMap<TransportId, TransportProperties>();
// Find the latest local update for each transport
Map<TransportId, LatestUpdate> latest = findLatest(txn,
localGroup.getId(), true);
// Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
BdfList message = clientHelper.getMessageAsList(txn,
e.getValue().messageId);
local.put(e.getKey(), parseProperties(message));
}
return local;
} catch (FormatException e) {
throw new DbException(e);
}
}
private void storeMessage(Transaction txn, GroupId g, TransportId t,
TransportProperties p, long version, boolean local, boolean shared)
throws DbException {