mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Replace old introduction client with new one
This commit is contained in:
@@ -11,7 +11,6 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
@@ -35,7 +34,7 @@ import static android.app.Activity.RESULT_OK;
|
||||
import static android.view.View.GONE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||
|
||||
public class IntroductionMessageFragment extends BaseFragment
|
||||
implements TextInputListener {
|
||||
@@ -175,7 +174,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
ui.message.setSendButtonEnabled(false);
|
||||
|
||||
String msg = ui.message.getText().toString();
|
||||
msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH);
|
||||
makeIntroduction(contact1, contact2, msg);
|
||||
|
||||
// don't wait for the introduction to be made before finishing activity
|
||||
@@ -190,7 +189,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
try {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
introductionManager.makeIntroduction(c1, c2, msg, timestamp);
|
||||
} catch (DbException | FormatException e) {
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
introductionError();
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
|
||||
@NotNullByDefault
|
||||
public enum IntroduceeAction {
|
||||
|
||||
LOCAL_ACCEPT,
|
||||
LOCAL_DECLINE,
|
||||
LOCAL_ABORT,
|
||||
REMOTE_REQUEST,
|
||||
REMOTE_ACCEPT,
|
||||
REMOTE_DECLINE,
|
||||
REMOTE_ABORT,
|
||||
ACK;
|
||||
|
||||
@Nullable
|
||||
public static IntroduceeAction getRemote(int type, boolean accept) {
|
||||
if (type == TYPE_REQUEST) return REMOTE_REQUEST;
|
||||
if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT;
|
||||
if (type == TYPE_RESPONSE) return REMOTE_DECLINE;
|
||||
if (type == TYPE_ACK) return ACK;
|
||||
if (type == TYPE_ABORT) return REMOTE_ABORT;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IntroduceeAction getRemote(int type) {
|
||||
return getRemote(type, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IntroduceeAction getLocal(int type, boolean accept) {
|
||||
if (type == TYPE_RESPONSE && accept) return LOCAL_ACCEPT;
|
||||
if (type == TYPE_RESPONSE) return LOCAL_DECLINE;
|
||||
if (type == TYPE_ACK) return ACK;
|
||||
if (type == TYPE_ABORT) return LOCAL_ABORT;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IntroduceeAction getLocal(int type) {
|
||||
return getLocal(type, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_DECLINE;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_DECLINE;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_REQUEST;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public enum IntroduceeProtocolState {
|
||||
|
||||
ERROR(0),
|
||||
AWAIT_REQUEST(1) {
|
||||
@Override
|
||||
public IntroduceeProtocolState next(IntroduceeAction a) {
|
||||
if (a == REMOTE_REQUEST) return AWAIT_RESPONSES;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_RESPONSES(2) {
|
||||
@Override
|
||||
public IntroduceeProtocolState next(IntroduceeAction a) {
|
||||
if (a == REMOTE_ACCEPT) return AWAIT_LOCAL_RESPONSE;
|
||||
if (a == REMOTE_DECLINE) return FINISHED;
|
||||
if (a == LOCAL_ACCEPT) return AWAIT_REMOTE_RESPONSE;
|
||||
if (a == LOCAL_DECLINE) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_REMOTE_RESPONSE(3) {
|
||||
@Override
|
||||
public IntroduceeProtocolState next(IntroduceeAction a) {
|
||||
if (a == REMOTE_ACCEPT) return AWAIT_ACK;
|
||||
if (a == REMOTE_DECLINE) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_LOCAL_RESPONSE(4) {
|
||||
@Override
|
||||
public IntroduceeProtocolState next(IntroduceeAction a) {
|
||||
if (a == LOCAL_ACCEPT) return AWAIT_ACK;
|
||||
if (a == LOCAL_DECLINE) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_ACK(5) {
|
||||
@Override
|
||||
public IntroduceeProtocolState next(IntroduceeAction a) {
|
||||
if (a == ACK) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(6);
|
||||
|
||||
private final int value;
|
||||
|
||||
IntroduceeProtocolState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static IntroduceeProtocolState fromValue(int value) {
|
||||
for (IntroduceeProtocolState s : values()) {
|
||||
if (s.value == value) return s;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public IntroduceeProtocolState next(IntroduceeAction a) {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
|
||||
@NotNullByDefault
|
||||
public enum IntroducerAction {
|
||||
|
||||
LOCAL_REQUEST,
|
||||
LOCAL_ABORT,
|
||||
REMOTE_ACCEPT_1,
|
||||
REMOTE_ACCEPT_2,
|
||||
REMOTE_DECLINE_1,
|
||||
REMOTE_DECLINE_2,
|
||||
REMOTE_ABORT,
|
||||
ACK_1,
|
||||
ACK_2;
|
||||
|
||||
@Nullable
|
||||
public static IntroducerAction getLocal(int type) {
|
||||
if (type == TYPE_REQUEST) return LOCAL_REQUEST;
|
||||
if (type == TYPE_ABORT) return LOCAL_ABORT;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IntroducerAction getRemote(int type, boolean one,
|
||||
boolean accept) {
|
||||
|
||||
if (one) {
|
||||
if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_1;
|
||||
if (type == TYPE_RESPONSE) return REMOTE_DECLINE_1;
|
||||
if (type == TYPE_ACK) return ACK_1;
|
||||
} else {
|
||||
if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_2;
|
||||
if (type == TYPE_RESPONSE) return REMOTE_DECLINE_2;
|
||||
if (type == TYPE_ACK) return ACK_2;
|
||||
}
|
||||
if (type == TYPE_ABORT) return REMOTE_ABORT;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IntroducerAction getRemote(int type, boolean one) {
|
||||
return getRemote(type, one, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.ACK_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.ACK_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_2;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public enum IntroducerProtocolState {
|
||||
|
||||
ERROR(0),
|
||||
PREPARE_REQUESTS(1) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == LOCAL_REQUEST) return AWAIT_RESPONSES;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_RESPONSES(2) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == REMOTE_ACCEPT_1) return AWAIT_RESPONSE_2;
|
||||
if (a == REMOTE_ACCEPT_2) return AWAIT_RESPONSE_1;
|
||||
if (a == REMOTE_DECLINE_1) return FINISHED;
|
||||
if (a == REMOTE_DECLINE_2) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_RESPONSE_1(3) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == REMOTE_ACCEPT_1) return AWAIT_ACKS;
|
||||
if (a == REMOTE_DECLINE_1) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_RESPONSE_2(4) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == REMOTE_ACCEPT_2) return AWAIT_ACKS;
|
||||
if (a == REMOTE_DECLINE_2) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_ACKS(5) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == ACK_1) return AWAIT_ACK_2;
|
||||
if (a == ACK_2) return AWAIT_ACK_1;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_ACK_1(6) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == ACK_1) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_ACK_2(7) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == ACK_2) return FINISHED;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(8) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == REMOTE_ABORT) return ERROR;
|
||||
return FINISHED;
|
||||
}
|
||||
};
|
||||
|
||||
private final int value;
|
||||
|
||||
IntroducerProtocolState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static IntroducerProtocolState fromValue(int value) {
|
||||
for (IntroducerProtocolState s : values()) {
|
||||
if (s.value == value) return s;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static boolean isOngoing(IntroducerProtocolState state) {
|
||||
return state != FINISHED && state != ERROR;
|
||||
}
|
||||
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -4,126 +4,26 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_L
|
||||
|
||||
public interface IntroductionConstants {
|
||||
|
||||
/* Protocol roles */
|
||||
int ROLE_INTRODUCER = 0;
|
||||
int ROLE_INTRODUCEE = 1;
|
||||
|
||||
/* Message types */
|
||||
int TYPE_REQUEST = 1;
|
||||
int TYPE_RESPONSE = 2;
|
||||
int TYPE_ACK = 3;
|
||||
int TYPE_ABORT = 4;
|
||||
|
||||
/* Message Constants */
|
||||
String TYPE = "type";
|
||||
String GROUP_ID = "groupId";
|
||||
String SESSION_ID = "sessionId";
|
||||
String CONTACT = "contactId";
|
||||
String NAME = "name";
|
||||
String PUBLIC_KEY = "publicKey";
|
||||
String E_PUBLIC_KEY = "ephemeralPublicKey";
|
||||
String MSG = "msg";
|
||||
String ACCEPT = "accept";
|
||||
String TIME = "time";
|
||||
String TRANSPORT = "transport";
|
||||
String MESSAGE_ID = "messageId";
|
||||
String MESSAGE_TIME = "timestamp";
|
||||
String MAC = "mac";
|
||||
String SIGNATURE = "signature";
|
||||
|
||||
/* Validation Constants */
|
||||
|
||||
/**
|
||||
* The length of the message authentication code in bytes.
|
||||
*/
|
||||
int MAC_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* The maximum length of the introducer's optional message to the
|
||||
* introducees in UTF-8 bytes.
|
||||
*/
|
||||
int MAX_INTRODUCTION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||
int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||
|
||||
/* Introducer Local State Metadata */
|
||||
String STATE = "state";
|
||||
String ROLE = "role";
|
||||
String GROUP_ID_1 = "groupId1";
|
||||
String GROUP_ID_2 = "groupId2";
|
||||
String CONTACT_1 = "contact1";
|
||||
String CONTACT_2 = "contact2";
|
||||
String AUTHOR_ID_1 = "authorId1";
|
||||
String AUTHOR_ID_2 = "authorId2";
|
||||
String CONTACT_ID_1 = "contactId1";
|
||||
String CONTACT_ID_2 = "contactId2";
|
||||
String RESPONSE_1 = "response1";
|
||||
String RESPONSE_2 = "response2";
|
||||
String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID";
|
||||
|
||||
/* Introduction Request Action */
|
||||
String PUBLIC_KEY1 = "publicKey1";
|
||||
String PUBLIC_KEY2 = "publicKey2";
|
||||
String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY";
|
||||
|
||||
/* Introducee Local State Metadata (without those already defined) */
|
||||
String STORAGE_ID = "storageId";
|
||||
String INTRODUCER = "introducer";
|
||||
String LOCAL_AUTHOR_ID = "localAuthorId";
|
||||
String REMOTE_AUTHOR_ID = "remoteAuthorId";
|
||||
String OUR_PUBLIC_KEY = "ourEphemeralPublicKey";
|
||||
String OUR_PRIVATE_KEY = "ourEphemeralPrivateKey";
|
||||
String OUR_TIME = "ourTime";
|
||||
String ADDED_CONTACT_ID = "addedContactId";
|
||||
String NOT_OUR_RESPONSE = "notOurResponse";
|
||||
String EXISTS = "contactExists";
|
||||
String REMOTE_AUTHOR_IS_US = "remoteAuthorIsUs";
|
||||
String ANSWERED = "answered";
|
||||
String NONCE = "nonce";
|
||||
String MAC_KEY = "macKey";
|
||||
String OUR_TRANSPORT = "ourTransport";
|
||||
String OUR_MAC = "ourMac";
|
||||
String OUR_SIGNATURE = "ourSignature";
|
||||
|
||||
String TASK = "task";
|
||||
int TASK_ADD_CONTACT = 0;
|
||||
int TASK_ACTIVATE_CONTACT = 1;
|
||||
int TASK_ABORT = 2;
|
||||
|
||||
/**
|
||||
* Label for deriving the shared secret.
|
||||
*/
|
||||
String SHARED_SECRET_LABEL =
|
||||
"org.briarproject.briar.introduction/SHARED_SECRET";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's key binding nonce from the shared secret.
|
||||
*/
|
||||
String ALICE_NONCE_LABEL =
|
||||
"org.briarproject.briar.introduction/ALICE_NONCE";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's key binding nonce from the shared secret.
|
||||
*/
|
||||
String BOB_NONCE_LABEL =
|
||||
"org.briarproject.briar.introduction/BOB_NONCE";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's MAC key from the shared secret.
|
||||
*/
|
||||
String ALICE_MAC_KEY_LABEL =
|
||||
String LABEL_ALICE_MAC_KEY =
|
||||
"org.briarproject.briar.introduction/ALICE_MAC_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's MAC key from the shared secret.
|
||||
*/
|
||||
String BOB_MAC_KEY_LABEL =
|
||||
String LABEL_BOB_MAC_KEY =
|
||||
"org.briarproject.briar.introduction/BOB_MAC_KEY";
|
||||
|
||||
/**
|
||||
* Label for signing the introduction response.
|
||||
*/
|
||||
String SIGNING_LABEL =
|
||||
"org.briarproject.briar.introduction/RESPONSE_SIGNATURE";
|
||||
String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC";
|
||||
|
||||
String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN";
|
||||
|
||||
String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE";
|
||||
|
||||
/**
|
||||
* Label for MACing the introduction response.
|
||||
*/
|
||||
String MAC_LABEL = "org.briarproject.briar.introduction/RESPONSE_MAC";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -24,25 +23,25 @@ public interface IntroductionManager extends ConversationClient {
|
||||
/**
|
||||
* The current version of the introduction client.
|
||||
*/
|
||||
int CLIENT_VERSION = 0;
|
||||
int CLIENT_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Sends two initial introduction messages.
|
||||
*/
|
||||
void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
|
||||
long timestamp) throws DbException, FormatException;
|
||||
long timestamp) throws DbException;
|
||||
|
||||
/**
|
||||
* Accepts an introduction.
|
||||
*/
|
||||
void acceptIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException, FormatException;
|
||||
long timestamp) throws DbException;
|
||||
|
||||
/**
|
||||
* Declines an introduction.
|
||||
*/
|
||||
void declineIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException, FormatException;
|
||||
long timestamp) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all introduction messages for the given contact.
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.briarproject.briar.api.client.SessionId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -16,10 +16,10 @@ public class IntroductionMessage extends BaseMessageHeader {
|
||||
|
||||
private final SessionId sessionId;
|
||||
private final MessageId messageId;
|
||||
private final int role;
|
||||
private final Role role;
|
||||
|
||||
IntroductionMessage(SessionId sessionId, MessageId messageId,
|
||||
GroupId groupId, int role, long time, boolean local, boolean sent,
|
||||
GroupId groupId, Role role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read) {
|
||||
|
||||
super(messageId, groupId, time, local, sent, seen, read);
|
||||
@@ -37,7 +37,7 @@ public class IntroductionMessage extends BaseMessageHeader {
|
||||
}
|
||||
|
||||
public boolean isIntroducer() {
|
||||
return role == ROLE_INTRODUCER;
|
||||
return role == INTRODUCER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
@@ -15,21 +14,19 @@ public class IntroductionRequest extends IntroductionResponse {
|
||||
|
||||
@Nullable
|
||||
private final String message;
|
||||
private final boolean answered, exists, introducesOtherIdentity;
|
||||
private final boolean answered, exists;
|
||||
|
||||
public IntroductionRequest(SessionId sessionId, MessageId messageId,
|
||||
GroupId groupId, int role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read, AuthorId authorId, String name,
|
||||
boolean accepted, @Nullable String message, boolean answered,
|
||||
boolean exists, boolean introducesOtherIdentity) {
|
||||
GroupId groupId, Role role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read, String name, boolean accepted,
|
||||
@Nullable String message, boolean answered, boolean exists) {
|
||||
|
||||
super(sessionId, messageId, groupId, role, time, local, sent, seen,
|
||||
read, authorId, name, accepted);
|
||||
read, name, accepted);
|
||||
|
||||
this.message = message;
|
||||
this.answered = answered;
|
||||
this.exists = exists;
|
||||
this.introducesOtherIdentity = introducesOtherIdentity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -44,8 +41,4 @@ public class IntroductionRequest extends IntroductionResponse {
|
||||
public boolean contactExists() {
|
||||
return exists;
|
||||
}
|
||||
|
||||
public boolean doesIntroduceOtherIdentity() {
|
||||
return introducesOtherIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
@@ -12,19 +11,15 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class IntroductionResponse extends IntroductionMessage {
|
||||
|
||||
private final AuthorId remoteAuthorId;
|
||||
private final String name;
|
||||
private final boolean accepted;
|
||||
|
||||
public IntroductionResponse(SessionId sessionId, MessageId messageId,
|
||||
GroupId groupId, int role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read, AuthorId remoteAuthorId, String name,
|
||||
boolean accepted) {
|
||||
|
||||
GroupId groupId, Role role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read, String name, boolean accepted) {
|
||||
super(sessionId, messageId, groupId, role, time, local, sent, seen,
|
||||
read);
|
||||
|
||||
this.remoteAuthorId = remoteAuthorId;
|
||||
this.name = name;
|
||||
this.accepted = accepted;
|
||||
}
|
||||
@@ -37,7 +32,4 @@ public class IntroductionResponse extends IntroductionMessage {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
public AuthorId getRemoteAuthorId() {
|
||||
return remoteAuthorId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.api.introduction2;
|
||||
package org.briarproject.briar.api.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.api.introduction.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.briar.api.client.SessionId;
|
||||
@@ -11,19 +10,14 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class IntroductionAbortedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final SessionId sessionId;
|
||||
|
||||
public IntroductionAbortedEvent(ContactId contactId, SessionId sessionId) {
|
||||
this.contactId = contactId;
|
||||
public IntroductionAbortedEvent(SessionId sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
// TODO still needed?
|
||||
public class IntroductionSucceededEvent extends Event {
|
||||
|
||||
private final Contact contact;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
|
||||
public interface IntroductionConstants {
|
||||
|
||||
/**
|
||||
* The maximum length of the introducer's optional message to the
|
||||
* introducees in UTF-8 bytes.
|
||||
*/
|
||||
int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||
|
||||
String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID";
|
||||
|
||||
String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY";
|
||||
|
||||
String LABEL_ALICE_MAC_KEY =
|
||||
"org.briarproject.briar.introduction/ALICE_MAC_KEY";
|
||||
|
||||
String LABEL_BOB_MAC_KEY =
|
||||
"org.briarproject.briar.introduction/BOB_MAC_KEY";
|
||||
|
||||
String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC";
|
||||
|
||||
String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN";
|
||||
|
||||
String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE";
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.messaging.ConversationManager.ConversationClient;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface IntroductionManager extends ConversationClient {
|
||||
|
||||
/**
|
||||
* The unique ID of the introduction client.
|
||||
*/
|
||||
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.introduction");
|
||||
|
||||
/**
|
||||
* The current version of the introduction client.
|
||||
*/
|
||||
int CLIENT_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Sends two initial introduction messages.
|
||||
*/
|
||||
void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
|
||||
long timestamp) throws DbException;
|
||||
|
||||
/**
|
||||
* Accepts an introduction.
|
||||
*/
|
||||
void acceptIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException;
|
||||
|
||||
/**
|
||||
* Declines an introduction.
|
||||
*/
|
||||
void declineIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all introduction messages for the given contact.
|
||||
*/
|
||||
Collection<IntroductionMessage> getIntroductionMessages(ContactId contactId)
|
||||
throws DbException;
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.BaseMessageHeader;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class IntroductionMessage extends BaseMessageHeader {
|
||||
|
||||
private final SessionId sessionId;
|
||||
private final MessageId messageId;
|
||||
private final Role role;
|
||||
|
||||
IntroductionMessage(SessionId sessionId, MessageId messageId,
|
||||
GroupId groupId, Role role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read) {
|
||||
|
||||
super(messageId, groupId, time, local, sent, seen, read);
|
||||
this.sessionId = sessionId;
|
||||
this.messageId = messageId;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public boolean isIntroducer() {
|
||||
return role == INTRODUCER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class IntroductionRequest extends IntroductionResponse {
|
||||
|
||||
@Nullable
|
||||
private final String message;
|
||||
private final boolean answered, exists;
|
||||
|
||||
public IntroductionRequest(SessionId sessionId, MessageId messageId,
|
||||
GroupId groupId, Role role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read, String name, boolean accepted,
|
||||
@Nullable String message, boolean answered, boolean exists) {
|
||||
|
||||
super(sessionId, messageId, groupId, role, time, local, sent, seen,
|
||||
read, name, accepted);
|
||||
|
||||
this.message = message;
|
||||
this.answered = answered;
|
||||
this.exists = exists;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean wasAnswered() {
|
||||
return answered;
|
||||
}
|
||||
|
||||
public boolean contactExists() {
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class IntroductionResponse extends IntroductionMessage {
|
||||
|
||||
private final String name;
|
||||
private final boolean accepted;
|
||||
|
||||
public IntroductionResponse(SessionId sessionId, MessageId messageId,
|
||||
GroupId groupId, Role role, long time, boolean local, boolean sent,
|
||||
boolean seen, boolean read, String name, boolean accepted) {
|
||||
super(sessionId, messageId, groupId, role, time, local, sent, seen,
|
||||
read);
|
||||
|
||||
this.name = name;
|
||||
this.accepted = accepted;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean wasAccepted() {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
// TODO still needed?
|
||||
public class IntroductionAbortedEvent extends Event {
|
||||
|
||||
private final AuthorId remoteAuthorId;
|
||||
private final SessionId sessionId;
|
||||
|
||||
public IntroductionAbortedEvent(AuthorId remoteAuthorId,
|
||||
SessionId sessionId) {
|
||||
this.remoteAuthorId = remoteAuthorId;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public AuthorId getRemoteAuthorId() {
|
||||
return remoteAuthorId;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2.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.briar.api.introduction2.IntroductionRequest;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class IntroductionRequestReceivedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final IntroductionRequest introductionRequest;
|
||||
|
||||
public IntroductionRequestReceivedEvent(ContactId contactId,
|
||||
IntroductionRequest introductionRequest) {
|
||||
|
||||
this.contactId = contactId;
|
||||
this.introductionRequest = introductionRequest;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public IntroductionRequest getIntroductionRequest() {
|
||||
return introductionRequest;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2.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.briar.api.introduction2.IntroductionResponse;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class IntroductionResponseReceivedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final IntroductionResponse introductionResponse;
|
||||
|
||||
public IntroductionResponseReceivedEvent(ContactId contactId,
|
||||
IntroductionResponse introductionResponse) {
|
||||
|
||||
this.contactId = contactId;
|
||||
this.introductionResponse = introductionResponse;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public IntroductionResponse getIntroductionResponse() {
|
||||
return introductionResponse;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.briarproject.briar.api.introduction2.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
// TODO still needed?
|
||||
public class IntroductionSucceededEvent extends Event {
|
||||
|
||||
private final Contact contact;
|
||||
|
||||
public IntroductionSucceededEvent(Contact contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AbortMessage extends IntroductionMessage {
|
||||
class AbortMessage extends AbstractIntroductionMessage {
|
||||
|
||||
private final SessionId sessionId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
abstract class IntroductionMessage {
|
||||
abstract class AbstractIntroductionMessage {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final GroupId groupId;
|
||||
@@ -17,7 +17,7 @@ abstract class IntroductionMessage {
|
||||
@Nullable
|
||||
private final MessageId previousMessageId;
|
||||
|
||||
IntroductionMessage(MessageId messageId, GroupId groupId,
|
||||
AbstractIntroductionMessage(MessageId messageId, GroupId groupId,
|
||||
long timestamp, @Nullable MessageId previousMessageId) {
|
||||
this.messageId = messageId;
|
||||
this.groupId = groupId;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -25,14 +25,14 @@ import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
@@ -14,7 +14,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AcceptMessage extends IntroductionMessage {
|
||||
class AcceptMessage extends AbstractIntroductionMessage {
|
||||
|
||||
private final SessionId sessionId;
|
||||
private final byte[] ephemeralPublicKey;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class ActivateMessage extends IntroductionMessage {
|
||||
class ActivateMessage extends AbstractIntroductionMessage {
|
||||
|
||||
private final SessionId sessionId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AuthMessage extends IntroductionMessage {
|
||||
class AuthMessage extends AbstractIntroductionMessage {
|
||||
|
||||
private final SessionId sessionId;
|
||||
private final byte[] mac, signature;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class DeclineMessage extends IntroductionMessage {
|
||||
class DeclineMessage extends AbstractIntroductionMessage {
|
||||
|
||||
private final SessionId sessionId;
|
||||
|
||||
@@ -1,372 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.ProtocolEngine;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction.IntroduceeAction;
|
||||
import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
|
||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_DECLINE;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REMOTE_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.ERROR;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.FINISHED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ADD_CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroduceeEngine
|
||||
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroduceeEngine.class.getName());
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
|
||||
BdfDictionary localState, BdfDictionary localAction) {
|
||||
|
||||
try {
|
||||
IntroduceeProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
int type = localAction.getLong(TYPE).intValue();
|
||||
IntroduceeAction action;
|
||||
if (localState.containsKey(ACCEPT)) action = IntroduceeAction
|
||||
.getLocal(type, localState.getBoolean(ACCEPT));
|
||||
else action = IntroduceeAction.getLocal(type);
|
||||
IntroduceeProtocolState nextState = currentState.next(action);
|
||||
|
||||
if (action == LOCAL_ABORT && currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error: Invalid action in state " +
|
||||
currentState.name());
|
||||
}
|
||||
if (currentState == ERROR) return noUpdate(localState);
|
||||
else return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
List<BdfDictionary> messages = new ArrayList<>(1);
|
||||
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
|
||||
localState.put(STATE, nextState.getValue());
|
||||
localState.put(ANSWERED, true);
|
||||
// create the introduction response message
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg.put(ACCEPT, localState.getBoolean(ACCEPT));
|
||||
if (localState.getBoolean(ACCEPT)) {
|
||||
msg.put(TIME, localState.getLong(OUR_TIME));
|
||||
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);
|
||||
|
||||
if (nextState == AWAIT_ACK) {
|
||||
localState.put(TASK, TASK_ADD_CONTACT);
|
||||
}
|
||||
} else if (action == ACK) {
|
||||
// just send ACK, don't update local state again
|
||||
BdfDictionary ack = getAckMessage(localState);
|
||||
messages.add(ack);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
List<Event> events = Collections.emptyList();
|
||||
return new StateUpdate<>(false, false,
|
||||
localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
try {
|
||||
IntroduceeProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
int type = msg.getLong(TYPE).intValue();
|
||||
IntroduceeAction action = IntroduceeAction.getRemote(type);
|
||||
IntroduceeProtocolState nextState = currentState.next(action);
|
||||
|
||||
logMessageReceived(currentState, nextState, localState, type, msg);
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (currentState != ERROR && action != REMOTE_ABORT) {
|
||||
return abortSession(currentState, localState);
|
||||
} else {
|
||||
return noUpdate(localState);
|
||||
}
|
||||
}
|
||||
|
||||
// update local session state with next protocol state
|
||||
localState.put(STATE, nextState.getValue());
|
||||
List<BdfDictionary> messages;
|
||||
List<Event> events;
|
||||
// we received the introduction request
|
||||
if (currentState == AWAIT_REQUEST) {
|
||||
// remember the session ID used by the introducer
|
||||
localState.put(SESSION_ID, msg.getRaw(SESSION_ID));
|
||||
|
||||
addRequestData(localState, msg);
|
||||
messages = Collections.emptyList();
|
||||
events = Collections.singletonList(getEvent(localState, msg));
|
||||
}
|
||||
// we had the request and now one response came in _OR_
|
||||
// we had sent our response already and now received the other one
|
||||
else if (currentState == AWAIT_RESPONSES ||
|
||||
currentState == AWAIT_REMOTE_RESPONSE) {
|
||||
// update next state based on message content
|
||||
action = IntroduceeAction
|
||||
.getRemote(type, msg.getBoolean(ACCEPT));
|
||||
nextState = currentState.next(action);
|
||||
localState.put(STATE, nextState.getValue());
|
||||
|
||||
addResponseData(localState, msg);
|
||||
if (nextState == AWAIT_ACK) {
|
||||
localState.put(TASK, TASK_ADD_CONTACT);
|
||||
}
|
||||
messages = Collections.emptyList();
|
||||
events = Collections.emptyList();
|
||||
}
|
||||
// we already sent our ACK and now received the other one
|
||||
else if (currentState == AWAIT_ACK) {
|
||||
localState.put(TASK, TASK_ACTIVATE_CONTACT);
|
||||
addAckData(localState, msg);
|
||||
messages = Collections.emptyList();
|
||||
events = Collections.emptyList();
|
||||
}
|
||||
// we are done (probably declined response), ignore & delete message
|
||||
else if (currentState == FINISHED) {
|
||||
return new StateUpdate<>(true, false, localState,
|
||||
Collections.<BdfDictionary>emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
// this should not happen
|
||||
else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return new StateUpdate<>(false, false,
|
||||
localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRequestData(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
localState.put(NAME, msg.getString(NAME));
|
||||
localState.put(PUBLIC_KEY, msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) {
|
||||
localState.put(MSG, msg.getString(MSG));
|
||||
}
|
||||
}
|
||||
|
||||
private void addResponseData(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
if (localState.containsKey(ACCEPT)) {
|
||||
localState.put(ACCEPT,
|
||||
localState.getBoolean(ACCEPT) && msg.getBoolean(ACCEPT));
|
||||
} else {
|
||||
localState.put(ACCEPT, msg.getBoolean(ACCEPT));
|
||||
}
|
||||
localState.put(NOT_OUR_RESPONSE, msg.getRaw(MESSAGE_ID));
|
||||
|
||||
if (msg.getBoolean(ACCEPT)) {
|
||||
localState.put(TIME, msg.getLong(TIME));
|
||||
localState.put(E_PUBLIC_KEY, msg.getRaw(E_PUBLIC_KEY));
|
||||
localState.put(TRANSPORT, msg.getDictionary(TRANSPORT));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAckData(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
localState.put(MAC, msg.getRaw(MAC));
|
||||
localState.put(SIGNATURE, msg.getRaw(SIGNATURE));
|
||||
}
|
||||
|
||||
private BdfDictionary getAckMessage(BdfDictionary localState)
|
||||
throws FormatException {
|
||||
|
||||
BdfDictionary m = new BdfDictionary();
|
||||
m.put(TYPE, TYPE_ACK);
|
||||
m.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
m.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
m.put(MAC, localState.getRaw(OUR_MAC));
|
||||
m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE));
|
||||
return m;
|
||||
}
|
||||
|
||||
private void logAction(IntroduceeProtocolState state,
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
try {
|
||||
LOG.info("Sending " +
|
||||
(localState.getBoolean(ACCEPT) ? "accept " : "decline ") +
|
||||
"response in state " + state.name());
|
||||
LOG.info("Moving on to state " +
|
||||
getState(localState.getLong(STATE)).name());
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logMessageReceived(IntroduceeProtocolState currentState,
|
||||
IntroduceeProtocolState nextState, BdfDictionary localState,
|
||||
int type, BdfDictionary msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String t = "unknown";
|
||||
if (type == TYPE_REQUEST) t = "Introduction";
|
||||
else if (type == TYPE_RESPONSE) t = "Response";
|
||||
else if (type == TYPE_ACK) t = "ACK";
|
||||
else if (type == TYPE_ABORT) t = "Abort";
|
||||
|
||||
LOG.info("Received " + t + " in state " + currentState.name());
|
||||
LOG.info("Moving on to state " + nextState.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
|
||||
BdfDictionary localState, BdfDictionary delivered) {
|
||||
try {
|
||||
return noUpdate(localState);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IntroduceeProtocolState getState(Long state) {
|
||||
return IntroduceeProtocolState.fromValue(state.intValue());
|
||||
}
|
||||
|
||||
private Event getEvent(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
ContactId contactId =
|
||||
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
|
||||
AuthorId authorId = new AuthorId(localState.getRaw(REMOTE_AUTHOR_ID));
|
||||
|
||||
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
|
||||
MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID));
|
||||
GroupId groupId = new GroupId(msg.getRaw(GROUP_ID));
|
||||
long time = msg.getLong(MESSAGE_TIME);
|
||||
String name = msg.getString(NAME);
|
||||
String message = msg.getOptionalString(MSG);
|
||||
boolean exists = localState.getBoolean(EXISTS);
|
||||
boolean introducesOtherIdentity =
|
||||
localState.getBoolean(REMOTE_AUTHOR_IS_US);
|
||||
|
||||
IntroductionRequest ir = new IntroductionRequest(sessionId, messageId,
|
||||
groupId, ROLE_INTRODUCEE, time, false, false, false, false,
|
||||
authorId, name, false, message, false, exists,
|
||||
introducesOtherIdentity);
|
||||
return new IntroductionRequestReceivedEvent(contactId, ir);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
|
||||
IntroduceeProtocolState currentState, BdfDictionary localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Aborting protocol session in state " +
|
||||
currentState.name());
|
||||
|
||||
localState.put(STATE, ERROR.getValue());
|
||||
localState.put(TASK, TASK_ABORT);
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_ABORT);
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
List<BdfDictionary> messages = Collections.singletonList(msg);
|
||||
|
||||
// send abort event
|
||||
ContactId contactId =
|
||||
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
|
||||
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
|
||||
Event event = new IntroductionAbortedEvent(contactId, sessionId);
|
||||
List<Event> events = Collections.singletonList(event);
|
||||
|
||||
return new StateUpdate<>(false, false, localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
|
||||
BdfDictionary localState) throws FormatException {
|
||||
|
||||
return new StateUpdate<>(false, false, localState,
|
||||
Collections.<BdfDictionary>emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -1,569 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.BOB_MAC_KEY_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.BOB_NONCE_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ADD_CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroduceeManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroduceeManager.class.getName());
|
||||
|
||||
private final MessageSender messageSender;
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final Clock clock;
|
||||
private final CryptoComponent cryptoComponent;
|
||||
private final TransportPropertyManager transportPropertyManager;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final ContactManager contactManager;
|
||||
private final IdentityManager identityManager;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
|
||||
@Inject
|
||||
IntroduceeManager(MessageSender messageSender, DatabaseComponent db,
|
||||
ClientHelper clientHelper, Clock clock,
|
||||
CryptoComponent cryptoComponent,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
AuthorFactory authorFactory, ContactManager contactManager,
|
||||
IdentityManager identityManager,
|
||||
IntroductionGroupFactory introductionGroupFactory) {
|
||||
|
||||
this.messageSender = messageSender;
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.clock = clock;
|
||||
this.cryptoComponent = cryptoComponent;
|
||||
this.transportPropertyManager = transportPropertyManager;
|
||||
this.authorFactory = authorFactory;
|
||||
this.contactManager = contactManager;
|
||||
this.identityManager = identityManager;
|
||||
this.introductionGroupFactory = introductionGroupFactory;
|
||||
}
|
||||
|
||||
public BdfDictionary initialize(Transaction txn, GroupId groupId,
|
||||
BdfDictionary message) throws DbException, FormatException {
|
||||
|
||||
// create local message to keep engine state
|
||||
long now = clock.currentTimeMillis();
|
||||
Bytes salt = new Bytes(new byte[64]);
|
||||
cryptoComponent.getSecureRandom().nextBytes(salt.getBytes());
|
||||
|
||||
Message localMsg = clientHelper.createMessage(
|
||||
introductionGroupFactory.createLocalGroup().getId(), now,
|
||||
BdfList.of(salt));
|
||||
MessageId storageId = localMsg.getId();
|
||||
|
||||
// find out who is introducing us
|
||||
BdfDictionary gd =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, groupId);
|
||||
ContactId introducerId =
|
||||
new ContactId(gd.getLong(CONTACT).intValue());
|
||||
Contact introducer = db.getContact(txn, introducerId);
|
||||
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(STORAGE_ID, storageId);
|
||||
d.put(STATE, AWAIT_REQUEST.getValue());
|
||||
d.put(ROLE, ROLE_INTRODUCEE);
|
||||
d.put(GROUP_ID, groupId);
|
||||
d.put(INTRODUCER, introducer.getAuthor().getName());
|
||||
d.put(CONTACT_ID_1, introducer.getId().getInt());
|
||||
d.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes());
|
||||
d.put(NOT_OUR_RESPONSE, storageId);
|
||||
d.put(ANSWERED, false);
|
||||
|
||||
// check if the contact we are introduced to does already exist
|
||||
// TODO: Exchange author format version
|
||||
AuthorId remoteAuthorId = authorFactory
|
||||
.createAuthor(message.getString(NAME),
|
||||
message.getRaw(PUBLIC_KEY)).getId();
|
||||
boolean exists = contactManager.contactExists(txn, remoteAuthorId,
|
||||
introducer.getLocalAuthorId());
|
||||
d.put(EXISTS, exists);
|
||||
d.put(REMOTE_AUTHOR_ID, remoteAuthorId);
|
||||
|
||||
// check if someone is trying to introduce us to ourselves
|
||||
if (remoteAuthorId.equals(introducer.getLocalAuthorId())) {
|
||||
LOG.warning("Received Introduction Request to Ourselves");
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
// check if remote author is actually one of our other identities
|
||||
boolean introducesOtherIdentity =
|
||||
db.containsLocalAuthor(txn, remoteAuthorId);
|
||||
d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity);
|
||||
|
||||
// save local state to database
|
||||
clientHelper.addLocalMessage(txn, localMsg, d, false);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public void incomingMessage(Transaction txn, BdfDictionary state,
|
||||
BdfDictionary message) throws DbException, FormatException {
|
||||
|
||||
IntroduceeEngine engine = new IntroduceeEngine();
|
||||
processStateUpdate(txn, message,
|
||||
engine.onMessageReceived(state, message));
|
||||
}
|
||||
|
||||
void acceptIntroduction(Transaction txn, BdfDictionary state,
|
||||
long timestamp) throws DbException, FormatException {
|
||||
|
||||
// get data to connect and derive a shared secret later
|
||||
long now = clock.currentTimeMillis();
|
||||
KeyPair keyPair = cryptoComponent.generateAgreementKeyPair();
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
byte[] privateKey = keyPair.getPrivate().getEncoded();
|
||||
Map<TransportId, TransportProperties> transportProperties =
|
||||
transportPropertyManager.getLocalProperties(txn);
|
||||
BdfDictionary tp = encodeTransportProperties(transportProperties);
|
||||
|
||||
// update session state for later
|
||||
state.put(ACCEPT, true);
|
||||
state.put(OUR_TIME, now);
|
||||
state.put(OUR_PUBLIC_KEY, publicKey);
|
||||
state.put(OUR_PRIVATE_KEY, privateKey);
|
||||
state.put(OUR_TRANSPORT, tp);
|
||||
|
||||
// define action
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
localAction.put(TYPE, TYPE_RESPONSE);
|
||||
localAction.put(TRANSPORT, tp);
|
||||
localAction.put(MESSAGE_TIME, timestamp);
|
||||
|
||||
// start engine and process its state update
|
||||
IntroduceeEngine engine = new IntroduceeEngine();
|
||||
processStateUpdate(txn, null, engine.onLocalAction(state, localAction));
|
||||
}
|
||||
|
||||
void declineIntroduction(Transaction txn, BdfDictionary state,
|
||||
long timestamp) throws DbException, FormatException {
|
||||
|
||||
// update session state
|
||||
state.put(ACCEPT, false);
|
||||
|
||||
// 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();
|
||||
processStateUpdate(txn, null,
|
||||
engine.onLocalAction(state, localAction));
|
||||
}
|
||||
|
||||
private void processStateUpdate(Transaction txn,
|
||||
@Nullable BdfDictionary msg,
|
||||
IntroduceeEngine.StateUpdate<BdfDictionary, BdfDictionary> result)
|
||||
throws DbException, FormatException {
|
||||
|
||||
// perform actions based on new local state
|
||||
BdfDictionary followUpAction = performTasks(txn, result.localState);
|
||||
|
||||
// save new local state
|
||||
MessageId storageId =
|
||||
new MessageId(result.localState.getRaw(STORAGE_ID));
|
||||
clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
|
||||
|
||||
// send messages
|
||||
for (BdfDictionary d : result.toSend) {
|
||||
messageSender.sendMessage(txn, d);
|
||||
}
|
||||
|
||||
// broadcast events
|
||||
for (Event event : result.toBroadcast) {
|
||||
txn.attach(event);
|
||||
}
|
||||
|
||||
// delete message
|
||||
if (result.deleteMessage && msg != null) {
|
||||
MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID));
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Deleting message with id " + messageId.hashCode());
|
||||
}
|
||||
db.deleteMessage(txn, messageId);
|
||||
db.deleteMessageMetadata(txn, messageId);
|
||||
}
|
||||
|
||||
// process follow up action at the end if available
|
||||
if (followUpAction != null) {
|
||||
IntroduceeEngine engine = new IntroduceeEngine();
|
||||
processStateUpdate(txn, null,
|
||||
engine.onLocalAction(result.localState, followUpAction));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BdfDictionary performTasks(Transaction txn,
|
||||
BdfDictionary localState) throws FormatException, DbException {
|
||||
|
||||
if (!localState.containsKey(TASK) || localState.get(TASK) == NULL_VALUE)
|
||||
return null;
|
||||
|
||||
// remember task and remove it from localState
|
||||
long task = localState.getLong(TASK);
|
||||
localState.put(TASK, NULL_VALUE);
|
||||
|
||||
if (task == TASK_ADD_CONTACT) {
|
||||
if (localState.getBoolean(EXISTS)) {
|
||||
// we have this contact already, so do not perform actions
|
||||
LOG.info("We have this contact already, do not add");
|
||||
return null;
|
||||
}
|
||||
|
||||
// figure out who takes which role by comparing public keys
|
||||
byte[] ourPublicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY);
|
||||
byte[] theirPublicKeyBytes = localState.getRaw(E_PUBLIC_KEY);
|
||||
int comp = Bytes.COMPARATOR.compare(new Bytes(ourPublicKeyBytes),
|
||||
new Bytes(theirPublicKeyBytes));
|
||||
boolean alice = comp < 0;
|
||||
|
||||
// get our local author
|
||||
LocalAuthor author = identityManager.getLocalAuthor(txn);
|
||||
|
||||
SecretKey secretKey;
|
||||
byte[] ourPrivateKeyBytes = localState.getRaw(OUR_PRIVATE_KEY);
|
||||
try {
|
||||
// derive secret master key
|
||||
secretKey = deriveSecretKey(ourPublicKeyBytes,
|
||||
ourPrivateKeyBytes, alice, theirPublicKeyBytes);
|
||||
// derive MAC keys and nonces, sign our nonce and calculate MAC
|
||||
deriveMacKeysAndNonces(localState, author, secretKey, alice);
|
||||
} catch (GeneralSecurityException e) {
|
||||
// we can not continue without the signature
|
||||
throw new DbException(e);
|
||||
}
|
||||
|
||||
LOG.info("Adding contact in inactive state");
|
||||
|
||||
// The agreed timestamp is the minimum of the peers' timestamps
|
||||
long ourTime = localState.getLong(OUR_TIME);
|
||||
long theirTime = localState.getLong(TIME);
|
||||
long timestamp = Math.min(ourTime, theirTime);
|
||||
|
||||
// Add the contact to the database as inactive
|
||||
// TODO: Exchange author format version
|
||||
Author remoteAuthor = authorFactory
|
||||
.createAuthor(localState.getString(NAME),
|
||||
localState.getRaw(PUBLIC_KEY));
|
||||
ContactId contactId = contactManager
|
||||
.addContact(txn, remoteAuthor, author.getId(), secretKey,
|
||||
timestamp, alice, false, false);
|
||||
|
||||
// Update local state with ContactId, so we know what to activate
|
||||
localState.put(ADDED_CONTACT_ID, contactId.getInt());
|
||||
|
||||
// let the transport manager know how to connect to the contact
|
||||
Map<TransportId, TransportProperties> transportProperties =
|
||||
parseTransportProperties(localState);
|
||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||
transportProperties);
|
||||
|
||||
// delete the ephemeral private key by overwriting with NULL value
|
||||
// this ensures future ephemeral keys can not be recovered when
|
||||
// this device should gets compromised
|
||||
localState.put(OUR_PRIVATE_KEY, NULL_VALUE);
|
||||
|
||||
// define next action: Send ACK
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
localAction.put(TYPE, TYPE_ACK);
|
||||
|
||||
// return follow up action to start engine
|
||||
// and process its state update again
|
||||
return localAction;
|
||||
}
|
||||
|
||||
// we sent and received an ACK, so activate contact
|
||||
if (task == TASK_ACTIVATE_CONTACT) {
|
||||
if (!localState.getBoolean(EXISTS) &&
|
||||
localState.containsKey(ADDED_CONTACT_ID)) {
|
||||
try {
|
||||
LOG.info("Verifying Signature...");
|
||||
verifySignature(localState);
|
||||
LOG.info("Verifying MAC...");
|
||||
verifyMac(localState);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
|
||||
LOG.info("Activating Contact...");
|
||||
|
||||
ContactId contactId = new ContactId(
|
||||
localState.getLong(ADDED_CONTACT_ID).intValue());
|
||||
|
||||
// activate and show contact in contact list
|
||||
contactManager.setContactActive(txn, contactId, true);
|
||||
|
||||
// broadcast event informing of successful introduction
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
Event event = new IntroductionSucceededEvent(contact);
|
||||
txn.attach(event);
|
||||
} else {
|
||||
LOG.info(
|
||||
"We must have had this contact already, not activating...");
|
||||
}
|
||||
}
|
||||
|
||||
// we need to abort the protocol, clean up what has been done
|
||||
if (task == TASK_ABORT) {
|
||||
if (localState.containsKey(ADDED_CONTACT_ID)) {
|
||||
LOG.info("Deleting added contact due to abort...");
|
||||
ContactId contactId = new ContactId(
|
||||
localState.getLong(ADDED_CONTACT_ID).intValue());
|
||||
contactManager.removeContact(txn, contactId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SecretKey deriveSecretKey(byte[] ourPublicKeyBytes,
|
||||
byte[] ourPrivateKeyBytes, boolean alice,
|
||||
byte[] theirPublicKeyBytes) throws GeneralSecurityException {
|
||||
// parse the local ephemeral key pair
|
||||
KeyParser keyParser = cryptoComponent.getAgreementKeyParser();
|
||||
PublicKey ourPublicKey;
|
||||
PrivateKey ourPrivateKey;
|
||||
try {
|
||||
ourPublicKey = keyParser.parsePublicKey(ourPublicKeyBytes);
|
||||
ourPrivateKey = keyParser.parsePrivateKey(ourPrivateKeyBytes);
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
throw new RuntimeException("Our own ephemeral key is invalid");
|
||||
}
|
||||
KeyPair ourKeyPair = new KeyPair(ourPublicKey, ourPrivateKey);
|
||||
PublicKey theirPublicKey =
|
||||
keyParser.parsePublicKey(theirPublicKeyBytes);
|
||||
|
||||
// The shared secret is derived from the local ephemeral key pair
|
||||
// and the remote ephemeral public key
|
||||
byte[][] inputs = {
|
||||
new byte[] {CLIENT_VERSION},
|
||||
alice ? ourPublicKeyBytes : theirPublicKeyBytes,
|
||||
alice ? theirPublicKeyBytes : ourPublicKeyBytes
|
||||
};
|
||||
return cryptoComponent.deriveSharedSecret(SHARED_SECRET_LABEL,
|
||||
theirPublicKey, ourKeyPair, inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives nonces, signs our nonce and calculates MAC
|
||||
* <p>
|
||||
* Derives two nonces and two MAC keys from the shared secret key.
|
||||
* The other introducee's nonce and MAC key are added to the localState.
|
||||
* <p>
|
||||
* Our nonce is signed with the local author's long-term private key.
|
||||
* The signature is added to the localState.
|
||||
* <p>
|
||||
* Calculates a MAC and stores it in the localState.
|
||||
*/
|
||||
private void deriveMacKeysAndNonces(BdfDictionary localState,
|
||||
LocalAuthor author, SecretKey secretKey, boolean alice)
|
||||
throws FormatException, GeneralSecurityException {
|
||||
// Derive two nonces and two MAC keys from the shared secret key
|
||||
String ourNonceLabel = alice ? ALICE_NONCE_LABEL : BOB_NONCE_LABEL;
|
||||
String theirNonceLabel = alice ? BOB_NONCE_LABEL : ALICE_NONCE_LABEL;
|
||||
byte[] ourNonce = cryptoComponent.mac(ourNonceLabel, secretKey);
|
||||
byte[] theirNonce = cryptoComponent.mac(theirNonceLabel, secretKey);
|
||||
String ourKeyLabel = alice ? ALICE_MAC_KEY_LABEL : BOB_MAC_KEY_LABEL;
|
||||
String theirKeyLabel = alice ? BOB_MAC_KEY_LABEL : ALICE_MAC_KEY_LABEL;
|
||||
SecretKey ourMacKey = cryptoComponent.deriveKey(ourKeyLabel, secretKey);
|
||||
SecretKey theirMacKey =
|
||||
cryptoComponent.deriveKey(theirKeyLabel, secretKey);
|
||||
|
||||
// Save the other nonce and MAC key for the verification
|
||||
localState.put(NONCE, theirNonce);
|
||||
localState.put(MAC_KEY, theirMacKey.getBytes());
|
||||
|
||||
// Sign our nonce with our long-term identity public key
|
||||
byte[] sig = cryptoComponent.sign(SIGNING_LABEL, ourNonce,
|
||||
author.getPrivateKey());
|
||||
|
||||
// Calculate a MAC over identity public key, ephemeral public key,
|
||||
// transport properties and timestamp.
|
||||
byte[] publicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY);
|
||||
BdfDictionary tp = localState.getDictionary(OUR_TRANSPORT);
|
||||
long ourTime = localState.getLong(OUR_TIME);
|
||||
BdfList toMacList = BdfList.of(author.getPublicKey(),
|
||||
publicKeyBytes, tp, ourTime);
|
||||
byte[] toMac = clientHelper.toByteArray(toMacList);
|
||||
byte[] mac = cryptoComponent.mac(MAC_LABEL, ourMacKey, toMac);
|
||||
|
||||
// Add MAC and signature to localState, so it can be included in ACK
|
||||
localState.put(OUR_MAC, mac);
|
||||
localState.put(OUR_SIGNATURE, sig);
|
||||
}
|
||||
|
||||
void verifySignature(BdfDictionary localState)
|
||||
throws FormatException, GeneralSecurityException {
|
||||
byte[] nonce = localState.getRaw(NONCE);
|
||||
byte[] sig = localState.getRaw(SIGNATURE);
|
||||
byte[] key = localState.getRaw(PUBLIC_KEY);
|
||||
|
||||
// Verify the signature
|
||||
if (!cryptoComponent.verifySignature(sig, SIGNING_LABEL, nonce, key)) {
|
||||
LOG.warning("Invalid nonce signature in ACK");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
}
|
||||
|
||||
void verifyMac(BdfDictionary localState)
|
||||
throws FormatException, GeneralSecurityException {
|
||||
// get MAC and MAC key from session state
|
||||
byte[] mac = localState.getRaw(MAC);
|
||||
byte[] macKeyBytes = localState.getRaw(MAC_KEY);
|
||||
SecretKey macKey = new SecretKey(macKeyBytes);
|
||||
|
||||
// get MAC data and calculate a new MAC with stored key
|
||||
byte[] pubKey = localState.getRaw(PUBLIC_KEY);
|
||||
byte[] ePubKey = localState.getRaw(E_PUBLIC_KEY);
|
||||
BdfDictionary tp = localState.getDictionary(TRANSPORT);
|
||||
long timestamp = localState.getLong(TIME);
|
||||
BdfList toMacList = BdfList.of(pubKey, ePubKey, tp, timestamp);
|
||||
byte[] toMac = clientHelper.toByteArray(toMacList);
|
||||
byte[] calculatedMac = cryptoComponent.mac(MAC_LABEL, macKey, toMac);
|
||||
if (!Arrays.equals(mac, calculatedMac)) {
|
||||
LOG.warning("Received ACK with invalid MAC");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
}
|
||||
|
||||
public void abort(Transaction txn, BdfDictionary state) {
|
||||
IntroduceeEngine engine = new IntroduceeEngine();
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
localAction.put(TYPE, TYPE_ABORT);
|
||||
try {
|
||||
processStateUpdate(txn, null,
|
||||
engine.onLocalAction(state, localAction));
|
||||
} catch (DbException | IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private BdfDictionary encodeTransportProperties(
|
||||
Map<TransportId, TransportProperties> map) {
|
||||
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
for (Map.Entry<TransportId, TransportProperties> e : map.entrySet()) {
|
||||
d.put(e.getKey().getString(), e.getValue());
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
private Map<TransportId, TransportProperties> parseTransportProperties(
|
||||
BdfDictionary d) throws FormatException {
|
||||
|
||||
Map<TransportId, TransportProperties> tpMap = new HashMap<>();
|
||||
BdfDictionary tpMapDict = d.getDictionary(TRANSPORT);
|
||||
for (String key : tpMapDict.keySet()) {
|
||||
TransportId transportId = new TransportId(key);
|
||||
TransportProperties transportProperties = new TransportProperties();
|
||||
BdfDictionary tpDict = tpMapDict.getDictionary(key);
|
||||
for (String tkey : tpDict.keySet()) {
|
||||
transportProperties.put(tkey, tpDict.getString(tkey));
|
||||
}
|
||||
tpMap.put(transportId, transportProperties);
|
||||
}
|
||||
return tpMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -28,11 +28,12 @@ import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction2.event.IntroductionRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.introduction2.event.IntroductionSucceededEvent;
|
||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Map;
|
||||
@@ -41,11 +42,11 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_AUTH;
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.REMOTE_ACCEPTED;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -145,10 +146,11 @@ class IntroduceeProtocolEngine
|
||||
IntroduceeSession session, AcceptMessage m)
|
||||
throws DbException, FormatException {
|
||||
switch (session.getState()) {
|
||||
case START:
|
||||
return onRemoteResponseInStart(txn, session, m);
|
||||
case AWAIT_RESPONSES:
|
||||
case LOCAL_ACCEPTED:
|
||||
return onRemoteAccept(txn, session, m);
|
||||
case START:
|
||||
case LOCAL_DECLINED:
|
||||
case REMOTE_ACCEPTED:
|
||||
case AWAIT_AUTH:
|
||||
@@ -165,7 +167,7 @@ class IntroduceeProtocolEngine
|
||||
throws DbException, FormatException {
|
||||
switch (session.getState()) {
|
||||
case START:
|
||||
return session; // Ignore in the START state
|
||||
return onRemoteResponseInStart(txn, session, m);
|
||||
case AWAIT_RESPONSES:
|
||||
case LOCAL_DECLINED:
|
||||
case LOCAL_ACCEPTED:
|
||||
@@ -322,18 +324,6 @@ class IntroduceeProtocolEngine
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
|
||||
// Broadcast IntroductionResponseReceivedEvent
|
||||
Contact c = contactManager.getContact(s.getIntroducer().getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
IntroductionResponse request =
|
||||
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
|
||||
m.getGroupId(), INTRODUCEE, m.getTimestamp(), false,
|
||||
false, false, false, s.getRemoteAuthor().getName(),
|
||||
true);
|
||||
IntroductionResponseReceivedEvent e =
|
||||
new IntroductionResponseReceivedEvent(c.getId(), request);
|
||||
txn.attach(e);
|
||||
|
||||
// Determine next state
|
||||
IntroduceeState state =
|
||||
s.getState() == AWAIT_RESPONSES ? REMOTE_ACCEPTED : AWAIT_AUTH;
|
||||
@@ -356,12 +346,46 @@ class IntroduceeProtocolEngine
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
|
||||
// Mark the request visible in the UI
|
||||
markMessageVisibleInUi(txn, m.getMessageId());
|
||||
|
||||
// Track the incoming message
|
||||
messageTracker
|
||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||
|
||||
// Broadcast IntroductionResponseReceivedEvent
|
||||
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
IntroductionResponse request =
|
||||
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
|
||||
m.getGroupId(), INTRODUCEE, m.getTimestamp(), false,
|
||||
false, false, false, s.getRemoteAuthor().getName(),
|
||||
false);
|
||||
IntroductionResponseReceivedEvent e =
|
||||
new IntroductionResponseReceivedEvent(c.getId(), request);
|
||||
txn.attach(e);
|
||||
|
||||
// Move back to START state
|
||||
return IntroduceeSession
|
||||
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
|
||||
m.getMessageId());
|
||||
}
|
||||
|
||||
private IntroduceeSession onRemoteResponseInStart(Transaction txn,
|
||||
IntroduceeSession s, AbstractIntroductionMessage m) throws DbException {
|
||||
// The timestamp must be higher than the last request message
|
||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||
return abort(txn, s);
|
||||
// The dependency, if any, must be the last remote message
|
||||
if (isInvalidDependency(s, m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
|
||||
// Stay in START state
|
||||
return IntroduceeSession
|
||||
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
|
||||
m.getMessageId());
|
||||
}
|
||||
|
||||
private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s)
|
||||
throws DbException {
|
||||
boolean alice = isAlice(txn, s);
|
||||
@@ -459,6 +483,9 @@ class IntroduceeProtocolEngine
|
||||
if (requestId == null) throw new IllegalStateException();
|
||||
markRequestUnavailableToAnswer(txn, requestId);
|
||||
|
||||
// Broadcast abort event for testing
|
||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||
|
||||
// Reset the session back to initial state
|
||||
return IntroduceeSession
|
||||
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
|
||||
@@ -475,6 +502,9 @@ class IntroduceeProtocolEngine
|
||||
// Send an ABORT message
|
||||
Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s));
|
||||
|
||||
// Broadcast abort event for testing
|
||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||
|
||||
// Reset the session back to initial state
|
||||
return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(),
|
||||
s.getLastRemoteMessageId());
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
@@ -10,16 +10,16 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.Role;
|
||||
import org.briarproject.briar.api.introduction.Role;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_ACTIVATE;
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.START;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.START;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -1,370 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.ProtocolEngine;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction.IntroducerAction;
|
||||
import org.briarproject.briar.api.introduction.IntroducerProtocolState;
|
||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACKS;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACK_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACK_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.ERROR;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.FINISHED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroducerEngine
|
||||
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroducerEngine.class.getName());
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
|
||||
BdfDictionary localState, BdfDictionary localAction) {
|
||||
|
||||
try {
|
||||
IntroducerProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
int type = localAction.getLong(TYPE).intValue();
|
||||
IntroducerAction action = IntroducerAction.getLocal(type);
|
||||
IntroducerProtocolState nextState = currentState.next(action);
|
||||
|
||||
if (action == LOCAL_ABORT && currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error: Invalid action in state " +
|
||||
currentState.name());
|
||||
}
|
||||
return noUpdate(localState);
|
||||
}
|
||||
|
||||
localState.put(STATE, nextState.getValue());
|
||||
if (action == LOCAL_REQUEST) {
|
||||
// create the introduction requests for both contacts
|
||||
List<BdfDictionary> messages = new ArrayList<>(2);
|
||||
BdfDictionary msg1 = new BdfDictionary();
|
||||
msg1.put(TYPE, TYPE_REQUEST);
|
||||
msg1.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1));
|
||||
msg1.put(NAME, localState.getString(CONTACT_2));
|
||||
msg1.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY2));
|
||||
if (localAction.containsKey(MSG)) {
|
||||
msg1.put(MSG, localAction.getString(MSG));
|
||||
}
|
||||
msg1.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
|
||||
messages.add(msg1);
|
||||
logLocalAction(currentState, localState);
|
||||
BdfDictionary msg2 = new BdfDictionary();
|
||||
msg2.put(TYPE, TYPE_REQUEST);
|
||||
msg2.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2));
|
||||
msg2.put(NAME, localState.getString(CONTACT_1));
|
||||
msg2.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY1));
|
||||
if (localAction.containsKey(MSG)) {
|
||||
msg2.put(MSG, localAction.getString(MSG));
|
||||
}
|
||||
msg2.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
|
||||
messages.add(msg2);
|
||||
logLocalAction(currentState, localState);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
return new StateUpdate<>(false, false,
|
||||
localState, messages, events);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
try {
|
||||
IntroducerProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
int type = msg.getLong(TYPE).intValue();
|
||||
boolean one = isContact1(localState, msg);
|
||||
IntroducerAction action = IntroducerAction.getRemote(type, one);
|
||||
IntroducerProtocolState nextState = currentState.next(action);
|
||||
|
||||
logMessageReceived(currentState, nextState, localState, type, msg);
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
} else {
|
||||
return noUpdate(localState);
|
||||
}
|
||||
}
|
||||
|
||||
List<BdfDictionary> messages;
|
||||
List<Event> events;
|
||||
|
||||
// we have sent our requests and just got the 1st or 2nd response
|
||||
if (currentState == AWAIT_RESPONSES ||
|
||||
currentState == AWAIT_RESPONSE_1 ||
|
||||
currentState == AWAIT_RESPONSE_2) {
|
||||
// update next state based on message content
|
||||
action = IntroducerAction
|
||||
.getRemote(type, one, msg.getBoolean(ACCEPT));
|
||||
nextState = currentState.next(action);
|
||||
localState.put(STATE, nextState.getValue());
|
||||
if (one) localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID));
|
||||
else localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID));
|
||||
|
||||
messages = forwardMessage(localState, msg);
|
||||
events = Collections.singletonList(getEvent(localState, msg));
|
||||
}
|
||||
// we have forwarded both responses and now received the 1st or 2nd ACK
|
||||
else if (currentState == AWAIT_ACKS ||
|
||||
currentState == AWAIT_ACK_1 ||
|
||||
currentState == AWAIT_ACK_2) {
|
||||
localState.put(STATE, nextState.getValue());
|
||||
messages = forwardMessage(localState, msg);
|
||||
events = Collections.emptyList();
|
||||
}
|
||||
// we probably received a response while already being FINISHED
|
||||
else if (currentState == FINISHED) {
|
||||
// if it was a response store it to be found later
|
||||
if (action == REMOTE_ACCEPT_1 || action == REMOTE_DECLINE_1) {
|
||||
localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID));
|
||||
messages = Collections.emptyList();
|
||||
events = Collections
|
||||
.singletonList(getEvent(localState, msg));
|
||||
} else if (action == REMOTE_ACCEPT_2 ||
|
||||
action == REMOTE_DECLINE_2) {
|
||||
localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID));
|
||||
messages = Collections.emptyList();
|
||||
events = Collections
|
||||
.singletonList(getEvent(localState, msg));
|
||||
} else return noUpdate(localState);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<>(false, false,
|
||||
localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(IntroducerProtocolState state,
|
||||
BdfDictionary localState) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
try {
|
||||
LOG.info("Sending introduction request in state " + state.name());
|
||||
LOG.info("Moving on to state " +
|
||||
getState(localState.getLong(STATE)).name());
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logMessageReceived(IntroducerProtocolState currentState,
|
||||
IntroducerProtocolState nextState,
|
||||
BdfDictionary localState, int type, BdfDictionary msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String t = "unknown";
|
||||
if (type == TYPE_REQUEST) t = "Introduction";
|
||||
else if (type == TYPE_RESPONSE) t = "Response";
|
||||
else if (type == TYPE_ACK) t = "ACK";
|
||||
else if (type == TYPE_ABORT) t = "Abort";
|
||||
|
||||
LOG.info("Received " + t + " in state " + currentState.name());
|
||||
LOG.info("Moving on to state " + nextState.name());
|
||||
}
|
||||
|
||||
private List<BdfDictionary> forwardMessage(BdfDictionary localState,
|
||||
BdfDictionary message) throws FormatException {
|
||||
|
||||
// clone the message here, because we still need the original
|
||||
BdfDictionary msg = (BdfDictionary) message.clone();
|
||||
if (isContact1(localState, msg)) {
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID_2));
|
||||
} else {
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID_1));
|
||||
}
|
||||
|
||||
return Collections.singletonList(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
|
||||
BdfDictionary localState, BdfDictionary delivered) {
|
||||
try {
|
||||
return noUpdate(localState);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IntroducerProtocolState getState(Long state) {
|
||||
return IntroducerProtocolState.fromValue(state.intValue());
|
||||
}
|
||||
|
||||
private Event getEvent(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
ContactId contactId =
|
||||
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
|
||||
AuthorId authorId = new AuthorId(localState.getRaw(AUTHOR_ID_1));
|
||||
if (Arrays
|
||||
.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) {
|
||||
contactId =
|
||||
new ContactId(localState.getLong(CONTACT_ID_2).intValue());
|
||||
authorId = new AuthorId(localState.getRaw(AUTHOR_ID_2));
|
||||
}
|
||||
|
||||
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
|
||||
MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID));
|
||||
GroupId groupId = new GroupId(msg.getRaw(GROUP_ID));
|
||||
long time = msg.getLong(MESSAGE_TIME);
|
||||
String name = getOtherContact(localState, msg);
|
||||
boolean accept = msg.getBoolean(ACCEPT);
|
||||
|
||||
IntroductionResponse ir =
|
||||
new IntroductionResponse(sessionId, messageId, groupId,
|
||||
ROLE_INTRODUCER, time, false, false, false, false,
|
||||
authorId, name, accept);
|
||||
return new IntroductionResponseReceivedEvent(contactId, ir);
|
||||
}
|
||||
|
||||
private boolean isContact1(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
byte[] group = msg.getRaw(GROUP_ID);
|
||||
byte[] group1 = localState.getRaw(GROUP_ID_1);
|
||||
byte[] group2 = localState.getRaw(GROUP_ID_2);
|
||||
|
||||
if (Arrays.equals(group, group1)) {
|
||||
return true;
|
||||
} else if (Arrays.equals(group, group2)) {
|
||||
return false;
|
||||
} else {
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
private String getOtherContact(BdfDictionary localState, BdfDictionary msg)
|
||||
throws FormatException {
|
||||
|
||||
String to = localState.getString(CONTACT_2);
|
||||
if (Arrays
|
||||
.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) {
|
||||
to = localState.getString(CONTACT_1);
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
|
||||
IntroducerProtocolState currentState, BdfDictionary localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Aborting protocol session in state " +
|
||||
currentState.name());
|
||||
|
||||
localState.put(STATE, ERROR.getValue());
|
||||
List<BdfDictionary> messages = new ArrayList<>(2);
|
||||
BdfDictionary msg1 = new BdfDictionary();
|
||||
msg1.put(TYPE, TYPE_ABORT);
|
||||
msg1.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1));
|
||||
messages.add(msg1);
|
||||
BdfDictionary msg2 = new BdfDictionary();
|
||||
msg2.put(TYPE, TYPE_ABORT);
|
||||
msg2.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2));
|
||||
messages.add(msg2);
|
||||
|
||||
// send one abort event per contact
|
||||
List<Event> events = new ArrayList<>(2);
|
||||
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
|
||||
ContactId contactId1 =
|
||||
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
|
||||
ContactId contactId2 =
|
||||
new ContactId(localState.getLong(CONTACT_ID_2).intValue());
|
||||
Event event1 = new IntroductionAbortedEvent(contactId1, sessionId);
|
||||
events.add(event1);
|
||||
Event event2 = new IntroductionAbortedEvent(contactId2, sessionId);
|
||||
events.add(event2);
|
||||
|
||||
return new StateUpdate<>(false, false, localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
|
||||
BdfDictionary localState) throws FormatException {
|
||||
|
||||
return new StateUpdate<>(false, false, localState,
|
||||
Collections.<BdfDictionary>emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroducerManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroducerManager.class.getName());
|
||||
|
||||
private final MessageSender messageSender;
|
||||
private final ClientHelper clientHelper;
|
||||
private final Clock clock;
|
||||
private final CryptoComponent cryptoComponent;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
|
||||
@Inject
|
||||
IntroducerManager(MessageSender messageSender, ClientHelper clientHelper,
|
||||
Clock clock, CryptoComponent cryptoComponent,
|
||||
IntroductionGroupFactory introductionGroupFactory) {
|
||||
|
||||
this.messageSender = messageSender;
|
||||
this.clientHelper = clientHelper;
|
||||
this.clock = clock;
|
||||
this.cryptoComponent = cryptoComponent;
|
||||
this.introductionGroupFactory = introductionGroupFactory;
|
||||
}
|
||||
|
||||
public BdfDictionary initialize(Transaction txn, Contact c1, Contact c2)
|
||||
throws FormatException, DbException {
|
||||
|
||||
// create local message to keep engine state
|
||||
long now = clock.currentTimeMillis();
|
||||
Bytes salt = new Bytes(new byte[64]);
|
||||
cryptoComponent.getSecureRandom().nextBytes(salt.getBytes());
|
||||
|
||||
Message m = clientHelper.createMessage(
|
||||
introductionGroupFactory.createLocalGroup().getId(), now,
|
||||
BdfList.of(salt));
|
||||
MessageId sessionId = m.getId();
|
||||
|
||||
Group g1 = introductionGroupFactory.createIntroductionGroup(c1);
|
||||
Group g2 = introductionGroupFactory.createIntroductionGroup(c2);
|
||||
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(SESSION_ID, sessionId);
|
||||
d.put(STORAGE_ID, sessionId);
|
||||
d.put(STATE, PREPARE_REQUESTS.getValue());
|
||||
d.put(ROLE, ROLE_INTRODUCER);
|
||||
d.put(GROUP_ID_1, g1.getId());
|
||||
d.put(GROUP_ID_2, g2.getId());
|
||||
d.put(CONTACT_1, c1.getAuthor().getName());
|
||||
d.put(CONTACT_2, c2.getAuthor().getName());
|
||||
d.put(CONTACT_ID_1, c1.getId().getInt());
|
||||
d.put(CONTACT_ID_2, c2.getId().getInt());
|
||||
d.put(AUTHOR_ID_1, c1.getAuthor().getId());
|
||||
d.put(AUTHOR_ID_2, c2.getAuthor().getId());
|
||||
|
||||
// save local state to database
|
||||
clientHelper.addLocalMessage(txn, m, d, false);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void makeIntroduction(Transaction txn, Contact c1, Contact c2,
|
||||
@Nullable String msg, long timestamp)
|
||||
throws DbException, FormatException {
|
||||
|
||||
// TODO check for existing session with those contacts?
|
||||
// deny new introduction under which conditions?
|
||||
|
||||
// initialize engine state
|
||||
BdfDictionary localState = initialize(txn, c1, c2);
|
||||
|
||||
// define action
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
localAction.put(TYPE, TYPE_REQUEST);
|
||||
if (!StringUtils.isNullOrEmpty(msg)) {
|
||||
int msgLength = StringUtils.toUtf8(msg).length;
|
||||
if (msgLength > MAX_INTRODUCTION_MESSAGE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
localAction.put(MSG, msg);
|
||||
}
|
||||
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();
|
||||
processStateUpdate(txn,
|
||||
engine.onLocalAction(localState, localAction));
|
||||
}
|
||||
|
||||
public void incomingMessage(Transaction txn, BdfDictionary state,
|
||||
BdfDictionary message) throws DbException, FormatException {
|
||||
|
||||
IntroducerEngine engine = new IntroducerEngine();
|
||||
processStateUpdate(txn,
|
||||
engine.onMessageReceived(state, message));
|
||||
}
|
||||
|
||||
private void processStateUpdate(Transaction txn,
|
||||
IntroducerEngine.StateUpdate<BdfDictionary, BdfDictionary>
|
||||
result) throws DbException, FormatException {
|
||||
|
||||
// save new local state
|
||||
MessageId storageId =
|
||||
new MessageId(result.localState.getRaw(STORAGE_ID));
|
||||
clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
|
||||
|
||||
// send messages
|
||||
for (BdfDictionary d : result.toSend) {
|
||||
messageSender.sendMessage(txn, d);
|
||||
}
|
||||
|
||||
// broadcast events
|
||||
for (Event event : result.toBroadcast) {
|
||||
txn.attach(event);
|
||||
}
|
||||
}
|
||||
|
||||
public void abort(Transaction txn, BdfDictionary state) {
|
||||
IntroducerEngine engine = new IntroducerEngine();
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
localAction.put(TYPE, TYPE_ABORT);
|
||||
try {
|
||||
processStateUpdate(txn,
|
||||
engine.onLocalAction(state, localAction));
|
||||
} catch (DbException | IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -17,9 +18,10 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.briar.introduction2.IntroducerSession.Introducee;
|
||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -27,17 +29,17 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATES;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_A;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_B;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_A;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_B;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_A;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_B;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.START;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_A;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_B;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_A;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_B;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.START;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -94,6 +96,11 @@ class IntroducerProtocolEngine
|
||||
throw new UnsupportedOperationException(); // Invalid in this role
|
||||
}
|
||||
|
||||
IntroducerSession onAbortAction(Transaction txn, IntroducerSession s)
|
||||
throws DbException, FormatException {
|
||||
return abort(txn, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntroducerSession onRequestMessage(Transaction txn,
|
||||
IntroducerSession s, RequestMessage m)
|
||||
@@ -111,8 +118,7 @@ class IntroducerProtocolEngine
|
||||
case AWAIT_RESPONSE_B:
|
||||
return onRemoteAccept(txn, s, m);
|
||||
case START:
|
||||
// TODO check and update lastRemoteMsgId?
|
||||
return s; // Ignored in this state
|
||||
return onRemoteResponseInStart(txn, s, m);
|
||||
case AWAIT_AUTHS:
|
||||
case AWAIT_AUTH_A:
|
||||
case AWAIT_AUTH_B:
|
||||
@@ -135,8 +141,7 @@ class IntroducerProtocolEngine
|
||||
case AWAIT_RESPONSE_B:
|
||||
return onRemoteDecline(txn, s, m);
|
||||
case START:
|
||||
// TODO check and update lastRemoteMsgId?
|
||||
return s; // Ignored in this state
|
||||
return onRemoteResponseInStart(txn, s, m);
|
||||
case AWAIT_AUTHS:
|
||||
case AWAIT_AUTH_A:
|
||||
case AWAIT_AUTH_B:
|
||||
@@ -253,14 +258,16 @@ class IntroducerProtocolEngine
|
||||
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A;
|
||||
introducee1 = new Introducee(s.getIntroducee1(), sent);
|
||||
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
|
||||
c = contactManager.getContact(s.getIntroducee2().author.getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
c = contactManager
|
||||
.getContact(txn, s.getIntroducee2().author.getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
} else if (i.equals(s.getIntroducee2())) {
|
||||
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B;
|
||||
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
|
||||
introducee2 = new Introducee(s.getIntroducee2(), sent);
|
||||
c = contactManager.getContact(s.getIntroducee1().author.getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
c = contactManager
|
||||
.getContact(txn, s.getIntroducee1().author.getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
} else throw new AssertionError();
|
||||
|
||||
// Broadcast IntroductionResponseReceivedEvent
|
||||
@@ -296,22 +303,23 @@ class IntroducerProtocolEngine
|
||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||
long timestamp = getLocalTimestamp(s, i);
|
||||
Message sent = sendDeclineMessage(txn, i, timestamp, false);
|
||||
// Track the message
|
||||
messageTracker.trackOutgoingMessage(txn, sent);
|
||||
|
||||
// Move to the START state
|
||||
Introducee introducee1, introducee2;
|
||||
AuthorId localAuthorId =identityManager.getLocalAuthor(txn).getId();
|
||||
Contact c;
|
||||
if (i.equals(s.getIntroducee1())) {
|
||||
introducee1 = new Introducee(s.getIntroducee1(), sent);
|
||||
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
|
||||
c = contactManager.getContact(s.getIntroducee2().author.getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
c = contactManager
|
||||
.getContact(txn, s.getIntroducee2().author.getId(),
|
||||
localAuthorId);
|
||||
} else if (i.equals(s.getIntroducee2())) {
|
||||
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
|
||||
introducee2 = new Introducee(s.getIntroducee2(), sent);
|
||||
c = contactManager.getContact(s.getIntroducee2().author.getId(),
|
||||
identityManager.getLocalAuthor(txn).getId());
|
||||
c = contactManager
|
||||
.getContact(txn, s.getIntroducee1().author.getId(),
|
||||
localAuthorId);
|
||||
} else throw new AssertionError();
|
||||
|
||||
// Broadcast IntroductionResponseReceivedEvent
|
||||
@@ -327,6 +335,54 @@ class IntroducerProtocolEngine
|
||||
s.getRequestTimestamp(), introducee1, introducee2);
|
||||
}
|
||||
|
||||
private IntroducerSession onRemoteResponseInStart(Transaction txn,
|
||||
IntroducerSession s, AbstractIntroductionMessage m)
|
||||
throws DbException, FormatException {
|
||||
// The timestamp must be higher than the last request message
|
||||
if (m.getTimestamp() <= s.getRequestTimestamp())
|
||||
return abort(txn, s);
|
||||
// The dependency, if any, must be the last remote message
|
||||
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
|
||||
return abort(txn, s);
|
||||
|
||||
// Mark the response visible in the UI
|
||||
markMessageVisibleInUi(txn, m.getMessageId());
|
||||
// Track the incoming message
|
||||
messageTracker
|
||||
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
|
||||
|
||||
Introducee i = getIntroducee(s, m.getGroupId());
|
||||
Introducee introducee1, introducee2;
|
||||
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
|
||||
Contact c;
|
||||
if (i.equals(s.getIntroducee1())) {
|
||||
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
|
||||
introducee2 = s.getIntroducee2();
|
||||
c = contactManager
|
||||
.getContact(txn, s.getIntroducee1().author.getId(),
|
||||
localAuthorId);
|
||||
} else if (i.equals(s.getIntroducee2())) {
|
||||
introducee1 = s.getIntroducee1();
|
||||
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
|
||||
c = contactManager
|
||||
.getContact(txn, s.getIntroducee2().author.getId(),
|
||||
localAuthorId);
|
||||
} else throw new AssertionError();
|
||||
|
||||
// Broadcast IntroductionResponseReceivedEvent
|
||||
IntroductionResponse request =
|
||||
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
|
||||
m.getGroupId(), INTRODUCER, m.getTimestamp(), false,
|
||||
false, false, false, c.getAuthor().getName(),
|
||||
m instanceof AcceptMessage);
|
||||
IntroductionResponseReceivedEvent e =
|
||||
new IntroductionResponseReceivedEvent(c.getId(), request);
|
||||
txn.attach(e);
|
||||
|
||||
return new IntroducerSession(s.getSessionId(), START,
|
||||
s.getRequestTimestamp(), introducee1, introducee2);
|
||||
}
|
||||
|
||||
private IntroducerSession onRemoteAuth(Transaction txn,
|
||||
IntroducerSession s, AuthMessage m)
|
||||
throws DbException, FormatException {
|
||||
@@ -395,6 +451,9 @@ class IntroducerProtocolEngine
|
||||
long timestamp = getLocalTimestamp(s, i);
|
||||
Message sent = sendAbortMessage(txn, i, timestamp);
|
||||
|
||||
// Broadcast abort event for testing
|
||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||
|
||||
// Reset the session back to initial state
|
||||
Introducee introducee1, introducee2;
|
||||
if (i.equals(s.getIntroducee1())) {
|
||||
@@ -412,6 +471,10 @@ class IntroducerProtocolEngine
|
||||
IntroducerSession s) throws DbException, FormatException {
|
||||
// Mark any REQUEST messages in the session unavailable to answer
|
||||
markRequestsUnavailableToAnswer(txn, s);
|
||||
|
||||
// Broadcast abort event for testing
|
||||
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
|
||||
|
||||
// Send an ABORT message to both introducees
|
||||
long timestamp1 = getLocalTimestamp(s, s.getIntroducee1());
|
||||
Message sent1 = sendAbortMessage(txn, s.getIntroducee1(), timestamp1);
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -6,12 +6,12 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.Role;
|
||||
import org.briarproject.briar.api.introduction.Role;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
interface IntroductionConstants {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
@@ -24,14 +24,14 @@ import java.util.Map;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_ALICE_MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_MAC;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_NONCE;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_SIGN;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_BOB_MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_MASTER_KEY;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ALICE_MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_NONCE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_SIGN;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_BOB_MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
|
||||
|
||||
class IntroductionGroupFactory {
|
||||
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Group localGroup;
|
||||
|
||||
@Inject
|
||||
IntroductionGroupFactory(ContactGroupFactory contactGroupFactory) {
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
CLIENT_VERSION);
|
||||
}
|
||||
|
||||
Group createIntroductionGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
CLIENT_VERSION, c);
|
||||
}
|
||||
|
||||
Group createLocalGroup() {
|
||||
return localGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,19 +2,20 @@ package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
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.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchMessageException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
@@ -24,264 +25,278 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction.IntroducerProtocolState;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
import org.briarproject.briar.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction.Role;
|
||||
import org.briarproject.briar.client.ConversationClientImpl;
|
||||
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.FINISHED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroductionManagerImpl extends ConversationClientImpl
|
||||
implements IntroductionManager, Client, ContactHook {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroductionManagerImpl.class.getName());
|
||||
|
||||
private final IntroducerManager introducerManager;
|
||||
private final IntroduceeManager introduceeManager;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final MessageParser messageParser;
|
||||
private final SessionEncoder sessionEncoder;
|
||||
private final SessionParser sessionParser;
|
||||
private final IntroducerProtocolEngine introducerEngine;
|
||||
private final IntroduceeProtocolEngine introduceeEngine;
|
||||
private final IntroductionCrypto crypto;
|
||||
private final IdentityManager identityManager;
|
||||
|
||||
@Inject
|
||||
IntroductionManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
MetadataParser metadataParser, MessageTracker messageTracker,
|
||||
IntroducerManager introducerManager,
|
||||
IntroduceeManager introduceeManager,
|
||||
IntroductionGroupFactory introductionGroupFactory) {
|
||||
|
||||
IntroductionManagerImpl(
|
||||
DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
MetadataParser metadataParser,
|
||||
MessageTracker messageTracker,
|
||||
ContactGroupFactory contactGroupFactory,
|
||||
MessageParser messageParser,
|
||||
SessionEncoder sessionEncoder,
|
||||
SessionParser sessionParser,
|
||||
IntroducerProtocolEngine introducerEngine,
|
||||
IntroduceeProtocolEngine introduceeEngine,
|
||||
IntroductionCrypto crypto,
|
||||
IdentityManager identityManager) {
|
||||
super(db, clientHelper, metadataParser, messageTracker);
|
||||
this.introducerManager = introducerManager;
|
||||
this.introduceeManager = introduceeManager;
|
||||
this.introductionGroupFactory = introductionGroupFactory;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.messageParser = messageParser;
|
||||
this.sessionEncoder = sessionEncoder;
|
||||
this.sessionParser = sessionParser;
|
||||
this.introducerEngine = introducerEngine;
|
||||
this.introduceeEngine = introduceeEngine;
|
||||
this.crypto = crypto;
|
||||
this.identityManager = identityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
Group localGroup = introductionGroupFactory.createLocalGroup();
|
||||
// Create a local group to store protocol sessions
|
||||
Group localGroup = getLocalGroup();
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Ensure we've set things up for any pre-existing contacts
|
||||
// Set up groups for communication with any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
// TODO adapt to use upcoming ClientVersioning client
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
// Return if we've already set things up for this contact
|
||||
if (db.containsGroup(txn, g.getId())) return;
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(txn, g);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
||||
try {
|
||||
// Create an introduction group for sending introduction messages
|
||||
Group g = getContactGroup(c);
|
||||
// Return if we've already set things up for this contact
|
||||
if (db.containsGroup(txn, g.getId())) return;
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(txn, g);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary gm = new BdfDictionary();
|
||||
gm.put(CONTACT, c.getId().getInt());
|
||||
clientHelper.mergeGroupMetadata(txn, g.getId(), gm);
|
||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
GroupId gId = introductionGroupFactory.createLocalGroup().getId();
|
||||
|
||||
// search for session states where c introduced us
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(ROLE, ROLE_INTRODUCEE),
|
||||
new BdfEntry(CONTACT_ID_1, c.getId().getInt())
|
||||
);
|
||||
try {
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, gId, query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
// delete states if introducee removes introducer
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
removeSessionWithIntroducer(txn, c);
|
||||
abortOrRemoveSessionWithIntroducee(txn, c);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// check for open sessions with c and abort those,
|
||||
// so the other introducee knows
|
||||
query = BdfDictionary.of(
|
||||
new BdfEntry(ROLE, ROLE_INTRODUCER)
|
||||
);
|
||||
try {
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, gId, query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
BdfDictionary d = entry.getValue();
|
||||
ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue());
|
||||
ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue());
|
||||
|
||||
if (c1.equals(c.getId()) || c2.equals(c.getId())) {
|
||||
IntroducerProtocolState state = IntroducerProtocolState
|
||||
.fromValue(d.getLong(STATE).intValue());
|
||||
// abort protocol if still ongoing
|
||||
if (IntroducerProtocolState.isOngoing(state)) {
|
||||
introducerManager.abort(txn, d);
|
||||
}
|
||||
// also delete state if both contacts have been deleted
|
||||
if (c1.equals(c.getId())) {
|
||||
try {
|
||||
db.getContact(txn, c2);
|
||||
} catch (NoSuchContactException e) {
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
} else if (c2.equals(c.getId())) {
|
||||
try {
|
||||
db.getContact(txn, c1);
|
||||
} catch (NoSuchContactException e) {
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
|
||||
// remove the group (all messages will be removed with it)
|
||||
// this contact won't get our abort message, but the other will
|
||||
// Remove the contact group (all messages will be removed with it)
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when a new message arrived and is being validated.
|
||||
* It is the central method where we determine which role we play
|
||||
* in the introduction protocol and which engine we need to start.
|
||||
*/
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary message) throws DbException, FormatException {
|
||||
|
||||
// Get message data and type
|
||||
GroupId groupId = m.getGroupId();
|
||||
long type = message.getLong(TYPE, -1L);
|
||||
|
||||
// we are an introducee, need to initialize new state
|
||||
if (type == TYPE_REQUEST) {
|
||||
boolean stateExists = true;
|
||||
try {
|
||||
getSessionState(txn, groupId, message.getRaw(SESSION_ID), false);
|
||||
} catch (FormatException e) {
|
||||
stateExists = false;
|
||||
}
|
||||
if (stateExists) throw new FormatException();
|
||||
BdfDictionary state =
|
||||
introduceeManager.initialize(txn, groupId, message);
|
||||
try {
|
||||
introduceeManager.incomingMessage(txn, state, message);
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
introduceeManager.abort(txn, state);
|
||||
} catch (FormatException e) {
|
||||
// FIXME necessary?
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
introduceeManager.abort(txn, state);
|
||||
}
|
||||
}
|
||||
// our role can be anything
|
||||
else if (type == TYPE_RESPONSE || type == TYPE_ACK || type == TYPE_ABORT) {
|
||||
BdfDictionary state =
|
||||
getSessionState(txn, groupId, message.getRaw(SESSION_ID));
|
||||
|
||||
long role = state.getLong(ROLE, -1L);
|
||||
try {
|
||||
if (role == ROLE_INTRODUCER) {
|
||||
introducerManager.incomingMessage(txn, state, message);
|
||||
if (type == TYPE_RESPONSE)
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
} else if (role == ROLE_INTRODUCEE) {
|
||||
introduceeManager.incomingMessage(txn, state, message);
|
||||
if (type == TYPE_RESPONSE && !message.getBoolean(ACCEPT))
|
||||
messageTracker.trackIncomingMessage(txn, m);
|
||||
} else {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Unknown role '" + role + "'");
|
||||
throw new DbException();
|
||||
}
|
||||
} catch (DbException | FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state);
|
||||
else introduceeManager.abort(txn, state);
|
||||
}
|
||||
} else {
|
||||
// the message has been validated, so this should not happen
|
||||
if(LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Unknown message type '" + type + "', deleting...");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory
|
||||
.createContactGroup(CLIENT_ID, CLIENT_VERSION, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getContactGroup(Contact contact) {
|
||||
return introductionGroupFactory.createIntroductionGroup(contact);
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// Look up the session, if there is one
|
||||
SessionId sessionId = meta.getSessionId();
|
||||
IntroduceeSession newIntroduceeSession = null;
|
||||
if (sessionId == null) {
|
||||
if (meta.getMessageType() != REQUEST) throw new AssertionError();
|
||||
newIntroduceeSession = createNewIntroduceeSession(txn, m, body);
|
||||
sessionId = newIntroduceeSession.getSessionId();
|
||||
}
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
// Handle the message
|
||||
Session session;
|
||||
MessageId storageId;
|
||||
if (ss == null) {
|
||||
if (meta.getMessageType() != REQUEST) throw new FormatException();
|
||||
if (newIntroduceeSession == null) throw new AssertionError();
|
||||
storageId = createStorageId(txn);
|
||||
session = handleMessage(txn, m, body, meta.getMessageType(),
|
||||
newIntroduceeSession, introduceeEngine);
|
||||
} else {
|
||||
storageId = ss.storageId;
|
||||
Role role = sessionParser.getRole(ss.bdfSession);
|
||||
if (role == INTRODUCER) {
|
||||
session = handleMessage(txn, m, body, meta.getMessageType(),
|
||||
sessionParser.parseIntroducerSession(ss.bdfSession),
|
||||
introducerEngine);
|
||||
} else if (role == INTRODUCEE) {
|
||||
session = handleMessage(txn, m, body, meta.getMessageType(),
|
||||
sessionParser.parseIntroduceeSession(m.getGroupId(),
|
||||
ss.bdfSession), introduceeEngine);
|
||||
} else throw new AssertionError();
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
}
|
||||
|
||||
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
||||
Message m, BdfList body) throws DbException, FormatException {
|
||||
ContactId introducerId = getContactId(txn, m.getGroupId());
|
||||
Author introducer = db.getContact(txn, introducerId).getAuthor();
|
||||
Author alice = identityManager.getLocalAuthor(txn);
|
||||
Author bob = messageParser.parseRequestMessage(m, body).getAuthor();
|
||||
if (alice.equals(bob)) throw new FormatException();
|
||||
SessionId sessionId = crypto.getSessionId(introducer, alice, bob);
|
||||
return IntroduceeSession
|
||||
.getInitial(m.getGroupId(), sessionId, introducer, bob);
|
||||
}
|
||||
|
||||
private <S extends Session> S handleMessage(Transaction txn, Message m,
|
||||
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
|
||||
throws DbException, FormatException {
|
||||
if (type == REQUEST) {
|
||||
RequestMessage request = messageParser.parseRequestMessage(m, body);
|
||||
return engine.onRequestMessage(txn, session, request);
|
||||
} else if (type == ACCEPT) {
|
||||
AcceptMessage accept = messageParser.parseAcceptMessage(m, body);
|
||||
return engine.onAcceptMessage(txn, session, accept);
|
||||
} else if (type == DECLINE) {
|
||||
DeclineMessage decline = messageParser.parseDeclineMessage(m, body);
|
||||
return engine.onDeclineMessage(txn, session, decline);
|
||||
} else if (type == AUTH) {
|
||||
AuthMessage auth = messageParser.parseAuthMessage(m, body);
|
||||
return engine.onAuthMessage(txn, session, auth);
|
||||
} else if (type == ACTIVATE) {
|
||||
ActivateMessage activate =
|
||||
messageParser.parseActivateMessage(m, body);
|
||||
return engine.onActivateMessage(txn, session, activate);
|
||||
} else if (type == ABORT) {
|
||||
AbortMessage abort = messageParser.parseAbortMessage(m, body);
|
||||
return engine.onAbortMessage(txn, session, abort);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private StoredSession getSession(Transaction txn,
|
||||
@Nullable SessionId sessionId) throws DbException, FormatException {
|
||||
if (sessionId == null) return null;
|
||||
BdfDictionary query = sessionParser.getSessionQuery(sessionId);
|
||||
Map<MessageId, BdfDictionary> results = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
|
||||
query);
|
||||
if (results.size() > 1) throw new DbException();
|
||||
if (results.isEmpty()) return null;
|
||||
return new StoredSession(results.keySet().iterator().next(),
|
||||
results.values().iterator().next());
|
||||
}
|
||||
|
||||
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException, FormatException {
|
||||
BdfDictionary meta =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
|
||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
||||
}
|
||||
|
||||
private MessageId createStorageId(Transaction txn) throws DbException {
|
||||
Message m = clientHelper
|
||||
.createMessageForStoringMetadata(getLocalGroup().getId());
|
||||
db.addLocalMessage(txn, m, new Metadata(), false);
|
||||
return m.getId();
|
||||
}
|
||||
|
||||
private void storeSession(Transaction txn, MessageId storageId,
|
||||
Session session) throws DbException, FormatException {
|
||||
BdfDictionary d;
|
||||
if (session.getRole() == INTRODUCER) {
|
||||
d = sessionEncoder
|
||||
.encodeIntroducerSession((IntroducerSession) session);
|
||||
} else if (session.getRole() == INTRODUCEE) {
|
||||
d = sessionEncoder
|
||||
.encodeIntroduceeSession((IntroduceeSession) session);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
clientHelper.mergeMessageMetadata(txn, storageId, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
|
||||
long timestamp) throws DbException, FormatException {
|
||||
|
||||
long timestamp) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
introducerManager.makeIntroduction(txn, c1, c2, msg, timestamp);
|
||||
Group g1 = getContactGroup(c1);
|
||||
Group g2 = getContactGroup(c2);
|
||||
messageTracker.trackMessage(txn, g1.getId(), timestamp, true);
|
||||
messageTracker.trackMessage(txn, g2.getId(), timestamp, true);
|
||||
// Look up the session, if there is one
|
||||
Author introducer = identityManager.getLocalAuthor(txn);
|
||||
SessionId sessionId =
|
||||
crypto.getSessionId(introducer, c1.getAuthor(),
|
||||
c2.getAuthor());
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
// Create or parse the session
|
||||
IntroducerSession session;
|
||||
MessageId storageId;
|
||||
if (ss == null) {
|
||||
// This is the first request - create a new session
|
||||
GroupId groupId1 = getContactGroup(c1).getId();
|
||||
GroupId groupId2 = getContactGroup(c2).getId();
|
||||
session = new IntroducerSession(sessionId, groupId1,
|
||||
c1.getAuthor(), groupId2, c2.getAuthor());
|
||||
storageId = createStorageId(txn);
|
||||
} else {
|
||||
// An earlier request exists, so we already have a session
|
||||
session = sessionParser.parseIntroducerSession(ss.bdfSession);
|
||||
storageId = ss.storageId;
|
||||
}
|
||||
// Handle the request action
|
||||
session = introducerEngine
|
||||
.onRequestAction(txn, session, msg, timestamp);
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
@@ -289,147 +304,78 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
|
||||
@Override
|
||||
public void acceptIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException, FormatException {
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
Contact c = db.getContact(txn, contactId);
|
||||
Group g = getContactGroup(c);
|
||||
BdfDictionary state =
|
||||
getSessionState(txn, g.getId(), sessionId.getBytes());
|
||||
|
||||
introduceeManager.acceptIntroduction(txn, state, timestamp);
|
||||
messageTracker.trackMessage(txn, g.getId(), timestamp, true);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
long timestamp) throws DbException {
|
||||
respondToRequest(contactId, sessionId, timestamp, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declineIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException, FormatException {
|
||||
long timestamp) throws DbException {
|
||||
respondToRequest(contactId, sessionId, timestamp, false);
|
||||
}
|
||||
|
||||
private void respondToRequest(ContactId contactId, SessionId sessionId,
|
||||
long timestamp, boolean accept) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
Contact c = db.getContact(txn, contactId);
|
||||
Group g = getContactGroup(c);
|
||||
BdfDictionary state =
|
||||
getSessionState(txn, g.getId(), sessionId.getBytes());
|
||||
|
||||
introduceeManager.declineIntroduction(txn, state, timestamp);
|
||||
messageTracker.trackMessage(txn, g.getId(), timestamp, true);
|
||||
// Look up the session
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
if (ss == null) throw new IllegalArgumentException();
|
||||
// Parse the session
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
GroupId contactGroupId = getContactGroup(contact).getId();
|
||||
IntroduceeSession session = sessionParser
|
||||
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
|
||||
// Handle the join or leave action
|
||||
if (accept) {
|
||||
session = introduceeEngine
|
||||
.onAcceptAction(txn, session, timestamp);
|
||||
} else {
|
||||
session = introduceeEngine
|
||||
.onDeclineAction(txn, session, timestamp);
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, ss.storageId, session);
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<IntroductionMessage> getIntroductionMessages(
|
||||
ContactId contactId) throws DbException {
|
||||
|
||||
Collection<IntroductionMessage> list = new ArrayList<>();
|
||||
|
||||
Map<MessageId, BdfDictionary> metadata;
|
||||
Collection<MessageStatus> statuses;
|
||||
public Collection<IntroductionMessage> getIntroductionMessages(ContactId c)
|
||||
throws DbException {
|
||||
List<IntroductionMessage> messages;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
// get messages and their status
|
||||
GroupId g = getContactGroup(db.getContact(txn, contactId)).getId();
|
||||
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
statuses = db.getMessageStatus(txn, contactId, g);
|
||||
|
||||
// turn messages into classes for the UI
|
||||
for (MessageStatus s : statuses) {
|
||||
MessageId messageId = s.getMessageId();
|
||||
BdfDictionary msg = metadata.get(messageId);
|
||||
if (msg == null) continue;
|
||||
|
||||
try {
|
||||
long type = msg.getLong(TYPE);
|
||||
if (type == TYPE_ACK || type == TYPE_ABORT) continue;
|
||||
|
||||
// get session state
|
||||
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
|
||||
BdfDictionary state =
|
||||
getSessionState(txn, g, sessionId.getBytes());
|
||||
|
||||
int role = state.getLong(ROLE).intValue();
|
||||
boolean local;
|
||||
long time = msg.getLong(MESSAGE_TIME);
|
||||
boolean accepted = msg.getBoolean(ACCEPT, false);
|
||||
boolean read = msg.getBoolean(MSG_KEY_READ, false);
|
||||
AuthorId authorId;
|
||||
String name;
|
||||
if (type == TYPE_RESPONSE) {
|
||||
if (role == ROLE_INTRODUCER) {
|
||||
if (!concernsThisContact(contactId, messageId, state)) {
|
||||
// this response is not from contactId
|
||||
continue;
|
||||
}
|
||||
local = false;
|
||||
authorId =
|
||||
getAuthorIdForIntroducer(contactId, state);
|
||||
name = getNameForIntroducer(contactId, state);
|
||||
} else {
|
||||
if (Arrays.equals(state.getRaw(NOT_OUR_RESPONSE),
|
||||
messageId.getBytes())) {
|
||||
// this response is not ours,
|
||||
// check if it was a decline
|
||||
if (!accepted) {
|
||||
local = false;
|
||||
} else {
|
||||
// don't include positive responses
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
local = true;
|
||||
}
|
||||
authorId = new AuthorId(
|
||||
state.getRaw(REMOTE_AUTHOR_ID));
|
||||
name = state.getString(NAME);
|
||||
}
|
||||
IntroductionResponse ir = new IntroductionResponse(
|
||||
sessionId, messageId, g, role, time, local,
|
||||
s.isSent(), s.isSeen(), read, authorId, name,
|
||||
accepted);
|
||||
list.add(ir);
|
||||
} else if (type == TYPE_REQUEST) {
|
||||
String message;
|
||||
boolean answered, exists, introducesOtherIdentity;
|
||||
if (role == ROLE_INTRODUCER) {
|
||||
local = true;
|
||||
authorId =
|
||||
getAuthorIdForIntroducer(contactId, state);
|
||||
name = getNameForIntroducer(contactId, state);
|
||||
message = msg.getOptionalString(MSG);
|
||||
answered = false;
|
||||
exists = false;
|
||||
introducesOtherIdentity = false;
|
||||
} else {
|
||||
local = false;
|
||||
authorId = new AuthorId(
|
||||
state.getRaw(REMOTE_AUTHOR_ID));
|
||||
name = state.getString(NAME);
|
||||
message = state.getOptionalString(MSG);
|
||||
boolean finished = state.getLong(STATE) ==
|
||||
FINISHED.getValue();
|
||||
answered = finished || state.getBoolean(ANSWERED);
|
||||
exists = state.getBoolean(EXISTS);
|
||||
introducesOtherIdentity =
|
||||
state.getBoolean(REMOTE_AUTHOR_IS_US);
|
||||
}
|
||||
IntroductionRequest ir = new IntroductionRequest(
|
||||
sessionId, messageId, g, role, time, local,
|
||||
s.isSent(), s.isSeen(), read, authorId, name,
|
||||
accepted, message, answered, exists,
|
||||
introducesOtherIdentity);
|
||||
list.add(ir);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
GroupId contactGroupId = getContactGroup(contact).getId();
|
||||
BdfDictionary query = messageParser.getMessagesVisibleInUiQuery();
|
||||
Map<MessageId, BdfDictionary> results = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
||||
messages = new ArrayList<>(results.size());
|
||||
for (Map.Entry<MessageId, BdfDictionary> e : results.entrySet()) {
|
||||
MessageId m = e.getKey();
|
||||
MessageMetadata meta =
|
||||
messageParser.parseMetadata(e.getValue());
|
||||
MessageStatus status = db.getMessageStatus(txn, c, m);
|
||||
StoredSession ss = getSession(txn, meta.getSessionId());
|
||||
if (ss == null) throw new AssertionError();
|
||||
MessageType type = meta.getMessageType();
|
||||
if (type == REQUEST) {
|
||||
messages.add(
|
||||
parseInvitationRequest(txn, contactGroupId, m,
|
||||
meta, status, ss.bdfSession));
|
||||
} else if (type == ACCEPT) {
|
||||
messages.add(
|
||||
parseInvitationResponse(txn, contactGroupId, m,
|
||||
meta, status, ss.bdfSession, true));
|
||||
} else if (type == DECLINE) {
|
||||
messages.add(
|
||||
parseInvitationResponse(txn, contactGroupId, m,
|
||||
meta, status, ss.bdfSession, false));
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
@@ -438,88 +384,129 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return list;
|
||||
return messages;
|
||||
}
|
||||
|
||||
private String getNameForIntroducer(ContactId contactId,
|
||||
BdfDictionary state) throws FormatException {
|
||||
|
||||
if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue())
|
||||
return state.getString(CONTACT_2);
|
||||
if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue())
|
||||
return state.getString(CONTACT_1);
|
||||
throw new RuntimeException(
|
||||
"Contact not part of this introduction session");
|
||||
}
|
||||
|
||||
private AuthorId getAuthorIdForIntroducer(ContactId contactId,
|
||||
BdfDictionary state) throws FormatException {
|
||||
|
||||
if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue())
|
||||
return new AuthorId(state.getRaw(AUTHOR_ID_2));
|
||||
if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue())
|
||||
return new AuthorId(state.getRaw(AUTHOR_ID_1));
|
||||
throw new RuntimeException(
|
||||
"Contact not part of this introduction session");
|
||||
}
|
||||
|
||||
private boolean concernsThisContact(ContactId contactId, MessageId messageId,
|
||||
BdfDictionary state) throws FormatException {
|
||||
|
||||
if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) {
|
||||
return Arrays.equals(state.getRaw(RESPONSE_1, new byte[0]),
|
||||
messageId.getBytes());
|
||||
} else {
|
||||
return Arrays.equals(state.getRaw(RESPONSE_2, new byte[0]),
|
||||
messageId.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private BdfDictionary getSessionState(Transaction txn, GroupId groupId,
|
||||
byte[] sessionId, boolean warn)
|
||||
private IntroductionRequest parseInvitationRequest(Transaction txn,
|
||||
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
||||
MessageStatus status, BdfDictionary bdfSession)
|
||||
throws DbException, FormatException {
|
||||
Role role = sessionParser.getRole(bdfSession);
|
||||
SessionId sessionId;
|
||||
Author author;
|
||||
if (role == INTRODUCER) {
|
||||
IntroducerSession session =
|
||||
sessionParser.parseIntroducerSession(bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
if (localAuthor.equals(session.getIntroducee1().author)) {
|
||||
author = session.getIntroducee2().author;
|
||||
} else {
|
||||
author = session.getIntroducee1().author;
|
||||
}
|
||||
} else if (role == INTRODUCEE) {
|
||||
IntroduceeSession session = sessionParser
|
||||
.parseIntroduceeSession(contactGroupId, bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
author = session.getRemoteAuthor();
|
||||
} else throw new AssertionError();
|
||||
String message = ""; // TODO
|
||||
boolean contactExists = false; // TODO
|
||||
|
||||
try {
|
||||
// See if we can find the state directly for the introducer
|
||||
BdfDictionary state = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn,
|
||||
new MessageId(sessionId));
|
||||
GroupId g1 = new GroupId(state.getRaw(GROUP_ID_1));
|
||||
GroupId g2 = new GroupId(state.getRaw(GROUP_ID_2));
|
||||
if (!g1.equals(groupId) && !g2.equals(groupId)) {
|
||||
throw new NoSuchMessageException();
|
||||
return new IntroductionRequest(sessionId, m, contactGroupId,
|
||||
role, meta.getTimestamp(), meta.isLocal(),
|
||||
status.isSent(), status.isSeen(), meta.isRead(),
|
||||
author.getName(), false, message, !meta.isAvailableToAnswer(),
|
||||
contactExists);
|
||||
}
|
||||
|
||||
private IntroductionResponse parseInvitationResponse(Transaction txn,
|
||||
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
||||
MessageStatus status, BdfDictionary bdfSession, boolean accept)
|
||||
throws FormatException, DbException {
|
||||
Role role = sessionParser.getRole(bdfSession);
|
||||
SessionId sessionId;
|
||||
Author author;
|
||||
if (role == INTRODUCER) {
|
||||
IntroducerSession session =
|
||||
sessionParser.parseIntroducerSession(bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
if (localAuthor.equals(session.getIntroducee1().author)) {
|
||||
author = session.getIntroducee2().author;
|
||||
} else {
|
||||
author = session.getIntroducee1().author;
|
||||
}
|
||||
return state;
|
||||
} catch (NoSuchMessageException e) {
|
||||
// State not found directly, so iterate over all states
|
||||
// to find state for introducee
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn,
|
||||
introductionGroupFactory.createLocalGroup().getId());
|
||||
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
|
||||
if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) {
|
||||
BdfDictionary state = m.getValue();
|
||||
GroupId g = new GroupId(state.getRaw(GROUP_ID));
|
||||
if (g.equals(groupId)) return state;
|
||||
}
|
||||
}
|
||||
if (warn && LOG.isLoggable(WARNING))
|
||||
LOG.warning("No session state found");
|
||||
throw new FormatException();
|
||||
} else if (role == INTRODUCEE) {
|
||||
IntroduceeSession session = sessionParser
|
||||
.parseIntroduceeSession(contactGroupId, bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
author = session.getRemoteAuthor();
|
||||
} else throw new AssertionError();
|
||||
return new IntroductionResponse(sessionId, m, contactGroupId,
|
||||
role, meta.getTimestamp(), meta.isLocal(), status.isSent(),
|
||||
status.isSeen(), meta.isRead(), author.getName(), accept);
|
||||
}
|
||||
|
||||
private void removeSessionWithIntroducer(Transaction txn,
|
||||
Contact introducer) throws DbException, FormatException {
|
||||
BdfDictionary query = sessionEncoder
|
||||
.getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor());
|
||||
Map<MessageId, BdfDictionary> sessions = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
|
||||
query);
|
||||
for (MessageId id : sessions.keySet()) {
|
||||
db.deleteMessageMetadata(txn, id); // TODO needed?
|
||||
db.removeMessage(txn, id);
|
||||
}
|
||||
}
|
||||
|
||||
private BdfDictionary getSessionState(Transaction txn, GroupId groupId,
|
||||
byte[] sessionId) throws DbException, FormatException {
|
||||
|
||||
return getSessionState(txn, groupId, sessionId, true);
|
||||
private void abortOrRemoveSessionWithIntroducee(Transaction txn,
|
||||
Contact c) throws DbException, FormatException {
|
||||
BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery();
|
||||
Map<MessageId, BdfDictionary> sessions = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
|
||||
query);
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
for (Map.Entry<MessageId, BdfDictionary> session : sessions
|
||||
.entrySet()) {
|
||||
IntroducerSession s =
|
||||
sessionParser.parseIntroducerSession(session.getValue());
|
||||
if (s.getIntroducee1().author.equals(c.getAuthor())) {
|
||||
abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(),
|
||||
s.getIntroducee2(), localAuthor);
|
||||
} else if (s.getIntroducee2().author.equals(c.getAuthor())) {
|
||||
abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(),
|
||||
s.getIntroducee1(), localAuthor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteMessage(Transaction txn, MessageId messageId)
|
||||
throws DbException {
|
||||
private void abortOrRemoveSessionWithIntroducee(Transaction txn,
|
||||
IntroducerSession s, MessageId storageId, Introducee i,
|
||||
LocalAuthor localAuthor) throws DbException, FormatException {
|
||||
if (db.containsContact(txn, i.author.getId(), localAuthor.getId())) {
|
||||
IntroducerSession session = introducerEngine.onAbortAction(txn, s);
|
||||
storeSession(txn, storageId, session);
|
||||
} else {
|
||||
db.deleteMessageMetadata(txn, storageId); // TODO needed?
|
||||
db.removeMessage(txn, storageId);
|
||||
}
|
||||
}
|
||||
|
||||
db.deleteMessage(txn, messageId);
|
||||
db.deleteMessageMetadata(txn, messageId);
|
||||
private Group getLocalGroup() {
|
||||
return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
|
||||
}
|
||||
|
||||
private static class StoredSession {
|
||||
|
||||
private final MessageId storageId;
|
||||
private final BdfDictionary bdfSession;
|
||||
|
||||
private StoredSession(MessageId storageId, BdfDictionary bdfSession) {
|
||||
this.storageId = storageId;
|
||||
this.bdfSession = bdfSession;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
import org.briarproject.briar.api.messaging.ConversationManager;
|
||||
|
||||
@@ -21,22 +21,22 @@ import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT
|
||||
public class IntroductionModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
IntroductionManager introductionManager;
|
||||
@Inject
|
||||
IntroductionValidator introductionValidator;
|
||||
@Inject
|
||||
IntroductionManager introductionManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
IntroductionValidator provideValidator(
|
||||
MessageQueueManager messageQueueManager,
|
||||
MetadataEncoder metadataEncoder, ClientHelper clientHelper,
|
||||
Clock clock) {
|
||||
IntroductionValidator provideValidator(ValidationManager validationManager,
|
||||
MessageEncoder messageEncoder, MetadataEncoder metadataEncoder,
|
||||
ClientHelper clientHelper, Clock clock) {
|
||||
|
||||
IntroductionValidator introductionValidator = new IntroductionValidator(
|
||||
clientHelper, metadataEncoder, clock);
|
||||
messageQueueManager.registerMessageValidator(CLIENT_ID,
|
||||
IntroductionValidator introductionValidator =
|
||||
new IntroductionValidator(messageEncoder, clientHelper,
|
||||
metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID,
|
||||
introductionValidator);
|
||||
|
||||
return introductionValidator;
|
||||
@@ -46,16 +46,42 @@ public class IntroductionModule {
|
||||
@Singleton
|
||||
IntroductionManager provideIntroductionManager(
|
||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||
MessageQueueManager messageQueueManager,
|
||||
ValidationManager validationManager,
|
||||
ConversationManager conversationManager,
|
||||
IntroductionManagerImpl introductionManager) {
|
||||
|
||||
lifecycleManager.registerClient(introductionManager);
|
||||
contactManager.registerContactHook(introductionManager);
|
||||
messageQueueManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
introductionManager);
|
||||
conversationManager.registerConversationClient(introductionManager);
|
||||
|
||||
return introductionManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MessageParser provideMessageParser(MessageParserImpl messageParser) {
|
||||
return messageParser;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
|
||||
return messageEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
|
||||
return sessionParser;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
|
||||
return sessionEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
IntroductionCrypto provideIntroductionCrypto(
|
||||
IntroductionCryptoImpl introductionCrypto) {
|
||||
return introductionCrypto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
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.BdfMessageValidator;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
@@ -9,183 +11,166 @@ import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.client.BdfQueueMessageValidator;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
||||
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroductionValidator extends BdfQueueMessageValidator {
|
||||
class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
IntroductionValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
private final MessageEncoder messageEncoder;
|
||||
|
||||
IntroductionValidator(MessageEncoder messageEncoder,
|
||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||
Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
this.messageEncoder = messageEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||
|
||||
BdfDictionary d;
|
||||
long type = body.getLong(0);
|
||||
byte[] id = body.getRaw(1);
|
||||
checkLength(id, SessionId.LENGTH);
|
||||
switch (type) {
|
||||
case REQUEST:
|
||||
return validateRequestMessage(m, body);
|
||||
case ACCEPT:
|
||||
return validateAcceptMessage(m, body);
|
||||
case AUTH:
|
||||
return validateAuthMessage(m, body);
|
||||
case DECLINE:
|
||||
case ACTIVATE:
|
||||
case ABORT:
|
||||
return validateOtherMessage(type, m, body);
|
||||
default:
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
if (type == TYPE_REQUEST) {
|
||||
d = validateRequest(body);
|
||||
} else if (type == TYPE_RESPONSE) {
|
||||
d = validateResponse(body);
|
||||
} else if (type == TYPE_ACK) {
|
||||
d = validateAck(body);
|
||||
} else if (type == TYPE_ABORT) {
|
||||
d = validateAbort(body);
|
||||
private BdfMessageContext validateRequestMessage(Message m, BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 4);
|
||||
|
||||
byte[] previousMessageId = body.getOptionalRaw(1);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
BdfList authorList = body.getList(2);
|
||||
clientHelper.parseAndValidateAuthor(authorList);
|
||||
|
||||
String msg = body.getOptionalString(3);
|
||||
checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
|
||||
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeRequestMetadata(m.getTimestamp(), false, false,
|
||||
false, false);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
throw new FormatException();
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
|
||||
d.put(TYPE, type);
|
||||
d.put(SESSION_ID, id);
|
||||
d.put(GROUP_ID, m.getGroupId());
|
||||
d.put(MESSAGE_ID, m.getId());
|
||||
d.put(MESSAGE_TIME, m.getTimestamp());
|
||||
return new BdfMessageContext(d);
|
||||
}
|
||||
|
||||
private BdfDictionary validateRequest(BdfList message)
|
||||
private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 6);
|
||||
|
||||
checkSize(message, 4, 5);
|
||||
byte[] sessionIdBytes = body.getRaw(1);
|
||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||
|
||||
// TODO: Exchange author format version
|
||||
byte[] previousMessageId = body.getOptionalRaw(2);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
// parse contact name
|
||||
String name = message.getString(2);
|
||||
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
|
||||
byte[] ephemeralPublicKey = body.getRaw(3);
|
||||
checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
|
||||
|
||||
// parse contact's public key
|
||||
byte[] key = message.getRaw(3);
|
||||
checkLength(key, 0, MAX_PUBLIC_KEY_LENGTH);
|
||||
body.getLong(4);
|
||||
|
||||
// parse (optional) message
|
||||
String msg = null;
|
||||
if (message.size() == 5) {
|
||||
msg = message.getString(4);
|
||||
checkLength(msg, 0, MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
BdfDictionary transportProperties = body.getDictionary(5);
|
||||
if (transportProperties.size() < 1) throw new FormatException();
|
||||
for (String tId : transportProperties.keySet()) {
|
||||
checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = transportProperties.getDictionary(tId);
|
||||
clientHelper.parseAndValidateTransportProperties(tProps);
|
||||
}
|
||||
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(NAME, name);
|
||||
d.put(PUBLIC_KEY, key);
|
||||
if (msg != null) {
|
||||
d.put(MSG, msg);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
private BdfDictionary validateResponse(BdfList message)
|
||||
throws FormatException {
|
||||
|
||||
checkSize(message, 3, 6);
|
||||
|
||||
// parse accept/decline
|
||||
boolean accept = message.getBoolean(2);
|
||||
|
||||
long time = 0;
|
||||
byte[] pubkey = null;
|
||||
BdfDictionary tp = new BdfDictionary();
|
||||
if (accept) {
|
||||
checkSize(message, 6);
|
||||
|
||||
// parse timestamp
|
||||
time = message.getLong(3);
|
||||
|
||||
// parse ephemeral public key
|
||||
pubkey = message.getRaw(4);
|
||||
checkLength(pubkey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
|
||||
// parse transport properties
|
||||
tp = message.getDictionary(5);
|
||||
if (tp.size() < 1) throw new FormatException();
|
||||
for (String tId : tp.keySet()) {
|
||||
checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = tp.getDictionary(tId);
|
||||
checkSize(tProps, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
||||
for (String propId : tProps.keySet()) {
|
||||
checkLength(propId, 0, MAX_PROPERTY_LENGTH);
|
||||
String prop = tProps.getString(propId);
|
||||
checkLength(prop, 0, MAX_PROPERTY_LENGTH);
|
||||
}
|
||||
}
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false,
|
||||
false, false);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
checkSize(message, 3);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(ACCEPT, accept);
|
||||
if (accept) {
|
||||
d.put(TIME, time);
|
||||
d.put(E_PUBLIC_KEY, pubkey);
|
||||
d.put(TRANSPORT, tp);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
private BdfDictionary validateAck(BdfList message) throws FormatException {
|
||||
checkSize(message, 4);
|
||||
|
||||
byte[] mac = message.getRaw(2);
|
||||
checkLength(mac, 1, MAC_LENGTH);
|
||||
|
||||
byte[] sig = message.getRaw(3);
|
||||
checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
|
||||
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(MAC, mac);
|
||||
d.put(SIGNATURE, sig);
|
||||
return d;
|
||||
}
|
||||
|
||||
private BdfDictionary validateAbort(BdfList message)
|
||||
private BdfMessageContext validateAuthMessage(Message m, BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 5);
|
||||
|
||||
checkSize(message, 2);
|
||||
byte[] sessionIdBytes = body.getRaw(1);
|
||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||
|
||||
// Return the metadata
|
||||
return new BdfDictionary();
|
||||
byte[] previousMessageId = body.getRaw(2);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
byte[] mac = body.getRaw(3);
|
||||
checkLength(mac, MAC_BYTES);
|
||||
|
||||
byte[] signature = body.getRaw(4);
|
||||
checkLength(signature, 1, MAX_SIGNATURE_BYTES);
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false,
|
||||
false);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
|
||||
private BdfMessageContext validateOtherMessage(MessageType type,
|
||||
Message m, BdfList body) throws FormatException {
|
||||
checkSize(body, 3);
|
||||
|
||||
byte[] sessionIdBytes = body.getRaw(1);
|
||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||
|
||||
byte[] previousMessageId = body.getOptionalRaw(2);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeMetadata(type, sessionId, m.getTimestamp(), false, false,
|
||||
false);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -20,19 +20,19 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
|
||||
@NotNullByDefault
|
||||
class MessageEncoderImpl implements MessageEncoder {
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -18,14 +18,14 @@ import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
|
||||
@NotNullByDefault
|
||||
class MessageParserImpl implements MessageParser {
|
||||
@@ -1,125 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
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.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MessageSender {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final Clock clock;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MessageQueueManager messageQueueManager;
|
||||
|
||||
@Inject
|
||||
MessageSender(DatabaseComponent db, ClientHelper clientHelper, Clock clock,
|
||||
MetadataEncoder metadataEncoder,
|
||||
MessageQueueManager messageQueueManager) {
|
||||
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.clock = clock;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.messageQueueManager = messageQueueManager;
|
||||
}
|
||||
|
||||
void sendMessage(Transaction txn, BdfDictionary message)
|
||||
throws DbException, FormatException {
|
||||
|
||||
BdfList bdfList = encodeMessage(message);
|
||||
byte[] body = clientHelper.toByteArray(bdfList);
|
||||
GroupId groupId = new GroupId(message.getRaw(GROUP_ID));
|
||||
Group group = db.getGroup(txn, groupId);
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
|
||||
message.put(MESSAGE_TIME, timestamp);
|
||||
Metadata metadata = metadataEncoder.encode(message);
|
||||
|
||||
messageQueueManager.sendMessage(txn, group, timestamp, body, metadata);
|
||||
}
|
||||
|
||||
private BdfList encodeMessage(BdfDictionary d) throws FormatException {
|
||||
|
||||
BdfList body;
|
||||
long type = d.getLong(TYPE);
|
||||
if (type == TYPE_REQUEST) {
|
||||
body = encodeRequest(d);
|
||||
} else if (type == TYPE_RESPONSE) {
|
||||
body = encodeResponse(d);
|
||||
} else if (type == TYPE_ACK) {
|
||||
body = encodeAck(d);
|
||||
} else if (type == TYPE_ABORT) {
|
||||
body = encodeAbort(d);
|
||||
} else {
|
||||
throw new FormatException();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
private BdfList encodeRequest(BdfDictionary d) throws FormatException {
|
||||
BdfList list = BdfList.of(TYPE_REQUEST, d.getRaw(SESSION_ID),
|
||||
d.getString(NAME), d.getRaw(PUBLIC_KEY));
|
||||
|
||||
if (d.containsKey(MSG)) {
|
||||
list.add(d.getString(MSG));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private BdfList encodeResponse(BdfDictionary d) throws FormatException {
|
||||
BdfList list = BdfList.of(TYPE_RESPONSE, d.getRaw(SESSION_ID),
|
||||
d.getBoolean(ACCEPT));
|
||||
|
||||
if (d.getBoolean(ACCEPT)) {
|
||||
list.add(d.getLong(TIME));
|
||||
list.add(d.getRaw(E_PUBLIC_KEY));
|
||||
list.add(d.getDictionary(TRANSPORT));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private BdfList encodeAck(BdfDictionary d) throws FormatException {
|
||||
return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID), d.getRaw(MAC),
|
||||
d.getRaw(SIGNATURE));
|
||||
}
|
||||
|
||||
private BdfList encodeAbort(BdfDictionary d) throws FormatException {
|
||||
return BdfList.of(TYPE_ABORT, d.getRaw(SESSION_ID));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RequestMessage extends IntroductionMessage {
|
||||
class RequestMessage extends AbstractIntroductionMessage {
|
||||
|
||||
private final Author author;
|
||||
@Nullable
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.Role;
|
||||
import org.briarproject.briar.api.introduction.Role;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
interface SessionEncoder {
|
||||
|
||||
BdfDictionary getIntroduceeSessionsByIntroducerQuery(Author introducer);
|
||||
|
||||
BdfDictionary getIntroducerSessionsQuery();
|
||||
|
||||
BdfDictionary encodeIntroducerSession(IntroducerSession s);
|
||||
|
||||
BdfDictionary encodeIntroduceeSession(IntroduceeSession s);
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.briar.introduction2.IntroducerSession.Introducee;
|
||||
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -14,28 +16,30 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -48,6 +52,23 @@ class SessionEncoderImpl implements SessionEncoder {
|
||||
this.clientHelper = clientHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary getIntroduceeSessionsByIntroducerQuery(
|
||||
Author introducer) {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(SESSION_KEY_ROLE, INTRODUCEE.getValue()),
|
||||
new BdfEntry(SESSION_KEY_INTRODUCER,
|
||||
clientHelper.toList(introducer))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary getIntroducerSessionsQuery() {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(SESSION_KEY_ROLE, INTRODUCER.getValue())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary encodeIntroducerSession(IntroducerSession s) {
|
||||
BdfDictionary d = encodeSession(s);
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.Role;
|
||||
import org.briarproject.briar.api.introduction.Role;
|
||||
|
||||
@NotNullByDefault
|
||||
interface SessionParser {
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -12,8 +12,8 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.Role;
|
||||
import org.briarproject.briar.introduction2.IntroducerSession.Introducee;
|
||||
import org.briarproject.briar.api.introduction.Role;
|
||||
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -22,30 +22,30 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
interface State {
|
||||
|
||||
int getValue();
|
||||
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
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.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionManager;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionMessage;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction2.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction2.Role;
|
||||
import org.briarproject.briar.client.ConversationClientImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroductionManagerImpl extends ConversationClientImpl
|
||||
implements IntroductionManager, Client, ContactHook {
|
||||
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final MessageParser messageParser;
|
||||
private final SessionEncoder sessionEncoder;
|
||||
private final SessionParser sessionParser;
|
||||
private final IntroducerProtocolEngine introducerEngine;
|
||||
private final IntroduceeProtocolEngine introduceeEngine;
|
||||
private final IntroductionCrypto crypto;
|
||||
private final IdentityManager identityManager;
|
||||
|
||||
@Inject
|
||||
IntroductionManagerImpl(
|
||||
DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
MetadataParser metadataParser,
|
||||
MessageTracker messageTracker,
|
||||
ContactGroupFactory contactGroupFactory,
|
||||
MessageParser messageParser,
|
||||
SessionEncoder sessionEncoder,
|
||||
SessionParser sessionParser,
|
||||
IntroducerProtocolEngine introducerEngine,
|
||||
IntroduceeProtocolEngine introduceeEngine,
|
||||
IntroductionCrypto crypto,
|
||||
IdentityManager identityManager) {
|
||||
super(db, clientHelper, metadataParser, messageTracker);
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.messageParser = messageParser;
|
||||
this.sessionEncoder = sessionEncoder;
|
||||
this.sessionParser = sessionParser;
|
||||
this.introducerEngine = introducerEngine;
|
||||
this.introduceeEngine = introduceeEngine;
|
||||
this.crypto = crypto;
|
||||
this.identityManager = identityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
// Create a local group to store protocol sessions
|
||||
Group localGroup = getLocalGroup();
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set up groups for communication with any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
// TODO adapt to use upcoming ClientVersioning client
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
// Return if we've already set things up for this contact
|
||||
if (db.containsGroup(txn, g.getId())) return;
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(txn, g);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
||||
try {
|
||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Remove the contact group (all messages will be removed with it)
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
// TODO abort other sessions the contact is involved in
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory
|
||||
.createContactGroup(CLIENT_ID, CLIENT_VERSION, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// Look up the session, if there is one
|
||||
SessionId sessionId = meta.getSessionId();
|
||||
IntroduceeSession newIntroduceeSession = null;
|
||||
if (sessionId == null) {
|
||||
if (meta.getMessageType() != REQUEST) throw new AssertionError();
|
||||
newIntroduceeSession = createNewIntroduceeSession(txn, m, body);
|
||||
sessionId = newIntroduceeSession.getSessionId();
|
||||
}
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
// Handle the message
|
||||
Session session;
|
||||
MessageId storageId;
|
||||
if (ss == null) {
|
||||
if (meta.getMessageType() != REQUEST) throw new FormatException();
|
||||
if (newIntroduceeSession == null) throw new AssertionError();
|
||||
storageId = createStorageId(txn);
|
||||
session = handleMessage(txn, m, body, meta.getMessageType(),
|
||||
newIntroduceeSession, introduceeEngine);
|
||||
} else {
|
||||
storageId = ss.storageId;
|
||||
Role role = sessionParser.getRole(ss.bdfSession);
|
||||
if (role == INTRODUCER) {
|
||||
session = handleMessage(txn, m, body, meta.getMessageType(),
|
||||
sessionParser.parseIntroducerSession(ss.bdfSession),
|
||||
introducerEngine);
|
||||
} else if (role == INTRODUCEE) {
|
||||
session = handleMessage(txn, m, body, meta.getMessageType(),
|
||||
sessionParser.parseIntroduceeSession(m.getGroupId(),
|
||||
ss.bdfSession), introduceeEngine);
|
||||
} else throw new AssertionError();
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
return false;
|
||||
}
|
||||
|
||||
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
|
||||
Message m, BdfList body) throws DbException, FormatException {
|
||||
ContactId introducerId = getContactId(txn, m.getGroupId());
|
||||
Author introducer = db.getContact(txn, introducerId).getAuthor();
|
||||
Author alice = identityManager.getLocalAuthor(txn);
|
||||
Author bob = messageParser.parseRequestMessage(m, body).getAuthor();
|
||||
SessionId sessionId = crypto.getSessionId(introducer, alice, bob);
|
||||
return IntroduceeSession
|
||||
.getInitial(m.getGroupId(), sessionId, introducer, bob);
|
||||
}
|
||||
|
||||
private <S extends Session> S handleMessage(Transaction txn, Message m,
|
||||
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
|
||||
throws DbException, FormatException {
|
||||
if (type == REQUEST) {
|
||||
RequestMessage request = messageParser.parseRequestMessage(m, body);
|
||||
return engine.onRequestMessage(txn, session, request);
|
||||
} else if (type == ACCEPT) {
|
||||
AcceptMessage accept = messageParser.parseAcceptMessage(m, body);
|
||||
return engine.onAcceptMessage(txn, session, accept);
|
||||
} else if (type == DECLINE) {
|
||||
DeclineMessage decline = messageParser.parseDeclineMessage(m, body);
|
||||
return engine.onDeclineMessage(txn, session, decline);
|
||||
} else if (type == AUTH) {
|
||||
AuthMessage auth = messageParser.parseAuthMessage(m, body);
|
||||
return engine.onAuthMessage(txn, session, auth);
|
||||
} else if (type == ACTIVATE) {
|
||||
ActivateMessage activate =
|
||||
messageParser.parseActivateMessage(m, body);
|
||||
return engine.onActivateMessage(txn, session, activate);
|
||||
} else if (type == ABORT) {
|
||||
AbortMessage abort = messageParser.parseAbortMessage(m, body);
|
||||
return engine.onAbortMessage(txn, session, abort);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private StoredSession getSession(Transaction txn,
|
||||
@Nullable SessionId sessionId) throws DbException, FormatException {
|
||||
if (sessionId == null) return null;
|
||||
BdfDictionary query = sessionParser.getSessionQuery(sessionId);
|
||||
Map<MessageId, BdfDictionary> results = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
|
||||
query);
|
||||
if (results.size() > 1) throw new DbException();
|
||||
if (results.isEmpty()) return null;
|
||||
return new StoredSession(results.keySet().iterator().next(),
|
||||
results.values().iterator().next());
|
||||
}
|
||||
|
||||
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException, FormatException {
|
||||
BdfDictionary meta =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
|
||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
||||
}
|
||||
|
||||
private MessageId createStorageId(Transaction txn) throws DbException {
|
||||
Message m = clientHelper
|
||||
.createMessageForStoringMetadata(getLocalGroup().getId());
|
||||
db.addLocalMessage(txn, m, new Metadata(), false);
|
||||
return m.getId();
|
||||
}
|
||||
|
||||
private void storeSession(Transaction txn, MessageId storageId,
|
||||
Session session) throws DbException, FormatException {
|
||||
BdfDictionary d;
|
||||
if (session.getRole() == INTRODUCER) {
|
||||
d = sessionEncoder
|
||||
.encodeIntroducerSession((IntroducerSession) session);
|
||||
} else if (session.getRole() == INTRODUCEE) {
|
||||
d = sessionEncoder
|
||||
.encodeIntroduceeSession((IntroduceeSession) session);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
clientHelper.mergeMessageMetadata(txn, storageId, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
|
||||
long timestamp) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// Look up the session, if there is one
|
||||
Author introducer = identityManager.getLocalAuthor(txn);
|
||||
SessionId sessionId =
|
||||
crypto.getSessionId(introducer, c1.getAuthor(),
|
||||
c2.getAuthor());
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
// Create or parse the session
|
||||
IntroducerSession session;
|
||||
MessageId storageId;
|
||||
if (ss == null) {
|
||||
// This is the first request - create a new session
|
||||
GroupId groupId1 = getContactGroup(c1).getId();
|
||||
GroupId groupId2 = getContactGroup(c2).getId();
|
||||
session = new IntroducerSession(sessionId, groupId1,
|
||||
c1.getAuthor(), groupId2, c2.getAuthor());
|
||||
storageId = createStorageId(txn);
|
||||
} else {
|
||||
// An earlier request exists, so we already have a session
|
||||
session = sessionParser.parseIntroducerSession(ss.bdfSession);
|
||||
storageId = ss.storageId;
|
||||
}
|
||||
// Handle the request action
|
||||
session = introducerEngine
|
||||
.onRequestAction(txn, session, msg, timestamp);
|
||||
// Store the updated session
|
||||
storeSession(txn, storageId, session);
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException {
|
||||
respondToRequest(contactId, sessionId, timestamp, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declineIntroduction(ContactId contactId, SessionId sessionId,
|
||||
long timestamp) throws DbException {
|
||||
respondToRequest(contactId, sessionId, timestamp, false);
|
||||
}
|
||||
|
||||
private void respondToRequest(ContactId contactId, SessionId sessionId,
|
||||
long timestamp, boolean accept) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// Look up the session
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
if (ss == null) throw new IllegalArgumentException();
|
||||
// Parse the session
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
GroupId contactGroupId = getContactGroup(contact).getId();
|
||||
IntroduceeSession session = sessionParser
|
||||
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
|
||||
// Handle the join or leave action
|
||||
if (accept) {
|
||||
session = introduceeEngine
|
||||
.onAcceptAction(txn, session, timestamp);
|
||||
} else {
|
||||
session = introduceeEngine
|
||||
.onDeclineAction(txn, session, timestamp);
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, ss.storageId, session);
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<IntroductionMessage> getIntroductionMessages(ContactId c)
|
||||
throws DbException {
|
||||
List<IntroductionMessage> messages;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
Contact contact = db.getContact(txn, c);
|
||||
GroupId contactGroupId = getContactGroup(contact).getId();
|
||||
BdfDictionary query = messageParser.getMessagesVisibleInUiQuery();
|
||||
Map<MessageId, BdfDictionary> results = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
||||
messages = new ArrayList<>(results.size());
|
||||
for (Map.Entry<MessageId, BdfDictionary> e : results.entrySet()) {
|
||||
MessageId m = e.getKey();
|
||||
MessageMetadata meta =
|
||||
messageParser.parseMetadata(e.getValue());
|
||||
MessageStatus status = db.getMessageStatus(txn, c, m);
|
||||
StoredSession ss = getSession(txn, meta.getSessionId());
|
||||
if (ss == null) throw new AssertionError();
|
||||
MessageType type = meta.getMessageType();
|
||||
if (type == REQUEST) {
|
||||
messages.add(
|
||||
parseInvitationRequest(txn, contactGroupId, m,
|
||||
meta, status, ss.bdfSession));
|
||||
} else if (type == ACCEPT) {
|
||||
messages.add(
|
||||
parseInvitationResponse(txn, contactGroupId, m,
|
||||
meta, status, ss.bdfSession, true));
|
||||
} else if (type == DECLINE) {
|
||||
messages.add(
|
||||
parseInvitationResponse(txn, contactGroupId, m,
|
||||
meta, status, ss.bdfSession, false));
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
private IntroductionRequest parseInvitationRequest(Transaction txn,
|
||||
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
||||
MessageStatus status, BdfDictionary bdfSession)
|
||||
throws DbException, FormatException {
|
||||
Role role = sessionParser.getRole(bdfSession);
|
||||
SessionId sessionId;
|
||||
Author author;
|
||||
if (role == INTRODUCER) {
|
||||
IntroducerSession session =
|
||||
sessionParser.parseIntroducerSession(bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
if (localAuthor.equals(session.getIntroducee1().author)) {
|
||||
author = session.getIntroducee2().author;
|
||||
} else {
|
||||
author = session.getIntroducee1().author;
|
||||
}
|
||||
} else if (role == INTRODUCEE) {
|
||||
IntroduceeSession session = sessionParser
|
||||
.parseIntroduceeSession(contactGroupId, bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
author = session.getRemoteAuthor();
|
||||
} else throw new AssertionError();
|
||||
String message = ""; // TODO
|
||||
boolean contactExists = false; // TODO
|
||||
|
||||
return new IntroductionRequest(sessionId, m, contactGroupId,
|
||||
role, meta.getTimestamp(), meta.isLocal(),
|
||||
status.isSent(), status.isSeen(), meta.isRead(),
|
||||
author.getName(), false, message, !meta.isAvailableToAnswer(),
|
||||
contactExists);
|
||||
}
|
||||
|
||||
private IntroductionResponse parseInvitationResponse(Transaction txn,
|
||||
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
||||
MessageStatus status, BdfDictionary bdfSession, boolean accept)
|
||||
throws FormatException, DbException {
|
||||
Role role = sessionParser.getRole(bdfSession);
|
||||
SessionId sessionId;
|
||||
Author author;
|
||||
if (role == INTRODUCER) {
|
||||
IntroducerSession session =
|
||||
sessionParser.parseIntroducerSession(bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
if (localAuthor.equals(session.getIntroducee1().author)) {
|
||||
author = session.getIntroducee2().author;
|
||||
} else {
|
||||
author = session.getIntroducee1().author;
|
||||
}
|
||||
} else if (role == INTRODUCEE) {
|
||||
IntroduceeSession session = sessionParser
|
||||
.parseIntroduceeSession(contactGroupId, bdfSession);
|
||||
sessionId = session.getSessionId();
|
||||
author = session.getRemoteAuthor();
|
||||
} else throw new AssertionError();
|
||||
return new IntroductionResponse(sessionId, m, contactGroupId,
|
||||
role, meta.getTimestamp(), meta.isLocal(), status.isSent(),
|
||||
status.isSeen(), meta.isRead(), author.getName(), accept);
|
||||
}
|
||||
|
||||
private Group getLocalGroup() {
|
||||
return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
|
||||
}
|
||||
|
||||
private static class StoredSession {
|
||||
|
||||
private final MessageId storageId;
|
||||
private final BdfDictionary bdfSession;
|
||||
|
||||
private StoredSession(MessageId storageId, BdfDictionary bdfSession) {
|
||||
this.storageId = storageId;
|
||||
this.bdfSession = bdfSession;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
|
||||
@Module
|
||||
public class IntroductionModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
IntroductionValidator introductionValidator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
IntroductionValidator provideValidator(ValidationManager validationManager,
|
||||
MessageEncoder messageEncoder, MetadataEncoder metadataEncoder,
|
||||
ClientHelper clientHelper, Clock clock) {
|
||||
|
||||
IntroductionValidator introductionValidator =
|
||||
new IntroductionValidator(messageEncoder, clientHelper,
|
||||
metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID,
|
||||
introductionValidator);
|
||||
|
||||
return introductionValidator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
|
||||
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.BdfMessageValidator;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.AUTH;
|
||||
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
private final MessageEncoder messageEncoder;
|
||||
|
||||
IntroductionValidator(MessageEncoder messageEncoder,
|
||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||
Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
this.messageEncoder = messageEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||
|
||||
switch (type) {
|
||||
case REQUEST:
|
||||
return validateRequestMessage(m, body);
|
||||
case ACCEPT:
|
||||
return validateAcceptMessage(m, body);
|
||||
case AUTH:
|
||||
return validateAuthMessage(m, body);
|
||||
case DECLINE:
|
||||
case ACTIVATE:
|
||||
case ABORT:
|
||||
return validateOtherMessage(type, m, body);
|
||||
default:
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
private BdfMessageContext validateRequestMessage(Message m, BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 4);
|
||||
|
||||
byte[] previousMessageId = body.getOptionalRaw(1);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
BdfList authorList = body.getList(2);
|
||||
clientHelper.parseAndValidateAuthor(authorList);
|
||||
|
||||
String msg = body.getOptionalString(3);
|
||||
checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
|
||||
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeRequestMetadata(m.getTimestamp(), false, false,
|
||||
false, false);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 6);
|
||||
|
||||
byte[] sessionIdBytes = body.getRaw(1);
|
||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||
|
||||
byte[] previousMessageId = body.getRaw(2);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
byte[] ephemeralPublicKey = body.getRaw(3);
|
||||
checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
|
||||
|
||||
body.getLong(4);
|
||||
|
||||
BdfDictionary transportProperties = body.getDictionary(5);
|
||||
if (transportProperties.size() < 1) throw new FormatException();
|
||||
for (String tId : transportProperties.keySet()) {
|
||||
checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = transportProperties.getDictionary(tId);
|
||||
clientHelper.parseAndValidateTransportProperties(tProps);
|
||||
}
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false,
|
||||
false, false);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
|
||||
private BdfMessageContext validateAuthMessage(Message m, BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 5);
|
||||
|
||||
byte[] sessionIdBytes = body.getRaw(1);
|
||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||
|
||||
byte[] previousMessageId = body.getRaw(2);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
byte[] mac = body.getRaw(3);
|
||||
checkLength(mac, MAC_BYTES);
|
||||
|
||||
byte[] signature = body.getRaw(4);
|
||||
checkLength(signature, 1, MAX_SIGNATURE_BYTES);
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false,
|
||||
false);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
|
||||
private BdfMessageContext validateOtherMessage(MessageType type,
|
||||
Message m, BdfList body) throws FormatException {
|
||||
checkSize(body, 3);
|
||||
|
||||
byte[] sessionIdBytes = body.getRaw(1);
|
||||
checkLength(sessionIdBytes, UniqueId.LENGTH);
|
||||
|
||||
byte[] previousMessageId = body.getRaw(2);
|
||||
checkLength(previousMessageId, UniqueId.LENGTH);
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder
|
||||
.encodeMetadata(type, sessionId, m.getTimestamp(), false, false,
|
||||
false);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta,
|
||||
Collections.singletonList(dependency));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
|
||||
interface State {
|
||||
|
||||
int getValue();
|
||||
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
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.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
|
||||
import org.briarproject.briar.test.BriarTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.hamcrest.Matchers.array;
|
||||
import static org.hamcrest.Matchers.samePropertyValuesAs;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IntroduceeManagerTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final IntroduceeManager introduceeManager;
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent cryptoComponent;
|
||||
private final ClientHelper clientHelper;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final ContactManager contactManager;
|
||||
private final Clock clock;
|
||||
private final Contact introducer;
|
||||
private final Contact introducee1;
|
||||
private final Contact introducee2;
|
||||
private final Group localGroup1;
|
||||
private final Group introductionGroup1;
|
||||
private final Transaction txn;
|
||||
private final long time = 42L;
|
||||
private final Message localStateMessage;
|
||||
private final SessionId sessionId;
|
||||
private final Message message1;
|
||||
|
||||
public IntroduceeManagerTest() {
|
||||
context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
MessageSender messageSender = context.mock(MessageSender.class);
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
cryptoComponent = context.mock(CryptoComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
clock = context.mock(Clock.class);
|
||||
introductionGroupFactory =
|
||||
context.mock(IntroductionGroupFactory.class);
|
||||
TransportPropertyManager transportPropertyManager =
|
||||
context.mock(TransportPropertyManager.class);
|
||||
authorFactory = context.mock(AuthorFactory.class);
|
||||
contactManager = context.mock(ContactManager.class);
|
||||
IdentityManager identityManager = context.mock(IdentityManager.class);
|
||||
|
||||
introduceeManager = new IntroduceeManager(messageSender, db,
|
||||
clientHelper, clock, cryptoComponent, transportPropertyManager,
|
||||
authorFactory, contactManager, identityManager,
|
||||
introductionGroupFactory);
|
||||
|
||||
Author author0 = getAuthor();
|
||||
AuthorId localAuthorId = new AuthorId(getRandomId());
|
||||
ContactId contactId0 = new ContactId(234);
|
||||
introducer =
|
||||
new Contact(contactId0, author0, localAuthorId, true, true);
|
||||
|
||||
Author author1 = getAuthor();
|
||||
AuthorId localAuthorId1 = new AuthorId(getRandomId());
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
introducee1 =
|
||||
new Contact(contactId1, author1, localAuthorId1, true, true);
|
||||
|
||||
Author author2 = getAuthor();
|
||||
ContactId contactId2 = new ContactId(235);
|
||||
introducee2 =
|
||||
new Contact(contactId2, author2, localAuthorId, true, true);
|
||||
|
||||
localGroup1 = getGroup(CLIENT_ID);
|
||||
introductionGroup1 = getGroup(CLIENT_ID);
|
||||
|
||||
sessionId = new SessionId(getRandomId());
|
||||
localStateMessage = new Message(
|
||||
new MessageId(getRandomId()),
|
||||
localGroup1.getId(),
|
||||
time,
|
||||
getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
|
||||
);
|
||||
message1 = new Message(
|
||||
new MessageId(getRandomId()),
|
||||
introductionGroup1.getId(),
|
||||
time,
|
||||
getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
|
||||
);
|
||||
|
||||
txn = new Transaction(null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingRequestMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_REQUEST);
|
||||
msg.put(GROUP_ID, introductionGroup1.getId());
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(MESSAGE_ID, message1.getId());
|
||||
msg.put(MESSAGE_TIME, time);
|
||||
msg.put(NAME, introducee2.getAuthor().getName());
|
||||
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
|
||||
BdfDictionary state =
|
||||
initializeSessionState(txn, introductionGroup1.getId(), msg);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn,
|
||||
localStateMessage.getId(), state);
|
||||
}});
|
||||
|
||||
introduceeManager.incomingMessage(txn, state, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingResponseMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, introductionGroup1.getId());
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(MESSAGE_ID, message1.getId());
|
||||
msg.put(MESSAGE_TIME, time);
|
||||
msg.put(NAME, introducee2.getAuthor().getName());
|
||||
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
|
||||
BdfDictionary state =
|
||||
initializeSessionState(txn, introductionGroup1.getId(), msg);
|
||||
state.put(STATE, IntroduceeProtocolState.AWAIT_RESPONSES.ordinal());
|
||||
|
||||
// turn request message into a response
|
||||
msg.put(ACCEPT, true);
|
||||
msg.put(TIME, time);
|
||||
msg.put(E_PUBLIC_KEY, getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES));
|
||||
msg.put(TRANSPORT, new BdfDictionary());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn,
|
||||
localStateMessage.getId(), state);
|
||||
}});
|
||||
|
||||
introduceeManager.incomingMessage(txn, state, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectReplacedEphemeralPublicKey()
|
||||
throws DbException, FormatException, GeneralSecurityException {
|
||||
|
||||
// TODO MR !237 should use its new default initialization method here
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, introductionGroup1.getId());
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(MESSAGE_ID, message1.getId());
|
||||
msg.put(MESSAGE_TIME, time);
|
||||
msg.put(NAME, introducee2.getAuthor().getName());
|
||||
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
BdfDictionary state =
|
||||
initializeSessionState(txn, introductionGroup1.getId(), msg);
|
||||
|
||||
// prepare state for incoming ACK
|
||||
state.put(STATE, IntroduceeProtocolState.AWAIT_ACK.ordinal());
|
||||
state.put(ADDED_CONTACT_ID, 2);
|
||||
byte[] nonce = getRandomBytes(42);
|
||||
state.put(NONCE, nonce);
|
||||
state.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
|
||||
// create incoming ACK message
|
||||
byte[] mac = getRandomBytes(MAC_LENGTH);
|
||||
byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
BdfDictionary ack = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(SESSION_ID, sessionId),
|
||||
new BdfEntry(GROUP_ID, introductionGroup1.getId()),
|
||||
new BdfEntry(MAC, mac),
|
||||
new BdfEntry(SIGNATURE, sig)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verifySignature(sig, SIGNING_LABEL, nonce,
|
||||
introducee2.getAuthor().getPublicKey());
|
||||
will(returnValue(false));
|
||||
}});
|
||||
|
||||
try {
|
||||
introduceeManager.incomingMessage(txn, state, ack);
|
||||
fail();
|
||||
} catch (DbException e) {
|
||||
// expected
|
||||
assertTrue(e.getCause() instanceof GeneralSecurityException);
|
||||
}
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureVerification()
|
||||
throws FormatException, DbException, GeneralSecurityException {
|
||||
|
||||
byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
|
||||
byte[] nonce = getRandomBytes(MAC_LENGTH);
|
||||
byte[] sig = getRandomBytes(MAC_LENGTH);
|
||||
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(PUBLIC_KEY, publicKeyBytes);
|
||||
state.put(NONCE, nonce);
|
||||
state.put(SIGNATURE, sig);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verifySignature(sig, SIGNING_LABEL, nonce,
|
||||
publicKeyBytes);
|
||||
will(returnValue(true));
|
||||
}});
|
||||
introduceeManager.verifySignature(state);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMacVerification()
|
||||
throws FormatException, DbException, GeneralSecurityException {
|
||||
|
||||
byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
|
||||
BdfDictionary tp = BdfDictionary.of(new BdfEntry("fake", "fake"));
|
||||
byte[] ePublicKeyBytes = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
byte[] mac = getRandomBytes(MAC_LENGTH);
|
||||
SecretKey macKey = getSecretKey();
|
||||
|
||||
// move state to where it would be after an ACK arrived
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(PUBLIC_KEY, publicKeyBytes);
|
||||
state.put(TRANSPORT, tp);
|
||||
state.put(TIME, time);
|
||||
state.put(E_PUBLIC_KEY, ePublicKeyBytes);
|
||||
state.put(MAC, mac);
|
||||
state.put(MAC_KEY, macKey.getBytes());
|
||||
|
||||
byte[] signBytes = getRandomBytes(42);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toByteArray(
|
||||
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
|
||||
will(returnValue(signBytes));
|
||||
//noinspection unchecked
|
||||
oneOf(cryptoComponent).mac(with(MAC_LABEL),
|
||||
with(samePropertyValuesAs(macKey)),
|
||||
with(array(equal(signBytes))));
|
||||
will(returnValue(mac));
|
||||
}});
|
||||
introduceeManager.verifyMac(state);
|
||||
context.assertIsSatisfied();
|
||||
|
||||
// now produce wrong MAC
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toByteArray(
|
||||
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
|
||||
will(returnValue(signBytes));
|
||||
//noinspection unchecked
|
||||
oneOf(cryptoComponent).mac(with(MAC_LABEL),
|
||||
with(samePropertyValuesAs(macKey)),
|
||||
with(array(equal(signBytes))));
|
||||
will(returnValue(getRandomBytes(MAC_LENGTH)));
|
||||
}});
|
||||
try {
|
||||
introduceeManager.verifyMac(state);
|
||||
fail();
|
||||
} catch (GeneralSecurityException e) {
|
||||
// expected
|
||||
}
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private BdfDictionary initializeSessionState(Transaction txn,
|
||||
GroupId groupId, BdfDictionary msg)
|
||||
throws DbException, FormatException {
|
||||
|
||||
SecureRandom secureRandom = context.mock(SecureRandom.class);
|
||||
Bytes salt = new Bytes(new byte[64]);
|
||||
BdfDictionary groupMetadata = BdfDictionary.of(
|
||||
new BdfEntry(CONTACT, introducee1.getId().getInt())
|
||||
);
|
||||
boolean contactExists = false;
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(STORAGE_ID, localStateMessage.getId());
|
||||
state.put(STATE, AWAIT_REQUEST.getValue());
|
||||
state.put(ROLE, ROLE_INTRODUCEE);
|
||||
state.put(GROUP_ID, groupId);
|
||||
state.put(INTRODUCER, introducer.getAuthor().getName());
|
||||
state.put(CONTACT_ID_1, introducer.getId().getInt());
|
||||
state.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes());
|
||||
state.put(NOT_OUR_RESPONSE, localStateMessage.getId());
|
||||
state.put(ANSWERED, false);
|
||||
state.put(EXISTS, contactExists);
|
||||
state.put(REMOTE_AUTHOR_ID, introducee2.getAuthor().getId());
|
||||
state.put(REMOTE_AUTHOR_IS_US, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
oneOf(cryptoComponent).getSecureRandom();
|
||||
will(returnValue(secureRandom));
|
||||
oneOf(secureRandom).nextBytes(salt.getBytes());
|
||||
oneOf(introductionGroupFactory).createLocalGroup();
|
||||
will(returnValue(localGroup1));
|
||||
oneOf(clientHelper)
|
||||
.createMessage(localGroup1.getId(), time, BdfList.of(salt));
|
||||
will(returnValue(localStateMessage));
|
||||
|
||||
// who is making the introduction? who is the introducer?
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
oneOf(db).getContact(txn, introducer.getId());
|
||||
will(returnValue(introducer));
|
||||
|
||||
// create remote author to check if contact exists
|
||||
oneOf(authorFactory).createAuthor(introducee2.getAuthor().getName(),
|
||||
introducee2.getAuthor().getPublicKey());
|
||||
will(returnValue(introducee2.getAuthor()));
|
||||
oneOf(contactManager)
|
||||
.contactExists(txn, introducee2.getAuthor().getId(),
|
||||
introducer.getLocalAuthorId());
|
||||
will(returnValue(contactExists));
|
||||
|
||||
// checks if remote author is one of our identities
|
||||
oneOf(db).containsLocalAuthor(txn, introducee2.getAuthor().getId());
|
||||
will(returnValue(false));
|
||||
|
||||
// store session state
|
||||
oneOf(clientHelper)
|
||||
.addLocalMessage(txn, localStateMessage, state, false);
|
||||
}});
|
||||
|
||||
BdfDictionary result = introduceeManager.initialize(txn, groupId, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.test.BriarTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IntroducerManagerTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final IntroducerManager introducerManager;
|
||||
private final CryptoComponent cryptoComponent;
|
||||
private final ClientHelper clientHelper;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final MessageSender messageSender;
|
||||
private final Clock clock;
|
||||
private final Contact introducee1;
|
||||
private final Contact introducee2;
|
||||
private final Group localGroup0;
|
||||
private final Group introductionGroup1;
|
||||
private final Group introductionGroup2;
|
||||
|
||||
public IntroducerManagerTest() {
|
||||
context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
messageSender = context.mock(MessageSender.class);
|
||||
cryptoComponent = context.mock(CryptoComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
clock = context.mock(Clock.class);
|
||||
introductionGroupFactory =
|
||||
context.mock(IntroductionGroupFactory.class);
|
||||
|
||||
introducerManager =
|
||||
new IntroducerManager(messageSender, clientHelper, clock,
|
||||
cryptoComponent, introductionGroupFactory);
|
||||
|
||||
Author author1 = getAuthor();
|
||||
AuthorId localAuthorId1 = new AuthorId(getRandomId());
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
introducee1 =
|
||||
new Contact(contactId1, author1, localAuthorId1, true, true);
|
||||
|
||||
Author author2 = getAuthor();
|
||||
AuthorId localAuthorId2 = new AuthorId(getRandomId());
|
||||
ContactId contactId2 = new ContactId(235);
|
||||
introducee2 =
|
||||
new Contact(contactId2, author2, localAuthorId2, true, true);
|
||||
|
||||
localGroup0 = getGroup(CLIENT_ID);
|
||||
introductionGroup1 = getGroup(CLIENT_ID);
|
||||
introductionGroup2 = getGroup(CLIENT_ID);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMakeIntroduction() throws DbException, FormatException {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
long time = 42L;
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
SecureRandom secureRandom = context.mock(SecureRandom.class);
|
||||
Bytes salt = new Bytes(new byte[64]);
|
||||
Message msg = new Message(new MessageId(getRandomId()),
|
||||
localGroup0.getId(), time, getRandomBytes(64));
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(SESSION_ID, msg.getId());
|
||||
state.put(STORAGE_ID, msg.getId());
|
||||
state.put(STATE, PREPARE_REQUESTS.getValue());
|
||||
state.put(ROLE, ROLE_INTRODUCER);
|
||||
state.put(GROUP_ID_1, introductionGroup1.getId());
|
||||
state.put(GROUP_ID_2, introductionGroup2.getId());
|
||||
state.put(CONTACT_1, introducee1.getAuthor().getName());
|
||||
state.put(CONTACT_2, introducee2.getAuthor().getName());
|
||||
state.put(CONTACT_ID_1, introducee1.getId().getInt());
|
||||
state.put(CONTACT_ID_2, introducee2.getId().getInt());
|
||||
state.put(AUTHOR_ID_1, introducee1.getAuthor().getId());
|
||||
state.put(AUTHOR_ID_2, introducee2.getAuthor().getId());
|
||||
BdfDictionary state2 = (BdfDictionary) state.clone();
|
||||
state2.put(STATE, AWAIT_RESPONSES.getValue());
|
||||
|
||||
BdfDictionary msg1 = new BdfDictionary();
|
||||
msg1.put(TYPE, TYPE_REQUEST);
|
||||
msg1.put(SESSION_ID, state.getRaw(SESSION_ID));
|
||||
msg1.put(GROUP_ID, state.getRaw(GROUP_ID_1));
|
||||
msg1.put(NAME, state.getString(CONTACT_2));
|
||||
msg1.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
BdfDictionary msg1send = (BdfDictionary) msg1.clone();
|
||||
msg1send.put(MESSAGE_TIME, time);
|
||||
|
||||
BdfDictionary msg2 = new BdfDictionary();
|
||||
msg2.put(TYPE, TYPE_REQUEST);
|
||||
msg2.put(SESSION_ID, state.getRaw(SESSION_ID));
|
||||
msg2.put(GROUP_ID, state.getRaw(GROUP_ID_2));
|
||||
msg2.put(NAME, state.getString(CONTACT_1));
|
||||
msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey());
|
||||
BdfDictionary msg2send = (BdfDictionary) msg2.clone();
|
||||
msg2send.put(MESSAGE_TIME, time);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// initialize and store session state
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
oneOf(cryptoComponent).getSecureRandom();
|
||||
will(returnValue(secureRandom));
|
||||
oneOf(secureRandom).nextBytes(salt.getBytes());
|
||||
oneOf(introductionGroupFactory).createLocalGroup();
|
||||
will(returnValue(localGroup0));
|
||||
oneOf(clientHelper).createMessage(localGroup0.getId(), time,
|
||||
BdfList.of(salt));
|
||||
will(returnValue(msg));
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee2);
|
||||
will(returnValue(introductionGroup2));
|
||||
oneOf(clientHelper).addLocalMessage(txn, msg, state, false);
|
||||
|
||||
// send message
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2);
|
||||
oneOf(messageSender).sendMessage(txn, msg1send);
|
||||
oneOf(messageSender).sendMessage(txn, msg2send);
|
||||
}});
|
||||
|
||||
introducerManager
|
||||
.makeIntroduction(txn, introducee1, introducee2, null, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -11,7 +11,7 @@ import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IntroductionCryptoTest extends BrambleMockTestCase {
|
||||
@@ -6,23 +6,21 @@ import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
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.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.TestDatabaseModule;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
@@ -38,56 +36,35 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IntroductionIntegrationTest
|
||||
extends BriarIntegrationTest<IntroductionIntegrationTestComponent> {
|
||||
|
||||
@Inject
|
||||
IntroductionGroupFactory introductionGroupFactory;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile IntroductionManager introductionManager0;
|
||||
private volatile IntroductionManager introductionManager1;
|
||||
@@ -102,7 +79,7 @@ public class IntroductionIntegrationTest
|
||||
Logger.getLogger(IntroductionIntegrationTest.class.getName());
|
||||
|
||||
interface StateVisitor {
|
||||
boolean visit(BdfDictionary response);
|
||||
AcceptMessage visit(AcceptMessage response);
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -151,50 +128,50 @@ public class IntroductionIntegrationTest
|
||||
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
||||
|
||||
// check that messages are tracked properly
|
||||
Group g1 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee1);
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee2);
|
||||
assertGroupCount(messageTracker0, g1.getId(), 1, 0, time);
|
||||
assertGroupCount(messageTracker0, g2.getId(), 1, 0, time);
|
||||
Group g1 = introductionManager0.getContactGroup(introducee1);
|
||||
Group g2 = introductionManager0.getContactGroup(introducee2);
|
||||
assertGroupCount(messageTracker0, g1.getId(), 1, 0);
|
||||
assertGroupCount(messageTracker0, g2.getId(), 1, 0);
|
||||
|
||||
// sync first request message
|
||||
// sync first REQUEST message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
|
||||
|
||||
// sync second request message
|
||||
// sync second REQUEST message
|
||||
sync0To2(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener2.requestReceived);
|
||||
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
|
||||
|
||||
// sync first response
|
||||
// sync first ACCEPT message
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
|
||||
|
||||
// sync second response
|
||||
// sync second ACCEPT message
|
||||
sync2To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
|
||||
|
||||
// sync forwarded responses to introducees
|
||||
// sync forwarded ACCEPT messages to introducees
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
|
||||
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
|
||||
|
||||
// sync first ACK and its forward
|
||||
// sync first AUTH and its forward
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// sync second ACK and its forward
|
||||
sync2To0(1, true);
|
||||
sync0To1(1, true);
|
||||
// sync second AUTH and its forward as well as the following ACTIVATE
|
||||
sync2To0(2, true);
|
||||
sync0To1(2, true);
|
||||
|
||||
// sync first ACTIVATE and its forward
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// wait for introduction to succeed
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
@@ -269,10 +246,8 @@ public class IntroductionIntegrationTest
|
||||
assertFalse(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
|
||||
Group g1 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee1);
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee2);
|
||||
Group g1 = introductionManager0.getContactGroup(introducee1);
|
||||
Group g2 = introductionManager0.getContactGroup(introducee2);
|
||||
assertEquals(2,
|
||||
introductionManager0.getIntroductionMessages(contactId1From0)
|
||||
.size());
|
||||
@@ -290,6 +265,10 @@ public class IntroductionIntegrationTest
|
||||
introductionManager2.getIntroductionMessages(contactId0From2)
|
||||
.size());
|
||||
assertGroupCount(messageTracker2, g2.getId(), 3, 2);
|
||||
|
||||
assertFalse(listener0.aborted);
|
||||
assertFalse(listener1.aborted);
|
||||
assertFalse(listener2.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -342,6 +321,9 @@ public class IntroductionIntegrationTest
|
||||
assertEquals(2,
|
||||
introductionManager2.getIntroductionMessages(contactId0From2)
|
||||
.size());
|
||||
assertFalse(listener0.aborted);
|
||||
assertFalse(listener1.aborted);
|
||||
assertFalse(listener2.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -393,6 +375,9 @@ public class IntroductionIntegrationTest
|
||||
// since introducee2 was already in FINISHED state when
|
||||
// introducee1's response arrived, she ignores and deletes it
|
||||
assertDefaultUiMessages();
|
||||
assertFalse(listener0.aborted);
|
||||
assertFalse(listener1.aborted);
|
||||
assertFalse(listener2.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -432,6 +417,8 @@ public class IntroductionIntegrationTest
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
assertFalse(listener0.aborted);
|
||||
assertFalse(listener1.aborted);
|
||||
assertFalse(listener2.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -452,61 +439,11 @@ public class IntroductionIntegrationTest
|
||||
// make really sure we don't have that request
|
||||
assertTrue(introductionManager1.getIntroductionMessages(contactId0From1)
|
||||
.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionIdReuse() throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get SessionId
|
||||
List<IntroductionMessage> list = new ArrayList<>(
|
||||
introductionManager1.getIntroductionMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
assertTrue(list.get(0) instanceof IntroductionRequest);
|
||||
IntroductionRequest msg = (IntroductionRequest) list.get(0);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
|
||||
// get contact group
|
||||
Group group =
|
||||
introductionGroupFactory.createIntroductionGroup(contact1From0);
|
||||
|
||||
// create new message with same SessionId
|
||||
BdfDictionary d = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_REQUEST),
|
||||
new BdfEntry(SESSION_ID, sessionId),
|
||||
new BdfEntry(GROUP_ID, group.getId()),
|
||||
new BdfEntry(NAME, getRandomString(42)),
|
||||
new BdfEntry(PUBLIC_KEY, getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
|
||||
);
|
||||
|
||||
// reset request received state
|
||||
listener1.requestReceived = false;
|
||||
|
||||
// add the message to the queue
|
||||
MessageSender sender0 = c0.getMessageSender();
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
sender0.sendMessage(txn, d);
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
|
||||
// actually send message
|
||||
sync0To1(1, false);
|
||||
|
||||
// make sure it does not arrive
|
||||
assertFalse(listener1.requestReceived);
|
||||
// The message was invalid, so no abort message was sent
|
||||
assertFalse(listener0.aborted);
|
||||
assertFalse(listener1.aborted);
|
||||
assertFalse(listener2.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -523,34 +460,20 @@ public class IntroductionIntegrationTest
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get database and local group for introducee
|
||||
Group group1 = introductionGroupFactory.createLocalGroup();
|
||||
// get local group for introducee1
|
||||
Group group1 =
|
||||
contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
|
||||
|
||||
// get local session state messages
|
||||
Map<MessageId, Metadata> map;
|
||||
Transaction txn = db1.startTransaction(false);
|
||||
try {
|
||||
map = db1.getMessageMetadata(txn, group1.getId());
|
||||
db1.commitTransaction(txn);
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
// check that we have one session state
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(1, c1.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(group1.getId()).size());
|
||||
|
||||
// introducee1 removes introducer
|
||||
contactManager1.removeContact(contactId0From1);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db1.startTransaction(false);
|
||||
try {
|
||||
map = db1.getMessageMetadata(txn, group1.getId());
|
||||
db1.commitTransaction(txn);
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
// make sure local state got deleted
|
||||
assertEquals(0, map.size());
|
||||
assertEquals(0, c1.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(group1.getId()).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -567,48 +490,36 @@ public class IntroductionIntegrationTest
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get database and local group for introducee
|
||||
Group group1 = introductionGroupFactory.createLocalGroup();
|
||||
// get local group for introducer
|
||||
Group group0 =
|
||||
contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
|
||||
|
||||
// get local session state messages
|
||||
Map<MessageId, Metadata> map;
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// check that we have one session state
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(1, c0.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(group0.getId()).size());
|
||||
|
||||
// introducer removes introducee1
|
||||
contactManager0.removeContact(contactId1From0);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// make sure local state is still there
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(1, c0.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(group0.getId()).size());
|
||||
|
||||
// ensure introducer has aborted the session
|
||||
assertTrue(listener0.aborted);
|
||||
|
||||
// sync REQUEST and ABORT message
|
||||
sync0To2(2, true);
|
||||
|
||||
// ensure introducee2 has aborted the session as well
|
||||
assertTrue(listener2.aborted);
|
||||
|
||||
// introducer removes other introducee
|
||||
contactManager0.removeContact(contactId2From0);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// make sure local state is gone now
|
||||
assertEquals(0, map.size());
|
||||
assertEquals(0, c0.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(group0.getId()).size());
|
||||
}
|
||||
|
||||
private void testModifiedResponse(StateVisitor visitor)
|
||||
@@ -630,26 +541,36 @@ public class IntroductionIntegrationTest
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// get response to be forwarded
|
||||
ClientHelper ch = c0.getClientHelper(); // need 0's ClientHelper here
|
||||
Entry<MessageId, BdfDictionary> resp =
|
||||
getMessageFor(ch, contact2From0, TYPE_RESPONSE);
|
||||
MessageId responseId = resp.getKey();
|
||||
BdfDictionary response = resp.getValue();
|
||||
|
||||
// adapt outgoing message queue to removed message
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(contact2From0);
|
||||
decreaseOutgoingMessageCounter(ch, g2.getId());
|
||||
AcceptMessage message =
|
||||
(AcceptMessage) getMessageFor(c0.getClientHelper(),
|
||||
contact2From0, ACCEPT);
|
||||
|
||||
// allow visitor to modify response
|
||||
boolean earlyAbort = visitor.visit(response);
|
||||
AcceptMessage m = visitor.visit(message);
|
||||
|
||||
// replace original response with modified one
|
||||
MessageSender sender0 = c0.getMessageSender();
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
db0.deleteMessage(txn, responseId);
|
||||
sender0.sendMessage(txn, response);
|
||||
db0.removeMessage(txn, message.getMessageId());
|
||||
Message msg = c0.getMessageEncoder()
|
||||
.encodeAcceptMessage(m.getGroupId(), m.getTimestamp(),
|
||||
m.getPreviousMessageId(), m.getSessionId(),
|
||||
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
|
||||
m.getTransportProperties());
|
||||
c0.getClientHelper()
|
||||
.addLocalMessage(txn, msg, new BdfDictionary(), true);
|
||||
Group group0 = contactGroupFactory
|
||||
.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(SESSION_KEY_SESSION_ID, m.getSessionId())
|
||||
);
|
||||
Map.Entry<MessageId, BdfDictionary> session = c0.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(txn, group0.getId(), query)
|
||||
.entrySet().iterator().next();
|
||||
replacePreviousLocalMessageId(contact2From0.getAuthor(),
|
||||
session.getValue(), msg.getId());
|
||||
c0.getClientHelper().mergeMessageMetadata(txn, session.getKey(),
|
||||
session.getValue());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
@@ -663,21 +584,14 @@ public class IntroductionIntegrationTest
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// sync first ACK and forward it
|
||||
// sync first AUTH and forward it
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// introducee2 should have detected the fake now
|
||||
// and deleted introducee1 again
|
||||
Collection<Contact> contacts2;
|
||||
txn = db2.startTransaction(true);
|
||||
try {
|
||||
contacts2 = db2.getContacts(txn);
|
||||
db2.commitTransaction(txn);
|
||||
} finally {
|
||||
db2.endTransaction(txn);
|
||||
}
|
||||
assertEquals(1, contacts2.size());
|
||||
assertFalse(listener0.aborted);
|
||||
assertFalse(listener1.aborted);
|
||||
assertTrue(listener2.aborted);
|
||||
|
||||
// sync introducee2's ack and following abort
|
||||
sync2To0(2, true);
|
||||
@@ -687,144 +601,44 @@ public class IntroductionIntegrationTest
|
||||
|
||||
// sync abort messages to introducees
|
||||
sync0To1(2, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
if (earlyAbort) {
|
||||
assertTrue(listener1.aborted);
|
||||
assertTrue(listener2.aborted);
|
||||
} else {
|
||||
assertTrue(listener2.aborted);
|
||||
// when aborted late, introducee1 keeps the contact,
|
||||
// so introducer can not make contacts disappear by aborting
|
||||
Collection<Contact> contacts1;
|
||||
txn = db1.startTransaction(true);
|
||||
try {
|
||||
contacts1 = db1.getContacts(txn);
|
||||
db1.commitTransaction(txn);
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
assertEquals(2, contacts1.size());
|
||||
}
|
||||
// ensure everybody got the abort now
|
||||
assertTrue(listener0.aborted);
|
||||
assertTrue(listener1.aborted);
|
||||
assertTrue(listener2.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedTransportProperties() throws Exception {
|
||||
testModifiedResponse(response -> {
|
||||
BdfDictionary tp = response.getDictionary(TRANSPORT, null);
|
||||
tp.put("fakeId", BdfDictionary.of(new BdfEntry("fake", "fake")));
|
||||
response.put(TRANSPORT, tp);
|
||||
return false;
|
||||
});
|
||||
testModifiedResponse(
|
||||
m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
|
||||
m.getTimestamp(), m.getPreviousMessageId(),
|
||||
m.getSessionId(), m.getEphemeralPublicKey(),
|
||||
m.getAcceptTimestamp(),
|
||||
getTransportPropertiesMap(2))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedTimestamp() throws Exception {
|
||||
testModifiedResponse(response -> {
|
||||
long timestamp = response.getLong(TIME, 0L);
|
||||
response.put(TIME, timestamp + 1);
|
||||
return false;
|
||||
});
|
||||
testModifiedResponse(
|
||||
m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
|
||||
m.getTimestamp(), m.getPreviousMessageId(),
|
||||
m.getSessionId(), m.getEphemeralPublicKey(),
|
||||
clock.currentTimeMillis(),
|
||||
m.getTransportProperties())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedEphemeralPublicKey() throws Exception {
|
||||
testModifiedResponse(response -> {
|
||||
KeyPair keyPair = crypto.generateAgreementKeyPair();
|
||||
response.put(E_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedEphemeralPublicKeyWithFakeMac()
|
||||
throws Exception {
|
||||
// initialize a real introducee manager
|
||||
MessageSender messageSender = c2.getMessageSender();
|
||||
TransportPropertyManager tpManager = c2.getTransportPropertyManager();
|
||||
IntroduceeManager manager2 =
|
||||
new IntroduceeManager(messageSender, db2, clientHelper, clock,
|
||||
crypto, tpManager, authorFactory, contactManager2,
|
||||
identityManager2, introductionGroupFactory);
|
||||
|
||||
// create keys
|
||||
KeyPair keyPair1 = crypto.generateSignatureKeyPair();
|
||||
KeyPair eKeyPair1 = crypto.generateAgreementKeyPair();
|
||||
KeyPair eKeyPair2 = crypto.generateAgreementKeyPair();
|
||||
|
||||
// Nonce 1
|
||||
byte[][] inputs = {
|
||||
new byte[] {CLIENT_VERSION},
|
||||
eKeyPair1.getPublic().getEncoded(),
|
||||
eKeyPair2.getPublic().getEncoded()
|
||||
};
|
||||
SecretKey sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
||||
eKeyPair2.getPublic(), eKeyPair1, inputs);
|
||||
byte[] nonce1 = crypto.mac(ALICE_NONCE_LABEL, sharedSecret);
|
||||
|
||||
// Signature 1
|
||||
byte[] sig1 = crypto.sign(SIGNING_LABEL, nonce1,
|
||||
keyPair1.getPrivate().getEncoded());
|
||||
|
||||
// MAC 1
|
||||
SecretKey macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret);
|
||||
BdfDictionary tp1 = BdfDictionary.of(new BdfEntry("fake", "fake"));
|
||||
long time1 = clock.currentTimeMillis();
|
||||
BdfList toMacList = BdfList.of(keyPair1.getPublic().getEncoded(),
|
||||
eKeyPair1.getPublic().getEncoded(), tp1, time1);
|
||||
byte[] toMac = clientHelper.toByteArray(toMacList);
|
||||
byte[] mac1 = crypto.mac(MAC_LABEL, macKey1, toMac);
|
||||
|
||||
// create only relevant part of state for introducee2
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(PUBLIC_KEY, keyPair1.getPublic().getEncoded());
|
||||
state.put(TRANSPORT, tp1);
|
||||
state.put(TIME, time1);
|
||||
state.put(E_PUBLIC_KEY, eKeyPair1.getPublic().getEncoded());
|
||||
state.put(MAC, mac1);
|
||||
state.put(MAC_KEY, macKey1.getBytes());
|
||||
state.put(NONCE, nonce1);
|
||||
state.put(SIGNATURE, sig1);
|
||||
|
||||
// MAC and signature verification should pass
|
||||
manager2.verifyMac(state);
|
||||
manager2.verifySignature(state);
|
||||
|
||||
// replace ephemeral key pair and recalculate matching keys and nonce
|
||||
KeyPair eKeyPair1f = crypto.generateAgreementKeyPair();
|
||||
byte[][] fakeInputs = {
|
||||
new byte[] {CLIENT_VERSION},
|
||||
eKeyPair1f.getPublic().getEncoded(),
|
||||
eKeyPair2.getPublic().getEncoded()
|
||||
};
|
||||
sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
||||
eKeyPair2.getPublic(), eKeyPair1f, fakeInputs);
|
||||
nonce1 = crypto.mac(ALICE_NONCE_LABEL, sharedSecret);
|
||||
|
||||
// recalculate MAC
|
||||
macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret);
|
||||
toMacList = BdfList.of(keyPair1.getPublic().getEncoded(),
|
||||
eKeyPair1f.getPublic().getEncoded(), tp1, time1);
|
||||
toMac = clientHelper.toByteArray(toMacList);
|
||||
mac1 = crypto.mac(MAC_LABEL, macKey1, toMac);
|
||||
|
||||
// update state with faked information
|
||||
state.put(E_PUBLIC_KEY, eKeyPair1f.getPublic().getEncoded());
|
||||
state.put(MAC, mac1);
|
||||
state.put(MAC_KEY, macKey1.getBytes());
|
||||
state.put(NONCE, nonce1);
|
||||
|
||||
// MAC verification should still pass
|
||||
manager2.verifyMac(state);
|
||||
|
||||
// Signature can not be verified, because we don't have private
|
||||
// long-term key to fake it
|
||||
try {
|
||||
manager2.verifySignature(state);
|
||||
fail();
|
||||
} catch (GeneralSecurityException e) {
|
||||
// expected
|
||||
}
|
||||
testModifiedResponse(
|
||||
m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
|
||||
m.getTimestamp(), m.getPreviousMessageId(),
|
||||
m.getSessionId(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
m.getAcceptTimestamp(), m.getTransportProperties())
|
||||
);
|
||||
}
|
||||
|
||||
private void addTransportProperties()
|
||||
@@ -832,17 +646,15 @@ public class IntroductionIntegrationTest
|
||||
TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
|
||||
TransportPropertyManager tpm1 = c1.getTransportPropertyManager();
|
||||
TransportPropertyManager tpm2 = c2.getTransportPropertyManager();
|
||||
TransportProperties tp = new TransportProperties(
|
||||
Collections.singletonMap("key", "value"));
|
||||
|
||||
tpm0.mergeLocalProperties(TRANSPORT_ID, tp);
|
||||
tpm0.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
tpm1.mergeLocalProperties(TRANSPORT_ID, tp);
|
||||
tpm1.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
|
||||
sync1To0(1, true);
|
||||
|
||||
tpm2.mergeLocalProperties(TRANSPORT_ID, tp);
|
||||
tpm2.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
|
||||
sync2To0(1, true);
|
||||
}
|
||||
|
||||
@@ -935,7 +747,7 @@ public class IntroductionIntegrationTest
|
||||
time);
|
||||
}
|
||||
}
|
||||
} catch (DbException | FormatException exception) {
|
||||
} catch (DbException exception) {
|
||||
eventWaiter.rethrow(exception);
|
||||
} finally {
|
||||
eventWaiter.resume();
|
||||
@@ -945,7 +757,6 @@ public class IntroductionIntegrationTest
|
||||
Contact contact = ((IntroductionSucceededEvent) e).getContact();
|
||||
eventWaiter
|
||||
.assertFalse(contact.getId().equals(contactId0From1));
|
||||
eventWaiter.assertTrue(contact.isActive());
|
||||
eventWaiter.resume();
|
||||
} else if (e instanceof IntroductionAbortedEvent) {
|
||||
aborted = true;
|
||||
@@ -981,30 +792,41 @@ public class IntroductionIntegrationTest
|
||||
|
||||
}
|
||||
|
||||
private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g)
|
||||
throws FormatException, DbException {
|
||||
BdfDictionary gD = ch.getGroupMetadataAsDictionary(g);
|
||||
LOG.warning(gD.toString());
|
||||
BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY);
|
||||
queue.put("nextOut", queue.getLong("nextOut") - 1);
|
||||
gD.put(QUEUE_STATE_KEY, queue);
|
||||
ch.mergeGroupMetadata(g, gD);
|
||||
private void replacePreviousLocalMessageId(Author author,
|
||||
BdfDictionary d, MessageId id) throws FormatException {
|
||||
BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_1);
|
||||
BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_2);
|
||||
Author a1 = clientHelper
|
||||
.parseAndValidateAuthor(i1.getList(SESSION_KEY_AUTHOR));
|
||||
Author a2 = clientHelper
|
||||
.parseAndValidateAuthor(i2.getList(SESSION_KEY_AUTHOR));
|
||||
|
||||
if (a1.equals(author)) {
|
||||
i1.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id);
|
||||
d.put(SESSION_KEY_INTRODUCEE_1, i1);
|
||||
} else if (a2.equals(author)) {
|
||||
i2.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id);
|
||||
d.put(SESSION_KEY_INTRODUCEE_2, i2);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private Entry<MessageId, BdfDictionary> getMessageFor(ClientHelper ch,
|
||||
Contact contact, long type) throws FormatException, DbException {
|
||||
Entry<MessageId, BdfDictionary> response = null;
|
||||
Group g = introductionGroupFactory
|
||||
.createIntroductionGroup(contact);
|
||||
private AbstractIntroductionMessage getMessageFor(ClientHelper ch,
|
||||
Contact contact, MessageType type)
|
||||
throws FormatException, DbException {
|
||||
Group g = introductionManager0.getContactGroup(contact);
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue())
|
||||
);
|
||||
Map<MessageId, BdfDictionary> map =
|
||||
ch.getMessageMetadataAsDictionary(g.getId());
|
||||
for (Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
if (entry.getValue().getLong(TYPE) == type) {
|
||||
response = entry;
|
||||
}
|
||||
}
|
||||
assertTrue(response != null);
|
||||
return response;
|
||||
ch.getMessageMetadataAsDictionary(g.getId(), query);
|
||||
assertEquals(1, map.size());
|
||||
MessageId id = map.entrySet().iterator().next().getKey();
|
||||
Message m = ch.getMessage(id);
|
||||
BdfList body = ch.getMessageAsList(id);
|
||||
//noinspection ConstantConditions
|
||||
return c0.getMessageParser().parseAcceptMessage(m, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ interface IntroductionIntegrationTestComponent
|
||||
|
||||
void inject(IntroductionIntegrationTest init);
|
||||
|
||||
MessageSender getMessageSender();
|
||||
MessageEncoder getMessageEncoder();
|
||||
MessageParser getMessageParser();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
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.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.test.BriarTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IntroductionManagerImplTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final IntroductionManagerImpl introductionManager;
|
||||
private final IntroducerManager introducerManager;
|
||||
private final IntroduceeManager introduceeManager;
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final MessageTracker messageTracker;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final SessionId sessionId = new SessionId(getRandomId());
|
||||
private final MessageId storageId = new MessageId(sessionId.getBytes());
|
||||
private final long time = 42L;
|
||||
private final Contact introducee1;
|
||||
private final Contact introducee2;
|
||||
private final Group introductionGroup1;
|
||||
private final Group introductionGroup2;
|
||||
private final Message message1;
|
||||
private Transaction txn;
|
||||
|
||||
public IntroductionManagerImplTest() {
|
||||
Author author1 = getAuthor();
|
||||
AuthorId localAuthorId1 = new AuthorId(getRandomId());
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
introducee1 =
|
||||
new Contact(contactId1, author1, localAuthorId1, true, true);
|
||||
|
||||
Author author2 = getAuthor();
|
||||
AuthorId localAuthorId2 = new AuthorId(getRandomId());
|
||||
ContactId contactId2 = new ContactId(235);
|
||||
introducee2 =
|
||||
new Contact(contactId2, author2, localAuthorId2, true, true);
|
||||
|
||||
introductionGroup1 = getGroup(CLIENT_ID);
|
||||
introductionGroup2 = getGroup(CLIENT_ID);
|
||||
|
||||
message1 = new Message(
|
||||
new MessageId(getRandomId()),
|
||||
introductionGroup1.getId(),
|
||||
time,
|
||||
getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
|
||||
);
|
||||
|
||||
// mock ALL THE THINGS!!!
|
||||
context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
introducerManager = context.mock(IntroducerManager.class);
|
||||
introduceeManager = context.mock(IntroduceeManager.class);
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
MetadataParser metadataParser = context.mock(MetadataParser.class);
|
||||
messageTracker = context.mock(MessageTracker.class);
|
||||
introductionGroupFactory = context.mock(IntroductionGroupFactory.class);
|
||||
|
||||
introductionManager = new IntroductionManagerImpl(db, clientHelper,
|
||||
metadataParser, messageTracker, introducerManager,
|
||||
introduceeManager, introductionGroupFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMakeIntroduction() throws DbException, FormatException {
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(introducerManager)
|
||||
.makeIntroduction(txn, introducee1, introducee2, null,
|
||||
time);
|
||||
// get both introduction groups
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee2);
|
||||
will(returnValue(introductionGroup2));
|
||||
// track message for group 1
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup1.getId(), time, true);
|
||||
// track message for group 2
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup2.getId(), time, true);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.makeIntroduction(introducee1, introducee2, null, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptIntroduction() throws DbException, FormatException {
|
||||
BdfDictionary state = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
|
||||
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
|
||||
);
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, introducee1.getId());
|
||||
will(returnValue(introducee1));
|
||||
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
|
||||
will(returnValue(state));
|
||||
oneOf(introduceeManager).acceptIntroduction(txn, state, time);
|
||||
// track message
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup1.getId(), time, true);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.acceptIntroduction(introducee1.getId(), sessionId, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclineIntroduction() throws DbException, FormatException {
|
||||
BdfDictionary state = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
|
||||
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
|
||||
);
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, introducee1.getId());
|
||||
will(returnValue(introducee1));
|
||||
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
|
||||
will(returnValue(state));
|
||||
oneOf(introduceeManager).declineIntroduction(txn, state, time);
|
||||
// track message
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup1.getId(), time, true);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.declineIntroduction(introducee1.getId(), sessionId, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntroductionMessages()
|
||||
throws DbException, FormatException {
|
||||
|
||||
Map<MessageId, BdfDictionary> metadata = Collections.emptyMap();
|
||||
Collection<MessageStatus> statuses = Collections.emptyList();
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, introducee1.getId());
|
||||
will(returnValue(introducee1));
|
||||
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
introductionGroup1.getId());
|
||||
will(returnValue(metadata));
|
||||
oneOf(db).getMessageStatus(txn, introducee1.getId(),
|
||||
introductionGroup1.getId());
|
||||
will(returnValue(statuses));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager.getIntroductionMessages(introducee1.getId());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingRequestMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_REQUEST);
|
||||
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(introduceeManager)
|
||||
.initialize(txn, introductionGroup1.getId(), msg);
|
||||
will(returnValue(state));
|
||||
oneOf(introduceeManager)
|
||||
.incomingMessage(txn, state, msg);
|
||||
// track message
|
||||
oneOf(messageTracker).trackIncomingMessage(txn, message1);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.incomingMessage(txn, message1, new BdfList(), msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingResponseMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_RESPONSE),
|
||||
new BdfEntry(SESSION_ID, sessionId)
|
||||
);
|
||||
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(ROLE, ROLE_INTRODUCER);
|
||||
state.put(GROUP_ID_1, introductionGroup1.getId());
|
||||
state.put(GROUP_ID_2, introductionGroup2.getId());
|
||||
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
|
||||
will(returnValue(state));
|
||||
oneOf(introducerManager).incomingMessage(txn, state, msg);
|
||||
// track message
|
||||
oneOf(messageTracker).trackIncomingMessage(txn, message1);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.incomingMessage(txn, message1, new BdfList(), msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,361 +1,424 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
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.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.bramble.test.ValidatorTestCase;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.test.BriarTestCase;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
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.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IntroductionValidatorTest extends BriarTestCase {
|
||||
public class IntroductionValidatorTest extends ValidatorTestCase {
|
||||
|
||||
private final Mockery context = new Mockery();
|
||||
private final Group group;
|
||||
private final Message message;
|
||||
private final IntroductionValidator validator;
|
||||
private final Clock clock = new SystemClock();
|
||||
private final MessageEncoder messageEncoder =
|
||||
context.mock(MessageEncoder.class);
|
||||
private final IntroductionValidator validator =
|
||||
new IntroductionValidator(messageEncoder, clientHelper,
|
||||
metadataEncoder, clock);
|
||||
|
||||
public IntroductionValidatorTest() {
|
||||
group = getGroup(getClientId());
|
||||
MessageId messageId = new MessageId(getRandomId());
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] raw = getRandomBytes(123);
|
||||
message = new Message(messageId, group.getId(), timestamp, raw);
|
||||
|
||||
|
||||
ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
validator = new IntroductionValidator(clientHelper, metadataEncoder,
|
||||
clock);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
private final SessionId sessionId = new SessionId(getRandomId());
|
||||
private final MessageId previousMsgId = new MessageId(getRandomId());
|
||||
private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
|
||||
private final BdfDictionary meta = new BdfDictionary();
|
||||
private final long acceptTimestamp = 42;
|
||||
private final BdfDictionary transportProperties = BdfDictionary.of(
|
||||
new BdfEntry("transportId", new BdfDictionary())
|
||||
);
|
||||
private final byte[] mac = getRandomBytes(MAC_BYTES);
|
||||
private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES);
|
||||
|
||||
//
|
||||
// Introduction Requests
|
||||
// Introduction REQUEST
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateProperIntroductionRequest() throws Exception {
|
||||
byte[] sessionId = getRandomId();
|
||||
String name = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
String text = getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
public void testAcceptsRequest() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(),
|
||||
authorList, text);
|
||||
|
||||
BdfList body = BdfList.of(TYPE_REQUEST, sessionId,
|
||||
name, publicKey, text);
|
||||
expectParseAuthor(authorList, author);
|
||||
expectEncodeRequestMetadata();
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
assertEquals(name, result.getString(NAME));
|
||||
assertEquals(publicKey, result.getRaw(PUBLIC_KEY));
|
||||
assertEquals(text, result.getString(MSG));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionRequestWithNoName() throws Exception {
|
||||
BdfDictionary msg = getValidIntroductionRequest();
|
||||
|
||||
// no NAME is message
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionRequestWithLongName() throws Exception {
|
||||
// too long NAME in message
|
||||
BdfDictionary msg = getValidIntroductionRequest();
|
||||
msg.put(NAME, msg.get(NAME) + "x");
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionRequestWithWrongType()
|
||||
throws Exception {
|
||||
// wrong message type
|
||||
BdfDictionary msg = getValidIntroductionRequest();
|
||||
msg.put(TYPE, 324234);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
private BdfDictionary getValidIntroductionRequest() throws Exception {
|
||||
byte[] sessionId = getRandomId();
|
||||
Author author = getAuthor();
|
||||
String text = getRandomString(MAX_MESSAGE_BODY_LENGTH);
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_REQUEST);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(NAME, author.getName());
|
||||
msg.put(PUBLIC_KEY, author.getPublicKey());
|
||||
msg.put(MSG, text);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Responses
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateIntroductionAcceptResponse() throws Exception {
|
||||
byte[] groupId = getRandomId();
|
||||
byte[] sessionId = getRandomId();
|
||||
long time = clock.currentTimeMillis();
|
||||
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
String transportId =
|
||||
getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = BdfDictionary.of(
|
||||
new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH),
|
||||
getRandomString(MAX_PROPERTY_LENGTH))
|
||||
);
|
||||
BdfDictionary tp = BdfDictionary.of(
|
||||
new BdfEntry(transportId, tProps)
|
||||
);
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, groupId);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(ACCEPT, true);
|
||||
msg.put(TIME, time);
|
||||
msg.put(E_PUBLIC_KEY, publicKey);
|
||||
msg.put(TRANSPORT, tp);
|
||||
|
||||
BdfList body = BdfList.of(TYPE_RESPONSE, msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT), msg.getLong(TIME),
|
||||
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
assertEquals(true, result.getBoolean(ACCEPT));
|
||||
assertEquals(publicKey, result.getRaw(E_PUBLIC_KEY));
|
||||
assertEquals(tp, result.getDictionary(TRANSPORT));
|
||||
context.assertIsSatisfied();
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateIntroductionDeclineResponse() throws Exception {
|
||||
BdfDictionary msg = getValidIntroductionResponse(false);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT));
|
||||
public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text);
|
||||
|
||||
BdfDictionary result = validator.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
expectParseAuthor(authorList, author);
|
||||
expectEncodeRequestMetadata();
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertFalse(result.getBoolean(ACCEPT));
|
||||
context.assertIsSatisfied();
|
||||
assertExpectedContext(messageContext, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsRequestWithMessageNull() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null);
|
||||
|
||||
expectParseAuthor(authorList, author);
|
||||
expectEncodeRequestMetadata();
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, null);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionResponseWithoutAccept()
|
||||
throws Exception {
|
||||
BdfDictionary msg = getValidIntroductionResponse(false);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
public void testRejectsTooShortBodyForRequest() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionResponseWithBrokenTp()
|
||||
throws Exception {
|
||||
BdfDictionary msg = getValidIntroductionResponse(true);
|
||||
BdfDictionary tp = msg.getDictionary(TRANSPORT);
|
||||
tp.put(
|
||||
getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X");
|
||||
msg.put(TRANSPORT, tp);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT), msg.getLong(TIME),
|
||||
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
|
||||
|
||||
public void testRejectsTooLongBodyForRequest() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(REQUEST.getValue(), null, authorList, text, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionResponseWithoutPublicKey()
|
||||
throws Exception {
|
||||
BdfDictionary msg = getValidIntroductionResponse(true);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT), msg.getLong(TIME),
|
||||
msg.getDictionary(TRANSPORT));
|
||||
|
||||
public void testRejectsRawMessageForRequest() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(REQUEST.getValue(), null, authorList, getRandomId());
|
||||
expectParseAuthor(authorList, author);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
private BdfDictionary getValidIntroductionResponse(boolean accept)
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsStringMessageIdForRequest() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ACCEPT
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
acceptTimestamp, transportProperties);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
transportProperties.getDictionary("transportId"));
|
||||
}});
|
||||
expectEncodeMetadata(ACCEPT);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
acceptTimestamp, transportProperties, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForAccept() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
|
||||
transportProperties);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), 1,
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
|
||||
transportProperties);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongPublicKeyForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
|
||||
transportProperties);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsEmptyTransportPropertiesForAccept()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
|
||||
new BdfDictionary());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
byte[] groupId = getRandomId();
|
||||
byte[] sessionId = getRandomId();
|
||||
long time = clock.currentTimeMillis();
|
||||
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
String transportId =
|
||||
getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = BdfDictionary.of(
|
||||
new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH),
|
||||
getRandomString(MAX_PROPERTY_LENGTH))
|
||||
);
|
||||
BdfDictionary tp = BdfDictionary.of(
|
||||
new BdfEntry(transportId, tProps)
|
||||
);
|
||||
//
|
||||
// Introduction DECLINE
|
||||
//
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, groupId);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(ACCEPT, accept);
|
||||
if (accept) {
|
||||
msg.put(TIME, time);
|
||||
msg.put(E_PUBLIC_KEY, publicKey);
|
||||
msg.put(TRANSPORT, tp);
|
||||
@Test
|
||||
public void testAcceptsDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes());
|
||||
|
||||
expectEncodeMetadata(DECLINE);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForDecline() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), 1);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction AUTH
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, signature);
|
||||
|
||||
expectEncodeMetadata(AUTH);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, signature, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortMacForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1),
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongMacForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAC_BYTES + 1), signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidMacForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortSignatureForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, getRandomBytes(0));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongSignatureForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac,
|
||||
getRandomBytes(MAX_SIGNATURE_BYTES + 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSignatureForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ACTIVATE
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes());
|
||||
|
||||
expectEncodeMetadata(ACTIVATE);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForActivate() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ABORT
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes());
|
||||
|
||||
expectEncodeMetadata(ABORT);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForAbort() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), 1);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Helper Methods
|
||||
//
|
||||
|
||||
private void expectEncodeRequestMetadata() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeRequestMetadata(message.getTimestamp(), false, false,
|
||||
false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectEncodeMetadata(MessageType type) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeMetadata(type, sessionId, message.getTimestamp(),
|
||||
false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
}
|
||||
|
||||
private void assertExpectedContext(BdfMessageContext c,
|
||||
@Nullable MessageId dependency) {
|
||||
assertEquals(meta, c.getDictionary());
|
||||
if (dependency == null) {
|
||||
assertEquals(0, c.getDependencies().size());
|
||||
} else {
|
||||
assertEquals(dependency, c.getDependencies().iterator().next());
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ACK
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateProperIntroductionAck() throws Exception {
|
||||
byte[] sessionId = getRandomId();
|
||||
byte[] mac = getRandomBytes(MAC_LENGTH);
|
||||
byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
|
||||
assertArrayEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
assertArrayEquals(mac, result.getRaw(MAC));
|
||||
assertArrayEquals(sig, result.getRaw(SIGNATURE));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateTooLongIntroductionAck() throws Exception {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(SESSION_ID, getRandomId()),
|
||||
new BdfEntry("garbage", getRandomString(255))
|
||||
);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString("garbage"));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionAckWithLongSessionId()
|
||||
throws Exception {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1])
|
||||
);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Abort
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateProperIntroductionAbort() throws Exception {
|
||||
byte[] sessionId = getRandomId();
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_ABORT);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateTooLongIntroductionAbort() throws Exception {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ABORT),
|
||||
new BdfEntry(SESSION_ID, getRandomId()),
|
||||
new BdfEntry("garbage", getRandomString(255))
|
||||
);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString("garbage"));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -28,9 +28,9 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -55,8 +55,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
|
||||
private final SessionId sessionId = new SessionId(getRandomId());
|
||||
private final MessageId previousMsgId = new MessageId(getRandomId());
|
||||
private final Author author;
|
||||
private final String text =
|
||||
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
|
||||
private final byte[] ephemeralPublicKey =
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
private final byte[] mac = getRandomBytes(MAC_BYTES);
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -16,8 +16,8 @@ import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
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.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
|
||||
public class MessageEncoderTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -35,8 +35,7 @@ public class MessageEncoderTest extends BrambleMockTestCase {
|
||||
private final byte[] body = getRandomBytes(42);
|
||||
private final Author author = getAuthor();
|
||||
private final BdfList authorList = new BdfList();
|
||||
private final String text =
|
||||
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
|
||||
|
||||
@Test
|
||||
public void testEncodeRequestMessage() throws FormatException {
|
||||
@@ -1,98 +0,0 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
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.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
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.sync.Group;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.test.BriarTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class MessageSenderTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final MessageSender messageSender;
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MessageQueueManager messageQueueManager;
|
||||
private final Clock clock;
|
||||
|
||||
public MessageSenderTest() {
|
||||
context = new Mockery();
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
metadataEncoder =
|
||||
context.mock(MetadataEncoder.class);
|
||||
messageQueueManager =
|
||||
context.mock(MessageQueueManager.class);
|
||||
clock = context.mock(Clock.class);
|
||||
|
||||
messageSender = new MessageSender(db, clientHelper, clock,
|
||||
metadataEncoder, messageQueueManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendMessage() throws DbException, FormatException {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Group privateGroup = getGroup(getClientId());
|
||||
SessionId sessionId = new SessionId(getRandomId());
|
||||
byte[] mac = getRandomBytes(42);
|
||||
byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
long time = 42L;
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(GROUP_ID, privateGroup.getId()),
|
||||
new BdfEntry(SESSION_ID, sessionId),
|
||||
new BdfEntry(MAC, mac),
|
||||
new BdfEntry(SIGNATURE, sig)
|
||||
);
|
||||
BdfList bodyList =
|
||||
BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig);
|
||||
byte[] body = getRandomBytes(8);
|
||||
Metadata metadata = new Metadata();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toByteArray(bodyList);
|
||||
will(returnValue(body));
|
||||
oneOf(db).getGroup(txn, privateGroup.getId());
|
||||
will(returnValue(privateGroup));
|
||||
oneOf(metadataEncoder).encode(msg);
|
||||
will(returnValue(metadata));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
oneOf(messageQueueManager)
|
||||
.sendMessage(txn, privateGroup, time, body, metadata);
|
||||
}});
|
||||
|
||||
messageSender.sendMessage(txn, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.introduction2.IntroducerSession.Introducee;
|
||||
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
||||
import org.briarproject.briar.test.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
|
||||
import org.junit.Test;
|
||||
@@ -29,11 +29,11 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS;
|
||||
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -1,428 +0,0 @@
|
||||
package org.briarproject.briar.introduction2;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
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.sync.MessageId;
|
||||
import org.briarproject.bramble.test.ValidatorTestCase;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
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.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.AUTH;
|
||||
import static org.briarproject.briar.introduction2.MessageType.DECLINE;
|
||||
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IntroductionValidatorTest extends ValidatorTestCase {
|
||||
|
||||
private final MessageEncoder messageEncoder =
|
||||
context.mock(MessageEncoder.class);
|
||||
private final IntroductionValidator validator =
|
||||
new IntroductionValidator(messageEncoder, clientHelper,
|
||||
metadataEncoder, clock);
|
||||
|
||||
private final SessionId sessionId = new SessionId(getRandomId());
|
||||
private final MessageId previousMsgId = new MessageId(getRandomId());
|
||||
private final String text =
|
||||
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
private final BdfDictionary meta = new BdfDictionary();
|
||||
private final long acceptTimestamp = 42;
|
||||
private final BdfDictionary transportProperties = BdfDictionary.of(
|
||||
new BdfEntry("transportId", new BdfDictionary())
|
||||
);
|
||||
private final byte[] mac = getRandomBytes(MAC_BYTES);
|
||||
private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES);
|
||||
|
||||
//
|
||||
// Introduction REQUEST
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsRequest() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(),
|
||||
authorList, text);
|
||||
|
||||
expectParseAuthor(authorList, author);
|
||||
expectEncodeRequestMetadata();
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text);
|
||||
|
||||
expectParseAuthor(authorList, author);
|
||||
expectEncodeRequestMetadata();
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsRequestWithMessageNull() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null);
|
||||
|
||||
expectParseAuthor(authorList, author);
|
||||
expectEncodeRequestMetadata();
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, null);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForRequest() throws Exception {
|
||||
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForRequest() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(REQUEST.getValue(), null, authorList, text, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsRawMessageForRequest() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(REQUEST.getValue(), null, authorList, getRandomId());
|
||||
expectParseAuthor(authorList, author);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsStringMessageIdForRequest() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ACCEPT
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
acceptTimestamp, transportProperties);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
transportProperties.getDictionary("transportId"));
|
||||
}});
|
||||
expectEncodeMetadata(ACCEPT);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
acceptTimestamp, transportProperties, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForAccept() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
|
||||
transportProperties);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
|
||||
transportProperties);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongPublicKeyForAccept() throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
|
||||
transportProperties);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsEmptyTransportPropertiesForAccept()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
|
||||
new BdfDictionary());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction DECLINE
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes());
|
||||
|
||||
expectEncodeMetadata(DECLINE);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForDecline() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception {
|
||||
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
|
||||
null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction AUTH
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, signature);
|
||||
|
||||
expectEncodeMetadata(AUTH);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, signature, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortMacForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1),
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongMacForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(),
|
||||
getRandomBytes(MAC_BYTES + 1), signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidMacForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortSignatureForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, getRandomBytes(0));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongSignatureForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac,
|
||||
getRandomBytes(MAX_SIGNATURE_BYTES + 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSignatureForAuth() throws Exception {
|
||||
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), mac, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ACTIVATE
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes());
|
||||
|
||||
expectEncodeMetadata(ACTIVATE);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForActivate() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception {
|
||||
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
|
||||
null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ABORT
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testAcceptsAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes());
|
||||
|
||||
expectEncodeMetadata(ABORT);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
|
||||
assertExpectedContext(messageContext, previousMsgId);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
|
||||
previousMsgId.getBytes(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidSessionIdForAbort() throws Exception {
|
||||
BdfList body =
|
||||
BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
|
||||
null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Helper Methods
|
||||
//
|
||||
|
||||
private void expectEncodeRequestMetadata() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeRequestMetadata(message.getTimestamp(), false, false,
|
||||
false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectEncodeMetadata(MessageType type) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeMetadata(type, sessionId, message.getTimestamp(),
|
||||
false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
}
|
||||
|
||||
private void assertExpectedContext(BdfMessageContext c,
|
||||
@Nullable MessageId dependency) {
|
||||
assertEquals(meta, c.getDictionary());
|
||||
if (dependency == null) {
|
||||
assertEquals(0, c.getDependencies().size());
|
||||
} else {
|
||||
assertEquals(dependency, c.getDependencies().iterator().next());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,9 +37,9 @@ import org.briarproject.briar.blog.BlogModule;
|
||||
import org.briarproject.briar.client.BriarClientModule;
|
||||
import org.briarproject.briar.forum.ForumModule;
|
||||
import org.briarproject.briar.introduction.IntroductionModule;
|
||||
import org.briarproject.briar.introduction2.IntroductionCryptoImplTest;
|
||||
import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest;
|
||||
import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest;
|
||||
import org.briarproject.briar.introduction.IntroductionCryptoImplTest;
|
||||
import org.briarproject.briar.introduction.MessageEncoderParserIntegrationTest;
|
||||
import org.briarproject.briar.introduction.SessionEncoderParserIntegrationTest;
|
||||
import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
|
||||
Reference in New Issue
Block a user