From 8fc622f85da7948ebc5bda76eb6c733c15c2ac24 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Wed, 24 Oct 2018 18:10:53 -0300
Subject: [PATCH] [bramble] Add support for contact aliases

Foundation for #41
---
 .../bramble/api/contact/Contact.java          | 19 ++++-
 .../bramble/api/contact/ContactManager.java   |  8 ++
 .../bramble/api/db/DatabaseComponent.java     |  6 ++
 .../bramble/contact/ContactManagerImpl.java   | 15 ++++
 .../org/briarproject/bramble/db/Database.java |  6 ++
 .../bramble/db/DatabaseComponentImpl.java     | 10 +++
 .../briarproject/bramble/db/JdbcDatabase.java | 75 +++++++++++++------
 .../bramble/db/Migration40_41.java            | 51 +++++++++++++
 .../contact/ContactManagerImplTest.java       | 26 ++++++-
 .../bramble/db/DatabaseComponentImplTest.java | 21 +++++-
 .../bramble/db/JdbcDatabaseTest.java          | 35 +++++++++
 .../identity/IdentityManagerImplTest.java     |  5 +-
 .../TransportPropertyManagerImplTest.java     |  3 +-
 .../bramble/test/DbExpectations.java          | 16 ++++
 .../bramble/test/RunTransactionAction.java    | 30 ++++++++
 .../bramble/transport/KeyManagerImplTest.java |  7 +-
 .../ClientVersioningManagerImplTest.java      |  4 +-
 .../briar/blog/BlogManagerImplTest.java       |  4 +-
 .../AbstractProtocolEngineTest.java           |  2 +-
 .../GroupInvitationManagerImplTest.java       |  6 +-
 .../invitation/InviteeProtocolEngineTest.java |  2 +-
 .../sharing/BlogSharingManagerImplTest.java   |  4 +-
 .../briar/headless/ControllerTest.kt          |  3 +-
 23 files changed, 312 insertions(+), 46 deletions(-)
 create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java
 create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/DbExpectations.java
 create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java

diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/Contact.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/Contact.java
index 1d921e8017..558035a52d 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/Contact.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/Contact.java
@@ -4,8 +4,12 @@ import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
+import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.bramble.util.StringUtils.toUtf8;
+
 @Immutable
 @NotNullByDefault
 public class Contact {
@@ -13,13 +17,21 @@ public class Contact {
 	private final ContactId id;
 	private final Author author;
 	private final AuthorId localAuthorId;
+	@Nullable
+	private final String alias;
 	private final boolean verified, active;
 
 	public Contact(ContactId id, Author author, AuthorId localAuthorId,
-			boolean verified, boolean active) {
+			@Nullable String alias, boolean verified, boolean active) {
+		if (alias != null) {
+			int aliasLength = toUtf8(alias).length;
+			if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
+				throw new IllegalArgumentException();
+		}
 		this.id = id;
 		this.author = author;
 		this.localAuthorId = localAuthorId;
+		this.alias = alias;
 		this.verified = verified;
 		this.active = active;
 	}
@@ -36,6 +48,11 @@ public class Contact {
 		return localAuthorId;
 	}
 
+	@Nullable
+	public String getAlias() {
+		return alias;
+	}
+
 	public boolean isVerified() {
 		return verified;
 	}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
index 517dcb89f3..2f4a21800d 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
@@ -10,6 +10,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
 import java.util.Collection;
 
+import javax.annotation.Nullable;
+
 @NotNullByDefault
 public interface ContactManager {
 
@@ -93,6 +95,12 @@ public interface ContactManager {
 	void setContactActive(Transaction txn, ContactId c, boolean active)
 			throws DbException;
 
+	/**
+	 * Sets an alias name for the contact or unsets it if alias is null.
+	 */
+	void setContactAlias(ContactId c, @Nullable String alias)
+			throws DbException;
+
 	/**
 	 * Return true if a contact with this name and public key already exists
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
index 3fff5f9d62..681e8ec016 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
@@ -515,6 +515,12 @@ public interface DatabaseComponent {
 	void setContactActive(Transaction txn, ContactId c, boolean active)
 			throws DbException;
 
+	/**
+	 * Sets an alias name for the contact or unsets it if alias is null.
+	 */
+	void setContactAlias(Transaction txn, ContactId c, @Nullable String alias)
+			throws DbException;
+
 	/**
 	 * Sets the given group's visibility to the given contact.
 	 */
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
index 02c94fc750..a6397f6599 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
@@ -18,9 +18,13 @@ import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.bramble.util.StringUtils.toUtf8;
+
 @ThreadSafe
 @NotNullByDefault
 class ContactManagerImpl implements ContactManager {
@@ -148,6 +152,17 @@ class ContactManagerImpl implements ContactManager {
 		db.setContactActive(txn, c, active);
 	}
 
+	@Override
+	public void setContactAlias(ContactId c, @Nullable String alias)
+			throws DbException {
+		if (alias != null) {
+			int aliasLength = toUtf8(alias).length;
+			if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
+				throw new IllegalArgumentException();
+		}
+		db.transaction(false, txn -> db.setContactAlias(txn, c, alias));
+	}
+
 	@Override
 	public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
 			AuthorId localAuthorId) throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
index ca958a7ed5..45764418bf 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
@@ -616,6 +616,12 @@ interface Database<T> {
 	void setContactActive(T txn, ContactId c, boolean active)
 			throws DbException;
 
+	/**
+	 * Sets an alias name for a contact.
+	 */
+	void setContactAlias(T txn, ContactId c, @Nullable String alias)
+			throws DbException;
+
 	/**
 	 * Sets the given group's visibility to the given contact to either
 	 * {@link Visibility VISIBLE} or {@link Visibility SHARED}.
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
index 829433ba5c..f6fbcd7e7f 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
@@ -859,6 +859,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		transaction.attach(new ContactStatusChangedEvent(c, active));
 	}
 
+	@Override
+	public void setContactAlias(Transaction transaction, ContactId c,
+			String alias) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
+		T txn = unbox(transaction);
+		if (!db.containsContact(txn, c))
+			throw new NoSuchContactException();
+		db.setContactAlias(txn, c, alias);
+	}
+
 	@Override
 	public void setGroupVisibility(Transaction transaction, ContactId c,
 			GroupId g, Visibility v) throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index c6e2f7bc9b..e9d388d9fa 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -56,6 +56,7 @@ import java.util.logging.Logger;
 import javax.annotation.Nullable;
 
 import static java.sql.Types.INTEGER;
+import static java.sql.Types.VARCHAR;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.db.Metadata.REMOVE;
@@ -83,7 +84,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
 abstract class JdbcDatabase implements Database<Connection> {
 
 	// Package access for testing
-	static final int CODE_SCHEMA_VERSION = 40;
+	static final int CODE_SCHEMA_VERSION = 41;
 
 	// Rotation period offsets for incoming transport keys
 	private static final int OFFSET_PREV = -1;
@@ -113,6 +114,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " authorId _HASH NOT NULL,"
 					+ " formatVersion INT NOT NULL,"
 					+ " name _STRING NOT NULL,"
+					+ " alias _STRING," // Null if no alias exists
 					+ " publicKey _BINARY NOT NULL,"
 					+ " localAuthorId _HASH NOT NULL,"
 					+ " verified BOOLEAN NOT NULL,"
@@ -427,7 +429,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	// Package access for testing
 	List<Migration<Connection>> getMigrations() {
-		return Arrays.asList(new Migration38_39(), new Migration39_40());
+		return Arrays.asList(
+				new Migration38_39(),
+				new Migration39_40(),
+				new Migration40_41()
+		);
 	}
 
 	private boolean isCompactionDue(Settings s) {
@@ -1258,8 +1264,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, formatVersion, name, publicKey,"
-					+ " localAuthorId, verified, active"
+			String sql = "SELECT authorId, formatVersion, name, alias,"
+					+ " publicKey, localAuthorId, verified, active"
 					+ " FROM contacts"
 					+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
@@ -1269,15 +1275,17 @@ abstract class JdbcDatabase implements Database<Connection> {
 			AuthorId authorId = new AuthorId(rs.getBytes(1));
 			int formatVersion = rs.getInt(2);
 			String name = rs.getString(3);
-			byte[] publicKey = rs.getBytes(4);
-			AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
-			boolean verified = rs.getBoolean(6);
-			boolean active = rs.getBoolean(7);
+			String alias = rs.getString(4);
+			byte[] publicKey = rs.getBytes(5);
+			AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
+			boolean verified = rs.getBoolean(7);
+			boolean active = rs.getBoolean(8);
 			rs.close();
 			ps.close();
 			Author author =
 					new Author(authorId, formatVersion, name, publicKey);
-			return new Contact(c, author, localAuthorId, verified, active);
+			return new Contact(c, author, localAuthorId, alias, verified,
+					active);
 		} catch (SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1292,7 +1300,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT contactId, authorId, formatVersion, name,"
-					+ " publicKey, localAuthorId, verified, active"
+					+ " alias, publicKey, localAuthorId, verified, active"
 					+ " FROM contacts";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
@@ -1302,14 +1310,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 				AuthorId authorId = new AuthorId(rs.getBytes(2));
 				int formatVersion = rs.getInt(3);
 				String name = rs.getString(4);
-				byte[] publicKey = rs.getBytes(5);
+				String alias = rs.getString(5);
+				byte[] publicKey = rs.getBytes(6);
 				Author author =
 						new Author(authorId, formatVersion, name, publicKey);
-				AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
-				boolean verified = rs.getBoolean(7);
-				boolean active = rs.getBoolean(8);
+				AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
+				boolean verified = rs.getBoolean(8);
+				boolean active = rs.getBoolean(9);
 				contacts.add(new Contact(contactId, author, localAuthorId,
-						verified, active));
+						alias, verified, active));
 			}
 			rs.close();
 			ps.close();
@@ -1350,8 +1359,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT contactId, formatVersion, name, publicKey,"
-					+ " localAuthorId, verified, active"
+			String sql = "SELECT contactId, formatVersion, name, alias,"
+					+ " publicKey, localAuthorId, verified, active"
 					+ " FROM contacts"
 					+ " WHERE authorId = ?";
 			ps = txn.prepareStatement(sql);
@@ -1362,14 +1371,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 				ContactId c = new ContactId(rs.getInt(1));
 				int formatVersion = rs.getInt(2);
 				String name = rs.getString(3);
-				byte[] publicKey = rs.getBytes(4);
-				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
-				boolean verified = rs.getBoolean(6);
-				boolean active = rs.getBoolean(7);
+				String alias = rs.getString(4);
+				byte[] publicKey = rs.getBytes(5);
+				AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
+				boolean verified = rs.getBoolean(7);
+				boolean active = rs.getBoolean(8);
 				Author author =
 						new Author(remote, formatVersion, name, publicKey);
-				contacts.add(new Contact(c, author, localAuthorId, verified,
-						active));
+				contacts.add(new Contact(c, author, localAuthorId, alias,
+						verified, active));
 			}
 			rs.close();
 			ps.close();
@@ -2794,6 +2804,25 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	@Override
+	public void setContactAlias(Connection txn, ContactId c,
+			@Nullable String alias) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE contacts SET alias = ? WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			if (alias == null) ps.setNull(1, VARCHAR);
+			else ps.setString(1, alias);
+			ps.setInt(2, c.getInt());
+			int affected = ps.executeUpdate();
+			if (affected < 0 || affected > 1) throw new DbStateException();
+			ps.close();
+		} catch (SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	@Override
 	public void setGroupVisibility(Connection txn, ContactId c, GroupId g,
 			boolean shared) throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java
new file mode 100644
index 0000000000..4d5ecea18a
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java
@@ -0,0 +1,51 @@
+package org.briarproject.bramble.db;
+
+import org.briarproject.bramble.api.db.DbException;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.util.LogUtils.logException;
+
+class Migration40_41 implements Migration<Connection> {
+
+	private static final Logger LOG = getLogger(Migration40_41.class.getName());
+
+	@Override
+	public int getStartVersion() {
+		return 40;
+	}
+
+	@Override
+	public int getEndVersion() {
+		return 41;
+	}
+
+	@Override
+	public void migrate(Connection txn) throws DbException {
+		Statement s = null;
+		try {
+			s = txn.createStatement();
+			s.execute("ALTER TABLE contacts"
+					// TODO how to insertTypeNames _STRING ?
+					+ " ADD alias VARCHAR");
+		} catch (SQLException e) {
+			tryToClose(s);
+			throw new DbException(e);
+		}
+	}
+
+	private void tryToClose(@Nullable Statement s) {
+		try {
+			if (s != null) s.close();
+		} catch (SQLException e) {
+			logException(LOG, WARNING, e);
+		}
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java
index a90d685b18..e2a997f426 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.transport.KeyManager;
 import org.briarproject.bramble.test.BrambleMockTestCase;
+import org.briarproject.bramble.test.DbExpectations;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Test;
@@ -20,9 +21,11 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Random;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -35,9 +38,10 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
 	private final ContactId contactId = new ContactId(42);
 	private final Author remote = getAuthor();
 	private final AuthorId local = new AuthorId(getRandomId());
+	private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
 	private final boolean verified = false, active = true;
 	private final Contact contact =
-			new Contact(contactId, remote, local, verified, active);
+			new Contact(contactId, remote, local, alias, verified, active);
 
 	public ContactManagerImplTest() {
 		contactManager = new ContactManagerImpl(db, keyManager);
@@ -131,7 +135,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
 	public void testActiveContacts() throws Exception {
 		Collection<Contact> activeContacts = Collections.singletonList(contact);
 		Collection<Contact> contacts = new ArrayList<>(activeContacts);
-		contacts.add(new Contact(new ContactId(3), remote, local, true, false));
+		contacts.add(new Contact(new ContactId(3), remote, local, alias, true,
+				false));
 		Transaction txn = new Transaction(null, true);
 		context.checking(new Expectations() {{
 			oneOf(db).startTransaction(true);
@@ -171,6 +176,23 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
 		contactManager.setContactActive(txn, contactId, active);
 	}
 
+	@Test
+	public void testSetContactAlias() throws Exception {
+		Transaction txn = new Transaction(null, false);
+		context.checking(new DbExpectations() {{
+			oneOf(db).transaction(with(equal(false)), withDbRunnable(txn));
+			oneOf(db).setContactAlias(txn, contactId, alias);
+		}});
+
+		contactManager.setContactAlias(contactId, alias);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testSetContactAliasTooLong() throws Exception {
+		contactManager.setContactAlias(contactId,
+				getRandomString(MAX_AUTHOR_NAME_LENGTH + 1));
+	}
+
 	@Test
 	public void testContactExists() throws Exception {
 		Transaction txn = new Transaction(null, true);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index 5096b91ec6..acf3a0212d 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -77,6 +77,7 @@ import static org.briarproject.bramble.test.TestUtils.getMessage;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -99,6 +100,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 	private final Group group;
 	private final Author author;
 	private final LocalAuthor localAuthor;
+	private final String alias;
 	private final Message message, message1;
 	private final MessageId messageId, messageId1;
 	private final Metadata metadata;
@@ -115,6 +117,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		groupId = group.getId();
 		author = getAuthor();
 		localAuthor = getLocalAuthor();
+		alias = getRandomString(5);
 		message = getMessage(groupId);
 		message1 = getMessage(groupId);
 		messageId = message.getId();
@@ -124,7 +127,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		transportId = getTransportId();
 		maxLatency = Integer.MAX_VALUE;
 		contactId = new ContactId(234);
-		contact = new Contact(contactId, author, localAuthor.getId(),
+		contact = new Contact(contactId, author, localAuthor.getId(), alias,
 				true, true);
 		keySetId = new KeySetId(345);
 	}
@@ -288,11 +291,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			throws Exception {
 		context.checking(new Expectations() {{
 			// Check whether the contact is in the DB (which it's not)
-			exactly(16).of(database).startTransaction();
+			exactly(17).of(database).startTransaction();
 			will(returnValue(txn));
-			exactly(16).of(database).containsContact(txn, contactId);
+			exactly(17).of(database).containsContact(txn, contactId);
 			will(returnValue(false));
-			exactly(16).of(database).abortTransaction(txn);
+			exactly(17).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
@@ -450,6 +453,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			db.endTransaction(transaction);
 		}
 
+		transaction = db.startTransaction(false);
+		try {
+			db.setContactAlias(transaction, contactId, alias);
+			fail();
+		} catch (NoSuchContactException expected) {
+			// Expected
+		} finally {
+			db.endTransaction(transaction);
+		}
+
 		transaction = db.startTransaction(false);
 		try {
 			db.setGroupVisibility(transaction, contactId, groupId, SHARED);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
index 05f8d0fa44..ba032aa725 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
@@ -53,6 +53,7 @@ import static java.util.Collections.singletonList;
 import static java.util.Collections.singletonMap;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.briarproject.bramble.api.db.Metadata.REMOVE;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -74,6 +75,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -1713,6 +1715,39 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		db.close();
 	}
 
+	@Test
+	public void testSetContactAlias() throws Exception {
+		Database<Connection> db = open(false);
+		Connection txn = db.startTransaction();
+
+		// Add a contact
+		db.addLocalAuthor(txn, localAuthor);
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
+				true, true));
+
+		// The contact should have no alias
+		Contact contact = db.getContact(txn, contactId);
+		assertNull(contact.getAlias());
+
+		// Set a contact alias
+		String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
+		db.setContactAlias(txn, contactId, alias);
+
+		// The contact should have an alias
+		contact = db.getContact(txn, contactId);
+		assertEquals(alias, contact.getAlias());
+
+		// Set the contact alias null
+		db.setContactAlias(txn, contactId, null);
+
+		// The contact should have no alias
+		contact = db.getContact(txn, contactId);
+		assertNull(contact.getAlias());
+
+		db.commitTransaction(txn);
+		db.close();
+	}
+
 	@Test
 	public void testSetMessageState() throws Exception {
 		Database<Connection> db = open(false);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
index 2926cd3f3e..2d228cbdb9 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
@@ -29,6 +29,7 @@ import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
 import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 
 public class IdentityManagerImplTest extends BrambleMockTestCase {
@@ -126,7 +127,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 
 		// add one unverified contact
 		Contact contact = new Contact(new ContactId(1), author,
-				localAuthor.getId(), false, true);
+				localAuthor.getId(), getRandomString(5), false, true);
 		contacts.add(contact);
 
 		checkAuthorStatusContext(authorId, contacts);
@@ -134,7 +135,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 
 		// add one verified contact
 		Contact contact2 = new Contact(new ContactId(1), author,
-				localAuthor.getId(), true, true);
+				localAuthor.getId(), getRandomString(5), true, true);
 		contacts.add(contact2);
 
 		checkAuthorStatusContext(authorId, contacts);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java
index 99b444a708..8f6345e03a 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java
@@ -39,6 +39,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
 import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getMessage;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -612,7 +613,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
 	private Contact getContact(boolean active) {
 		ContactId c = new ContactId(nextContactId++);
 		return new Contact(c, getAuthor(), localAuthor.getId(),
-				true, active);
+				getRandomString(5), true, active);
 	}
 
 	private void expectGetLocalProperties(Transaction txn) throws Exception {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/DbExpectations.java b/bramble-core/src/test/java/org/briarproject/bramble/test/DbExpectations.java
new file mode 100644
index 0000000000..4493552d3a
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/DbExpectations.java
@@ -0,0 +1,16 @@
+package org.briarproject.bramble.test;
+
+import org.briarproject.bramble.api.db.DbRunnable;
+import org.briarproject.bramble.api.db.Transaction;
+import org.jmock.Expectations;
+
+public class DbExpectations extends Expectations {
+
+	protected <E extends Exception> DbRunnable<E> withDbRunnable(
+			Transaction txn) {
+		addParameterMatcher(any(DbRunnable.class));
+		currentBuilder().setAction(new RunTransactionAction(txn));
+		return null;
+	}
+
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java
new file mode 100644
index 0000000000..7ee016201e
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java
@@ -0,0 +1,30 @@
+package org.briarproject.bramble.test;
+
+import org.briarproject.bramble.api.db.DbRunnable;
+import org.briarproject.bramble.api.db.Transaction;
+import org.hamcrest.Description;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+
+public class RunTransactionAction implements Action {
+
+	private final Transaction txn;
+
+	@SuppressWarnings("WeakerAccess")
+	public RunTransactionAction(Transaction txn) {
+		this.txn = txn;
+	}
+
+	@Override
+	public Object invoke(Invocation invocation) throws Throwable {
+		DbRunnable task = (DbRunnable) invocation.getParameter(1);
+		task.run(txn);
+		return null;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText("runs a task inside a database transaction");
+	}
+
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
index dd91eb2379..c11ca07914 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
@@ -33,6 +33,7 @@ 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.bramble.test.TestUtils.getTransportId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 
 public class KeyManagerImplTest extends BrambleMockTestCase {
@@ -66,10 +67,10 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
 		Author remoteAuthor = getAuthor();
 		AuthorId localAuthorId = new AuthorId(getRandomId());
 		Collection<Contact> contacts = new ArrayList<>();
-		contacts.add(new Contact(contactId, remoteAuthor, localAuthorId, true,
-				true));
+		contacts.add(new Contact(contactId, remoteAuthor, localAuthorId,
+				getRandomString(5), true, true));
 		contacts.add(new Contact(inactiveContactId, remoteAuthor, localAuthorId,
-				true, false));
+				getRandomString(5), true, false));
 		SimplexPluginFactory pluginFactory =
 				context.mock(SimplexPluginFactory.class);
 		Collection<SimplexPluginFactory> factories =
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java
index 7494873d9c..2a74c1e98c 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java
@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
 import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getMessage;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
 import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
 import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -56,7 +57,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
 	private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
 	private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
 	private final Contact contact = new Contact(new ContactId(123),
-			getAuthor(), getLocalAuthor().getId(), true, true);
+			getAuthor(), getLocalAuthor().getId(), getRandomString(5), true,
+			true);
 	private final ClientId clientId = getClientId();
 	private final long now = System.currentTimeMillis();
 	private final Transaction txn = new Transaction(null, false);
diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
index a0f16d45b6..0f1f94e5bd 100644
--- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
@@ -124,7 +124,7 @@ public class BlogManagerImplTest extends BriarTestCase {
 
 		ContactId contactId = new ContactId(0);
 		Contact contact = new Contact(contactId, blog2.getAuthor(),
-				blog1.getAuthor().getId(), true, true);
+				blog1.getAuthor().getId(), getRandomString(5), true, true);
 
 		context.checking(new Expectations() {{
 			oneOf(blogFactory).createBlog(blog2.getAuthor());
@@ -146,7 +146,7 @@ public class BlogManagerImplTest extends BriarTestCase {
 
 		ContactId contactId = new ContactId(0);
 		Contact contact = new Contact(contactId, blog2.getAuthor(),
-				blog1.getAuthor().getId(), true, true);
+				blog1.getAuthor().getId(), getRandomString(5), true, true);
 
 		context.checking(new Expectations() {{
 			oneOf(blogFactory).createBlog(blog2.getAuthor());
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java
index 2c2d1e6354..95158cfbd1 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java
@@ -83,7 +83,7 @@ abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
 			BdfDictionary.of(new BdfEntry("me", "ta"));
 	final ContactId contactId = new ContactId(5);
 	final Contact contact = new Contact(contactId, author,
-			new AuthorId(getRandomId()), true, true);
+			new AuthorId(getRandomId()), getRandomString(5), true, true);
 
 	final InviteMessage inviteMessage =
 			new InviteMessage(new MessageId(getRandomId()), contactGroupId,
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java
index 8c06d942e6..801ec176df 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java
@@ -100,7 +100,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
 	private final ContactId contactId = new ContactId(0);
 	private final Author author = getAuthor();
 	private final Contact contact = new Contact(contactId, author,
-			new AuthorId(getRandomId()), true, true);
+			new AuthorId(getRandomId()), getRandomString(5), true, true);
 	private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
 	private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
 	private final Group privateGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
@@ -845,9 +845,9 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testRemovingGroupEndsSessions() throws Exception {
 		Contact contact2 = new Contact(new ContactId(2), author,
-				author.getId(), true, true);
+				author.getId(), getRandomString(5), true, true);
 		Contact contact3 = new Contact(new ContactId(3), author,
-				author.getId(), true, true);
+				author.getId(), getRandomString(5), true, true);
 		Collection<Contact> contacts =
 				Arrays.asList(contact, contact2, contact3);
 
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
index 4937695b2a..b620a6c193 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
@@ -331,7 +331,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
 						signature);
 		Author notCreator = getAuthor();
 		Contact notCreatorContact = new Contact(contactId, notCreator,
-				localAuthor.getId(), true, true);
+				localAuthor.getId(), getRandomString(5), true, true);
 
 		expectGetContactId();
 		context.checking(new Expectations() {{
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java
index c44c1e23f1..c6422814e5 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java
@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
 import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getMessage;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
 import static org.briarproject.briar.api.blog.BlogSharingManager.MAJOR_VERSION;
 import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
@@ -63,7 +64,8 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase {
 	private final ContactId contactId = new ContactId(0);
 	private final Author author = getAuthor();
 	private final Contact contact =
-			new Contact(contactId, author, localAuthor.getId(), true, true);
+			new Contact(contactId, author, localAuthor.getId(),
+					getRandomString(5), true, true);
 	private final Collection<Contact> contacts =
 			Collections.singletonList(contact);
 	private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt
index dc07391097..a2a8655838 100644
--- a/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt
+++ b/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt
@@ -36,7 +36,8 @@ abstract class ControllerTest {
     protected val group: Group = getGroup(getClientId(), 0)
     protected val author: Author = getAuthor()
     protected val localAuthor: LocalAuthor = getLocalAuthor()
-    protected val contact = Contact(ContactId(1), author, localAuthor.id, true, true)
+    protected val contact =
+        Contact(ContactId(1), author, localAuthor.id, getRandomString(5), true, true)
     protected val message: Message = getMessage(group.id)
     protected val text: String = getRandomString(5)
     protected val timestamp = 42L
-- 
GitLab