Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • briar/briar
  • goapunk/briar
  • johndoe4221/briar
  • thomas/briar
4 results
Show changes
Commits on Source (43)
Showing
with 587 additions and 53 deletions
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
# Briar Mailbox
This project aims to develop an easy way for Briar users to increase their
reachability and lower the battery drain of their phone.
In Briar messages are exchanged directly between contacts (peer-to-peer).
This kind of synchronous message exchange requires contacts to be online and
connected to each other.
While this is great for privacy (no central server which
can log things or be censored) it's bad for reachability, especially in
mobile networks where connectivity can be limited.
```mermaid
graph LR
A[Alice]
B[Bob]
A1[Alice]
B1[Bob]
style B fill:#8db600
style A1 fill:#8db600
subgraph Alice offline
B-. can't send message .-> A
end
subgraph Bob offline
B1-. can't send message .-> A1
end
```
Message delivery could be delayed for an arbitrary time (or even indefinitely)
until both Bob and Alice are online at the same time.
The repeater solves this problem by providing
mailbox-like message buffer where contacts can leave messages for the owner
of the repeater and which is connected to a stable internet connection
(e.g. the wifi at home, cable internet) and a power source.
```mermaid
graph LR
A[Alice]
A1[Alice]
B[Bob]
B1[Bob]
RA["Mailbox (always online)"]
style B fill:#8db600
style RA fill:#8db600
style A1 fill:#8db600
subgraph Alice offline
B-. can't send message .-> A
end
subgraph Alices' Mailbox
B-- send message --> RA
end
subgraph Alice online
B1-. can't send message .-> A1
A1-- get message --> RA
end
```
## Hardware
We want the repeater to be as easy to deploy as possible. The first version
will come as Android application since it will be easy to setup and besides a
spare phone no special hardware is required. Once this is done support for
any hardware supporting Java (e.g. unix server, raspberry pi) will be added.
## Features
### Core features
* Allow contacts to store messages for the owner of the repeater
* Allow the owner to store messages for her contacts. Contacts can pick them up
when syncing with the repeater.
* Owner and contacts connect to the repeater via Tor.
### Extended features/components
* The repeater can sync group messages (from groups the owner is part of) with
other group members (increases message circulation)
* Contacts and the owner can connect to the repeater via other transports (Bluetooth, Wifi-Direct, Lan)
* Push-like message notification for the owner to decrease battery drain
File added
......@@ -6,6 +6,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.contact.ContactType.CONTACT;
@Immutable
@NotNullByDefault
public class Contact {
......@@ -36,6 +38,10 @@ public class Contact {
return localAuthorId;
}
public ContactType getType() {
return CONTACT;
}
public boolean isVerified() {
return verified;
}
......
......@@ -12,11 +12,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
public interface ContactExchangeTask {
/**
* The current version of the contact exchange protocol.
*/
byte PROTOCOL_VERSION = 1;
/**
* Label for deriving Alice's header key from the master secret.
*/
......@@ -44,5 +39,5 @@ public interface ContactExchangeTask {
void startExchange(ContactExchangeListener listener,
LocalAuthor localAuthor, SecretKey masterSecret,
DuplexTransportConnection conn, TransportId transportId,
boolean alice);
boolean alice, ContactType localType, ContactType remoteType);
}
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface ContactFactory {
static Contact createContact(ContactId c, Author author,
AuthorId localAuthorId, boolean verified, boolean active,
ContactType type) {
switch (type) {
case CONTACT:
return new Contact(c, author, localAuthorId, verified, active);
case PRIVATE_MAILBOX:
return new PrivateMailbox(c, author, localAuthorId, verified,
active);
case MAILBOX_OWNER:
return new MailboxOwner(c, author, localAuthorId, verified,
active);
default:
throw new IllegalArgumentException("Unknown contact type");
}
}
}
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.Map;
public class ContactInfo {
private final Author author;
private final Map<TransportId, TransportProperties> properties;
private final byte[] signature;
private final long timestamp;
private final ContactType type;
public ContactInfo(Author author,
Map<TransportId, TransportProperties> properties,
byte[] signature, long timestamp, ContactType type) {
this.author = author;
this.properties = properties;
this.signature = signature;
this.timestamp = timestamp;
this.type = type;
}
public Author getAuthor() {
return author;
}
public Map<TransportId, TransportProperties> getProperties() {
return properties;
}
public byte[] getSignature() {
return signature;
}
public long getTimestamp() {
return timestamp;
}
public ContactType getType() {
return type;
}
}
......@@ -49,6 +49,15 @@ public interface ContactManager {
long timestamp, boolean alice, boolean verified, boolean active)
throws DbException;
/**
* Add a private Mailbox
*/
ContactId addPrivateMailbox(Transaction txn, Author remote, AuthorId local,
SecretKey master, long timestamp, boolean alice) throws DbException;
ContactId addMailboxOwner(Transaction txn, Author remote, AuthorId local,
SecretKey master, long timestamp, boolean alice) throws DbException;
/**
* Returns the contact with the given ID.
*/
......@@ -77,6 +86,11 @@ public interface ContactManager {
*/
Collection<Contact> getActiveContacts() throws DbException;
/**
* Returns all contacts of the give type.
*/
Collection<Contact> getContactsByType(ContactType type) throws DbException;
/**
* Removes a contact and all associated state.
*/
......@@ -105,8 +119,11 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
interface ContactHook {
Collection<ContactType> getApplicableContactTypes();
void addingContact(Transaction txn, Contact c) throws DbException;
void removingContact(Transaction txn, Contact c) throws DbException;
......
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Enumeration of the different contact types.
*/
@Immutable
@NotNullByDefault
public enum ContactType {
/**
* A regular contact.
*/
CONTACT(0),
/**
* A mailbox paired with this device.
*/
PRIVATE_MAILBOX(1),
/**
* The owner of a mailbox.
*/
MAILBOX_OWNER(2),
/**
* A contact's mailbox
*/
CONTACT_MAILBOX(3);
private final int value;
ContactType(int value) {
this.value = value;
}
// Use this for deserialization
public static ContactType fromValue(int value)
throws FormatException {
for (ContactType type : values()) if (type.value == value) return type;
throw new FormatException();
}
// Use this for values coming from the database
public static ContactType fromLocalValue(int value) {
for (ContactType type : values()) if (type.value == value) return type;
throw new IllegalArgumentException();
}
public int getValue() {
return value;
}
}
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER;
@Immutable
@NotNullByDefault
public class MailboxOwner extends Contact {
public MailboxOwner(ContactId id,
Author author,
AuthorId localAuthorId,
boolean verified, boolean active) {
super(id, author, localAuthorId, verified, active);
}
@Override
public ContactType getType() {
return MAILBOX_OWNER;
}
}
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.contact.ContactType.PRIVATE_MAILBOX;
@Immutable
@NotNullByDefault
public class PrivateMailbox extends Contact {
public PrivateMailbox(ContactId id,
Author author,
AuthorId localAuthorId,
boolean verified, boolean active) {
super(id, author, localAuthorId, verified, active);
}
@Override
public ContactType getType() {
return PRIVATE_MAILBOX;
}
}
......@@ -2,10 +2,12 @@ package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactType;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.mailbox.MailboxInfo;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.settings.Settings;
......@@ -81,7 +83,8 @@ public interface DatabaseComponent {
* and returns an ID for the contact.
*/
ContactId addContact(Transaction txn, Author remote, AuthorId local,
boolean verified, boolean active) throws DbException;
boolean verified, boolean active, ContactType type)
throws DbException;
/**
* Stores a group.
......@@ -112,6 +115,13 @@ public interface DatabaseComponent {
KeySetId addTransportKeys(Transaction txn, ContactId c,
TransportKeys k) throws DbException;
/**
* Set the @{Link ContactType#CONTACT_MAILBOX} for a contact.
* key set ID.
*/
void setMailboxForContact(Transaction txn, ContactId c, @Nullable ContactId mailboxId,
@Nullable ContactId aliasId) throws DbException;
/**
* Returns true if the database contains the given contact for the given
* local pseudonym.
......@@ -196,12 +206,19 @@ public interface DatabaseComponent {
Contact getContact(Transaction txn, ContactId c) throws DbException;
/**
* Returns all contacts.
* Returns all @{link ContactType#CONTACT contacts}.
* <p/>
* Read-only.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/**
* Returns all contacts of every @{link ContactType}.
* <p/>
* Read-only.
*/
Collection<Contact> getAllContacts(Transaction txn) throws DbException;
/**
* Returns a possibly empty collection of contacts with the given author ID.
* <p/>
......@@ -210,6 +227,22 @@ public interface DatabaseComponent {
Collection<Contact> getContactsByAuthorId(Transaction txn, AuthorId remote)
throws DbException;
/**
* Returns a possibly empty collection of contacts of the given type.
* <p/>
* Read-only.
*/
Collection<Contact> getContactsByType(Transaction txn, ContactType type)
throws DbException;
/**
* Returns the type of a given contact.
* <p/>
* Read-only.
*/
ContactType getContactType(Transaction txn, ContactId contactId)
throws DbException;
/**
* Returns all contacts associated with the given local pseudonym.
* <p/>
......@@ -218,6 +251,14 @@ public interface DatabaseComponent {
Collection<ContactId> getContacts(Transaction txn, AuthorId a)
throws DbException;
/**
* Returns @{MailboxInfo}s for all contact mailboxes.
* <p/>
* Read-only.
*/
Collection<MailboxInfo> getContactMailboxes(Transaction txn)
throws DbException;
/**
* Returns the group with the given ID.
* <p/>
......
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.plugin.TransportId;
public interface MailboxConstants {
TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
int POLLING_INTERVALL = 30 * 1000 ; // Scientifically chosen
int MAX_LATENCY = 30 * 1000; // 30 seconds
int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
int MAX_MAILBOX_LATENCY = 604800000; // 1 Week
}
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public class MailboxInfo {
private final ContactId contactId;
// Null if the contact has no mailbox, our own will be used if possible.
@Nullable
private final ContactId mailboxId;
// The alias of the contact for the mailbox. The mailbox must not know
// the read id.
@Nullable
private final ContactId aliasId;
public MailboxInfo(ContactId contactId,
ContactId mailboxId,
ContactId aliasId) {
this.contactId = contactId;
this.mailboxId = mailboxId;
this.aliasId = aliasId;
}
public ContactId getContactId() {
return contactId;
}
@Nullable
public ContactId getMailboxId() {
return mailboxId;
}
@Nullable
public ContactId getAliasId() {
return aliasId;
}
}
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.StreamContext;
public interface MailboxManager {
void handleOutgoingContactMailboxConnection(ContactId contactId,
DuplexTransportConnection connection, TransportId transportId);
void handleOutgoingPrivateMailboxConnection(ContactId contactId,
DuplexTransportConnection connection, TransportId transportId);
/**
* Handle and incoming mailbox owner connection
* NOTE: Before calling the tag MUST be read from the stream!
* (Otherwise we wouldn't know that it's a MailboxOwner connection)
*/
void handleIncomingMailboxOwnerConnection(StreamContext ctx,
TransportId transportId, TransportConnectionReader reader,
TransportConnectionWriter writer);
}
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.lifecycle.Service;
public interface MailboxService extends Service {
}
package org.briarproject.bramble.api.properties;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactType;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
......@@ -40,6 +41,12 @@ public interface TransportPropertyManager {
Map<TransportId, TransportProperties> getLocalProperties()
throws DbException;
/**
* Returns the local anonymized transport properties for all transports.
*/
Map<TransportId, TransportProperties> getLocalAnonymizedProperties()
throws DbException;
/**
* Returns the local transport properties for all transports.
* <p/>
......@@ -53,12 +60,23 @@ public interface TransportPropertyManager {
*/
TransportProperties getLocalProperties(TransportId t) throws DbException;
Map<ContactId, TransportProperties> getRemotePropertiesByType(
Transaction txn,
TransportId t, ContactType cType) throws DbException;
/**
* Returns all remote transport properties for the given transport.
*/
Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
throws DbException;
/**
* Returns all remote transport properties for the given transport and
* Contact Type.
*/
Map<ContactId, TransportProperties> getRemotePropertiesByType(
TransportId t, ContactType cType) throws DbException;
/**
* Returns the remote transport properties for the given contact and
* transport.
......
......@@ -5,6 +5,7 @@ import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.reporting.ReportingModule;
......@@ -25,6 +26,8 @@ public interface BrambleCoreEagerSingletons {
void inject(LifecycleModule.EagerSingletons init);
void inject(MailboxModule.EagerSingletons init);
void inject(PluginModule.EagerSingletons init);
void inject(PropertiesModule.EagerSingletons init);
......@@ -38,4 +41,5 @@ public interface BrambleCoreEagerSingletons {
void inject(TransportModule.EagerSingletons init);
void inject(VersioningModule.EagerSingletons init);
}
......@@ -11,6 +11,7 @@ import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule;
......@@ -37,6 +38,7 @@ import dagger.Module;
IdentityModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
MailboxModule.class,
PluginModule.class,
PropertiesModule.class,
RecordModule.class,
......@@ -47,7 +49,7 @@ import dagger.Module;
SyncModule.class,
SystemModule.class,
TransportModule.class,
VersioningModule.class
VersioningModule.class,
})
public class BrambleCoreModule {
......@@ -64,5 +66,6 @@ public class BrambleCoreModule {
c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons());
c.inject(new MailboxModule.EagerSingletons());
}
}
......@@ -5,7 +5,9 @@ import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactInfo;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.ContactType;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
......@@ -44,6 +46,8 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.contact.ContactType.CONTACT;
import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER;
import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.util.LogUtils.logException;
......@@ -60,6 +64,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private static final String SIGNING_LABEL_EXCHANGE =
"org.briarproject.briar.contact/EXCHANGE";
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final RecordReaderFactory recordReaderFactory;
......@@ -72,12 +77,20 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
/**
* The version of the contact exchange protocol. Note: For backwards compatibility,
* the normal contact exchange is version 1, and 2 for Mailbox related exchanges
* Version 2 introduces a new field to the record, which indicates the contact type
*/
private volatile byte protocolVersion;
private volatile ContactExchangeListener listener;
private volatile LocalAuthor localAuthor;
private volatile DuplexTransportConnection conn;
private volatile TransportId transportId;
private volatile SecretKey masterSecret;
private volatile boolean alice;
private volatile ContactType localType;
private volatile ContactType remoteType;
@Inject
ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper,
......@@ -104,13 +117,19 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
public void startExchange(ContactExchangeListener listener,
LocalAuthor localAuthor, SecretKey masterSecret,
DuplexTransportConnection conn, TransportId transportId,
boolean alice) {
boolean alice, ContactType localType, ContactType remoteType) {
if (remoteType == CONTACT) protocolVersion = 1;
else protocolVersion = 2;
this.listener = listener;
this.localAuthor = localAuthor;
this.conn = conn;
this.transportId = transportId;
this.masterSecret = masterSecret;
this.alice = alice;
this.localType = localType;
this.remoteType = remoteType;
start();
}
......@@ -132,7 +151,11 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Get the local transport properties
Map<TransportId, TransportProperties> localProperties;
try {
localProperties = transportPropertyManager.getLocalProperties();
if (localType == MAILBOX_OWNER)
localProperties =
transportPropertyManager.getLocalAnonymizedProperties();
else
localProperties = transportPropertyManager.getLocalProperties();
} catch (DbException e) {
logException(LOG, WARNING, e);
listener.contactExchangeFailed();
......@@ -142,9 +165,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Derive the header keys for the transport streams
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
masterSecret, new byte[] {PROTOCOL_VERSION});
masterSecret, new byte[] {protocolVersion});
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
new byte[] {protocolVersion});
// Create the readers
InputStream streamReader =
......@@ -158,13 +181,14 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
streamWriterFactory.createContactExchangeStreamWriter(out,
alice ? aliceHeaderKey : bobHeaderKey);
RecordWriter recordWriter =
recordWriterFactory.createRecordWriter(streamWriter.getOutputStream());
recordWriterFactory
.createRecordWriter(streamWriter.getOutputStream());
// Derive the nonces to be signed
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
new byte[] {protocolVersion});
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
new byte[] {protocolVersion});
byte[] localNonce = alice ? aliceNonce : bobNonce;
byte[] remoteNonce = alice ? bobNonce : aliceNonce;
......@@ -202,7 +226,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
// Verify the contact's signature
if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) {
if (!verify(remoteInfo.getAuthor(), remoteNonce,
remoteInfo.getSignature())) {
LOG.warning("Invalid signature");
listener.contactExchangeFailed();
tryToClose(conn);
......@@ -210,22 +235,22 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
// The agreed timestamp is the minimum of the peers' timestamps
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
long timestamp = Math.min(localTimestamp, remoteInfo.getTimestamp());
try {
// Add the contact
ContactId contactId = addContact(remoteInfo.author, timestamp,
remoteInfo.properties);
ContactId contactId = addContact(remoteInfo.getAuthor(), timestamp,
remoteInfo.getProperties());
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contactId, transportId,
conn);
// Pseudonym exchange succeeded
LOG.info("Pseudonym exchange succeeded");
listener.contactExchangeSucceeded(remoteInfo.author);
listener.contactExchangeSucceeded(remoteInfo.getAuthor());
} catch (ContactExistsException e) {
logException(LOG, WARNING, e);
tryToClose(conn);
listener.duplicateContact(remoteInfo.author);
listener.duplicateContact(remoteInfo.getAuthor());
} catch (DbException e) {
logException(LOG, WARNING, e);
tryToClose(conn);
......@@ -256,8 +281,14 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
long timestamp) throws IOException {
BdfList authorList = clientHelper.toList(author);
BdfDictionary props = clientHelper.toDictionary(properties);
BdfList payload = BdfList.of(authorList, props, signature, timestamp);
recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO,
BdfList payload;
if (protocolVersion >= 2) {
payload = BdfList.of(authorList, props, signature, timestamp,
localType.getValue());
} else {
payload = BdfList.of(authorList, props, signature, timestamp);
}
recordWriter.writeRecord(new Record(protocolVersion, CONTACT_INFO,
clientHelper.toByteArray(payload)));
LOG.info("Sent contact info");
}
......@@ -267,12 +298,16 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
Record record;
do {
record = recordReader.readRecord();
if (record.getProtocolVersion() != PROTOCOL_VERSION)
if (record.getProtocolVersion() != protocolVersion)
throw new FormatException();
} while (record.getRecordType() != CONTACT_INFO);
LOG.info("Received contact info");
BdfList payload = clientHelper.toList(record.getPayload());
checkSize(payload, 4);
if (protocolVersion >= 2) {
checkSize(payload, 5);
} else {
checkSize(payload, 4);
}
Author author = clientHelper.parseAndValidateAuthor(payload.getList(0));
BdfDictionary props = payload.getDictionary(1);
Map<TransportId, TransportProperties> properties =
......@@ -281,7 +316,14 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
long timestamp = payload.getLong(3);
if (timestamp < 0) throw new FormatException();
return new ContactInfo(author, properties, signature, timestamp);
if (protocolVersion >= 2) {
ContactType type =
ContactType.fromValue(payload.getLong(4).intValue());
if (type != remoteType)
throw new FormatException();
}
return new ContactInfo(author, properties, signature, timestamp,
remoteType);
}
private ContactId addContact(Author remoteAuthor, long timestamp,
......@@ -290,11 +332,30 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
ContactId contactId;
Transaction txn = db.startTransaction(false);
try {
contactId = contactManager.addContact(txn, remoteAuthor,
localAuthor.getId(), masterSecret, timestamp, alice,
true, true);
switch (remoteType) {
case CONTACT:
contactId = contactManager.addContact(txn, remoteAuthor,
localAuthor.getId(), masterSecret, timestamp, alice,
true, true);
break;
case MAILBOX_OWNER:
contactId =
contactManager.addMailboxOwner(txn, remoteAuthor,
localAuthor.getId(), masterSecret,
timestamp, alice);
break;
case PRIVATE_MAILBOX:
contactId =
contactManager.addPrivateMailbox(txn, remoteAuthor,
localAuthor.getId(), masterSecret,
timestamp, alice);
break;
default:
throw new RuntimeException("Invalid remote contact type");
}
transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties);
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
......@@ -311,21 +372,4 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
logException(LOG, WARNING, e);
}
}
private static class ContactInfo {
private final Author author;
private final Map<TransportId, TransportProperties> properties;
private final byte[] signature;
private final long timestamp;
private ContactInfo(Author author,
Map<TransportId, TransportProperties> properties,
byte[] signature, long timestamp) {
this.author = author;
this.properties = properties;
this.signature = signature;
this.timestamp = timestamp;
}
}
}
......@@ -3,6 +3,7 @@ package org.briarproject.bramble.contact;
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.contact.ContactType;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
......@@ -21,6 +22,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.ContactType.CONTACT;
import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER;
import static org.briarproject.bramble.api.contact.ContactType.PRIVATE_MAILBOX;
@ThreadSafe
@NotNullByDefault
class ContactManagerImpl implements ContactManager {
......@@ -45,19 +50,27 @@ class ContactManagerImpl implements ContactManager {
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
SecretKey master, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified, active);
ContactId c = db.addContact(txn, remote, local, verified, active,
CONTACT);
keyManager.addContact(txn, c, master, timestamp, alice, active);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
for (ContactHook hook : hooks) {
if (hook.getApplicableContactTypes().contains(contact.getType()))
hook.addingContact(txn, contact);
}
return c;
}
@Override
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
boolean verified, boolean active) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified, active);
ContactId c = db.addContact(txn, remote, local, verified, active,
CONTACT);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
for (ContactHook hook : hooks) {
if (hook.getApplicableContactTypes().contains(contact.getType()))
hook.addingContact(txn, contact);
}
return c;
}
......@@ -77,6 +90,36 @@ class ContactManagerImpl implements ContactManager {
return c;
}
@Override
public ContactId addPrivateMailbox(Transaction txn, Author remote,
AuthorId local, SecretKey master, long timestamp, boolean alice)
throws DbException {
ContactId c = db.addContact(txn, remote, local, true, true,
PRIVATE_MAILBOX);
keyManager.addContact(txn, c, master, timestamp, alice, true);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) {
if (hook.getApplicableContactTypes().contains(contact.getType()))
hook.addingContact(txn, contact);
}
return c;
}
@Override
public ContactId addMailboxOwner(Transaction txn, Author remote,
AuthorId local, SecretKey master, long timestamp, boolean alice)
throws DbException {
ContactId c = db.addContact(txn, remote, local, true, true,
MAILBOX_OWNER);
keyManager.addContact(txn, c, master, timestamp, alice, true);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) {
if (hook.getApplicableContactTypes().contains(contact.getType()))
hook.addingContact(txn, contact);
}
return c;
}
@Override
public Contact getContact(ContactId c) throws DbException {
Contact contact;
......@@ -131,6 +174,20 @@ class ContactManagerImpl implements ContactManager {
return active;
}
@Override
public Collection<Contact> getContactsByType(ContactType type)
throws DbException {
Collection<Contact> contacts;
Transaction txn = db.startTransaction(true);
try {
contacts = db.getContactsByType(txn, type);
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
}
return contacts;
}
@Override
public void removeContact(ContactId c) throws DbException {
Transaction txn = db.startTransaction(false);
......@@ -172,7 +229,10 @@ class ContactManagerImpl implements ContactManager {
public void removeContact(Transaction txn, ContactId c)
throws DbException {
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.removingContact(txn, contact);
for (ContactHook hook : hooks) {
if (hook.getApplicableContactTypes().contains(contact.getType()))
hook.removingContact(txn, contact);
}
db.removeContact(txn, c);
}
......