mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Merge branch '1537-contact-manager-pending-contacts' into 'master'
Implement contact manager methods for pending contacts Closes #1537 See merge request briar/briar!1081
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
/**
|
||||
* Thrown when data being parsed uses a protocol or format version that is not
|
||||
* supported.
|
||||
*/
|
||||
public class UnsupportedVersionException extends FormatException {
|
||||
|
||||
private final boolean tooOld;
|
||||
|
||||
public UnsupportedVersionException(boolean tooOld) {
|
||||
this.tooOld = tooOld;
|
||||
}
|
||||
|
||||
public boolean isTooOld() {
|
||||
return tooOld;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
@@ -11,17 +13,12 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ContactManager {
|
||||
|
||||
int LINK_LENGTH = 64;
|
||||
Pattern LINK_REGEX =
|
||||
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
|
||||
|
||||
/**
|
||||
* Registers a hook to be called whenever a contact is added or removed.
|
||||
* This method should be called before
|
||||
@@ -59,17 +56,23 @@ public interface ContactManager {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the static link that needs to be sent to the contact to be added.
|
||||
* Returns the handshake link that needs to be sent to a contact we want
|
||||
* to add.
|
||||
*/
|
||||
String getHandshakeLink() throws DbException;
|
||||
|
||||
/**
|
||||
* Requests a new contact to be added via the given {@code link}.
|
||||
* Creates a {@link PendingContact} from the given handshake link and
|
||||
* alias, adds it to the database and returns it.
|
||||
*
|
||||
* @param link The link received from the contact we want to add.
|
||||
* @param alias The alias the user has given this contact.
|
||||
* @param link The handshake link received from the contact we want to add
|
||||
* @param alias The alias the user has given this contact
|
||||
* @return A PendingContact representing the contact to be added
|
||||
* @throws UnsupportedVersionException If the link uses a format version
|
||||
* that is not supported
|
||||
* @throws FormatException If the link is invalid
|
||||
*/
|
||||
void addPendingContact(String link, String alias)
|
||||
PendingContact addPendingContact(String link, String alias)
|
||||
throws DbException, FormatException;
|
||||
|
||||
/**
|
||||
@@ -78,10 +81,9 @@ public interface ContactManager {
|
||||
Collection<PendingContact> getPendingContacts() throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a {@link PendingContact} that is in state
|
||||
* {@link PendingContactState FAILED}.
|
||||
* Removes a {@link PendingContact}.
|
||||
*/
|
||||
void removePendingContact(PendingContactId pendingContact) throws DbException;
|
||||
void removePendingContact(PendingContactId p) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given ID.
|
||||
@@ -92,7 +94,7 @@ public interface ContactManager {
|
||||
* Returns the contact with the given remoteAuthorId
|
||||
* that was added by the LocalAuthor with the given localAuthorId
|
||||
*
|
||||
* @throws org.briarproject.bramble.api.db.NoSuchContactException
|
||||
* @throws NoSuchContactException If the contact is not in the database
|
||||
*/
|
||||
Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||
throws DbException;
|
||||
@@ -101,7 +103,7 @@ public interface ContactManager {
|
||||
* Returns the contact with the given remoteAuthorId
|
||||
* that was added by the LocalAuthor with the given localAuthorId
|
||||
*
|
||||
* @throws org.briarproject.bramble.api.db.NoSuchContactException
|
||||
* @throws NoSuchContactException If the contact is not in the database
|
||||
*/
|
||||
Contact getContact(Transaction txn, AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public interface HandshakeLinkConstants {
|
||||
|
||||
/**
|
||||
* The current version of the handshake link format.
|
||||
*/
|
||||
int FORMAT_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The length of a base32-encoded handshake link in bytes, excluding the
|
||||
* 'briar://' prefix.
|
||||
*/
|
||||
int BASE32_LINK_BYTES = 53;
|
||||
|
||||
/**
|
||||
* The length of a raw handshake link in bytes, before base32 encoding.
|
||||
*/
|
||||
int RAW_LINK_BYTES = 33;
|
||||
|
||||
/**
|
||||
* Regular expression for matching handshake links, including or excluding
|
||||
* the 'briar://' prefix.
|
||||
*/
|
||||
Pattern LINK_REGEX =
|
||||
Pattern.compile("(briar://)?([a-z2-7]{" + BASE32_LINK_BYTES + "})");
|
||||
|
||||
/**
|
||||
* Label for hashing handshake public keys to calculate their identifiers.
|
||||
*/
|
||||
String ID_LABEL = "org.briarproject.bramble/HANDSHAKE_KEY_ID";
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.keyagreement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown when a QR code that has been scanned uses a protocol version that is
|
||||
* not supported.
|
||||
*/
|
||||
public class UnsupportedVersionException extends IOException {
|
||||
|
||||
private final boolean tooOld;
|
||||
|
||||
public UnsupportedVersionException(boolean tooOld) {
|
||||
this.tooOld = tooOld;
|
||||
}
|
||||
|
||||
public boolean isTooOld() {
|
||||
return tooOld;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class Base32 {
|
||||
|
||||
private static final char[] DIGITS = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7'
|
||||
};
|
||||
|
||||
public static String encode(byte[] b) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
int byteIndex = 0, currentCode = 0x00;
|
||||
int byteMask = 0x80, codeMask = 0x10;
|
||||
while (byteIndex < b.length) {
|
||||
if ((b[byteIndex] & byteMask) != 0) currentCode |= codeMask;
|
||||
// After every 8 bits, move on to the next byte
|
||||
if (byteMask == 0x01) {
|
||||
byteMask = 0x80;
|
||||
byteIndex++;
|
||||
} else {
|
||||
byteMask >>>= 1;
|
||||
}
|
||||
// After every 5 bits, move on to the next digit
|
||||
if (codeMask == 0x01) {
|
||||
s.append(DIGITS[currentCode]);
|
||||
codeMask = 0x10;
|
||||
currentCode = 0x00;
|
||||
} else {
|
||||
codeMask >>>= 1;
|
||||
}
|
||||
}
|
||||
// If we're part-way through a digit, output it
|
||||
if (codeMask != 0x10) s.append(DIGITS[currentCode]);
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public static byte[] decode(String s, boolean strict) {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
int digitIndex = 0, digitCount = s.length(), currentByte = 0x00;
|
||||
int byteMask = 0x80, codeMask = 0x10;
|
||||
while (digitIndex < digitCount) {
|
||||
int code = decodeDigit(s.charAt(digitIndex));
|
||||
if ((code & codeMask) != 0) currentByte |= byteMask;
|
||||
// After every 8 bits, move on to the next byte
|
||||
if (byteMask == 0x01) {
|
||||
b.write(currentByte);
|
||||
byteMask = 0x80;
|
||||
currentByte = 0x00;
|
||||
} else {
|
||||
byteMask >>>= 1;
|
||||
}
|
||||
// After every 5 bits, move on to the next digit
|
||||
if (codeMask == 0x01) {
|
||||
codeMask = 0x10;
|
||||
digitIndex++;
|
||||
} else {
|
||||
codeMask >>>= 1;
|
||||
}
|
||||
}
|
||||
// If any extra bits were used for encoding, they should all be zero
|
||||
if (strict && byteMask != 0x80 && currentByte != 0x00)
|
||||
throw new IllegalArgumentException();
|
||||
return b.toByteArray();
|
||||
}
|
||||
|
||||
private static int decodeDigit(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a';
|
||||
if (c >= '2' && c <= '7') return c - '2' + 26;
|
||||
throw new IllegalArgumentException("Not a base32 digit: " + c);
|
||||
}
|
||||
}
|
||||
@@ -153,4 +153,13 @@ public class StringUtils {
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
public static String getRandomBase32String(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
int character = random.nextInt(32);
|
||||
if (character < 26) c[i] = (char) ('a' + character);
|
||||
else c[i] = (char) ('2' + (character - 26));
|
||||
}
|
||||
return new String(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
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;
|
||||
@@ -20,19 +21,19 @@ import org.briarproject.bramble.api.transport.KeyManager;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
|
||||
@ThreadSafe
|
||||
@@ -40,19 +41,22 @@ import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
class ContactManagerImpl implements ContactManager {
|
||||
|
||||
private static final String REMOTE_CONTACT_LINK =
|
||||
"briar://" + getRandomBase32String(LINK_LENGTH);
|
||||
"briar://" + getRandomBase32String(BASE32_LINK_BYTES);
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final KeyManager keyManager;
|
||||
private final IdentityManager identityManager;
|
||||
private final PendingContactFactory pendingContactFactory;
|
||||
private final List<ContactHook> hooks;
|
||||
|
||||
@Inject
|
||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
|
||||
IdentityManager identityManager) {
|
||||
IdentityManager identityManager,
|
||||
PendingContactFactory pendingContactFactory) {
|
||||
this.db = db;
|
||||
this.keyManager = keyManager;
|
||||
this.identityManager = identityManager;
|
||||
this.pendingContactFactory = pendingContactFactory;
|
||||
hooks = new CopyOnWriteArrayList<>();
|
||||
}
|
||||
|
||||
@@ -96,34 +100,23 @@ class ContactManagerImpl implements ContactManager {
|
||||
return REMOTE_CONTACT_LINK;
|
||||
}
|
||||
|
||||
// TODO replace with real implementation
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static String getRandomBase32String(int length) {
|
||||
Random random = new Random();
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
int character = random.nextInt(32);
|
||||
if (character < 26) c[i] = (char) ('a' + character);
|
||||
else c[i] = (char) ('2' + (character - 26));
|
||||
}
|
||||
return new String(c);
|
||||
@Override
|
||||
public PendingContact addPendingContact(String link, String alias)
|
||||
throws DbException, FormatException {
|
||||
PendingContact p =
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
db.transaction(false, txn -> db.addPendingContact(txn, p));
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPendingContact(String link, String alias)
|
||||
throws DbException {
|
||||
// TODO replace with real implementation
|
||||
public Collection<PendingContact> getPendingContacts() throws DbException {
|
||||
return db.transactionWithResult(true, db::getPendingContacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PendingContact> getPendingContacts() {
|
||||
// TODO replace with real implementation
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePendingContact(PendingContactId id) throws DbException {
|
||||
// TODO replace with real implementation
|
||||
public void removePendingContact(PendingContactId p) throws DbException {
|
||||
db.transaction(false, txn -> db.removePendingContact(txn, p));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,4 +28,10 @@ public class ContactModule {
|
||||
ContactExchangeTaskImpl contactExchangeTask) {
|
||||
return contactExchangeTask;
|
||||
}
|
||||
|
||||
@Provides
|
||||
PendingContactFactory providePendingContactFactory(
|
||||
PendingContactFactoryImpl pendingContactFactory) {
|
||||
return pendingContactFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
|
||||
interface PendingContactFactory {
|
||||
|
||||
/**
|
||||
* Creates a {@link PendingContact} from the given handshake link and alias.
|
||||
*
|
||||
* @throws UnsupportedVersionException If the link uses a format version
|
||||
* that is not supported
|
||||
* @throws FormatException If the link is invalid
|
||||
*/
|
||||
PendingContact createPendingContact(String link, String alias)
|
||||
throws FormatException;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
|
||||
class PendingContactFactoryImpl implements PendingContactFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
PendingContactFactoryImpl(CryptoComponent crypto, Clock clock) {
|
||||
this.crypto = crypto;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingContact createPendingContact(String link, String alias)
|
||||
throws FormatException {
|
||||
PublicKey publicKey = parseHandshakeLink(link);
|
||||
PendingContactId id = getPendingContactId(publicKey);
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
return new PendingContact(id, publicKey.getEncoded(), alias,
|
||||
WAITING_FOR_CONNECTION, timestamp);
|
||||
}
|
||||
|
||||
private PublicKey parseHandshakeLink(String link) throws FormatException {
|
||||
Matcher matcher = LINK_REGEX.matcher(link);
|
||||
if (!matcher.find()) throw new FormatException();
|
||||
// Discard 'briar://' and anything before or after the link
|
||||
link = matcher.group(2);
|
||||
byte[] base32 = Base32.decode(link, false);
|
||||
if (base32.length != RAW_LINK_BYTES) throw new AssertionError();
|
||||
byte version = base32[0];
|
||||
if (version != FORMAT_VERSION)
|
||||
throw new UnsupportedVersionException(version < FORMAT_VERSION);
|
||||
byte[] publicKeyBytes = new byte[base32.length - 1];
|
||||
arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length);
|
||||
try {
|
||||
KeyParser parser = crypto.getAgreementKeyParser();
|
||||
return parser.parsePublicKey(publicKeyBytes);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
private PendingContactId getPendingContactId(PublicKey publicKey) {
|
||||
byte[] hash = crypto.hash(ID_LABEL, publicKey.getEncoded());
|
||||
return new PendingContactId(hash);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
|
||||
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
|
||||
@@ -47,6 +47,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final PendingContactFactory pendingContactFactory =
|
||||
context.mock(PendingContactFactory.class);
|
||||
private final ContactManager contactManager;
|
||||
private final Author remote = getAuthor();
|
||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||
@@ -56,8 +58,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
private final ContactId contactId = contact.getId();
|
||||
|
||||
public ContactManagerImplTest() {
|
||||
contactManager =
|
||||
new ContactManagerImpl(db, keyManager, identityManager);
|
||||
contactManager = new ContactManagerImpl(db, keyManager,
|
||||
identityManager, pendingContactFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_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.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PendingContactFactoryImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final KeyParser keyParser = context.mock(KeyParser.class);
|
||||
private final PublicKey publicKey = context.mock(PublicKey.class);
|
||||
|
||||
private final PendingContactFactory pendingContactFactory =
|
||||
new PendingContactFactoryImpl(crypto, clock);
|
||||
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final byte[] publicKeyBytes = getRandomBytes(RAW_LINK_BYTES - 1);
|
||||
private final byte[] idBytes = getRandomId();
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsSyntacticallyInvalidLink() throws Exception {
|
||||
pendingContactFactory.createPendingContact("briar://potato", alias);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectsLinkWithUnknownFormatVersion() throws Exception {
|
||||
String link = encodeLink(FORMAT_VERSION + 1);
|
||||
try {
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
fail();
|
||||
} catch (UnsupportedVersionException e) {
|
||||
assertFalse(e.isTooOld());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLinkWithInvalidPublicKey() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).getAgreementKeyParser();
|
||||
will(returnValue(keyParser));
|
||||
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes)));
|
||||
will(throwException(new GeneralSecurityException()));
|
||||
}});
|
||||
|
||||
pendingContactFactory.createPendingContact(encodeLink(), alias);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidLinkWithoutPrefix() throws Exception {
|
||||
testAcceptsValidLink(encodeLink());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidLinkWithPrefix() throws Exception {
|
||||
testAcceptsValidLink("briar://" + encodeLink());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidLinkWithRubbish() throws Exception {
|
||||
testAcceptsValidLink("before " + encodeLink() + " after");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidLinkWithPrefixAndRubbish() throws Exception {
|
||||
testAcceptsValidLink("before briar://" + encodeLink() + " after");
|
||||
}
|
||||
|
||||
private void testAcceptsValidLink(String link) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).getAgreementKeyParser();
|
||||
will(returnValue(keyParser));
|
||||
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes)));
|
||||
will(returnValue(publicKey));
|
||||
allowing(publicKey).getEncoded();
|
||||
will(returnValue(publicKeyBytes));
|
||||
oneOf(crypto).hash(ID_LABEL, publicKeyBytes);
|
||||
will(returnValue(idBytes));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(timestamp));
|
||||
}});
|
||||
|
||||
PendingContact p =
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
assertArrayEquals(idBytes, p.getId().getBytes());
|
||||
assertArrayEquals(publicKeyBytes, p.getPublicKey());
|
||||
assertEquals(alias, p.getAlias());
|
||||
assertEquals(WAITING_FOR_CONNECTION, p.getState());
|
||||
assertEquals(timestamp, p.getTimestamp());
|
||||
}
|
||||
|
||||
private String encodeLink() {
|
||||
return encodeLink(FORMAT_VERSION);
|
||||
}
|
||||
|
||||
private String encodeLink(int formatVersion) {
|
||||
byte[] rawLink = new byte[RAW_LINK_BYTES];
|
||||
rawLink[0] = (byte) formatVersion;
|
||||
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
|
||||
String base32 = Base32.encode(rawLink).toLowerCase();
|
||||
assertEquals(BASE32_LINK_BYTES, base32.length());
|
||||
return base32;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@ package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class Base32Test extends BrambleTestCase {
|
||||
|
||||
// Test vectors from RFC 4648
|
||||
// https://tools.ietf.org/html/rfc4648#section-10
|
||||
|
||||
@Test
|
||||
public void testEncoding() {
|
||||
assertEquals("", Base32.encode(new byte[0]));
|
||||
assertEquals("MY", Base32.encode(new byte[] {'f'}));
|
||||
assertEquals("MZXQ", Base32.encode(new byte[] {'f', 'o'}));
|
||||
assertEquals("MZXW6", Base32.encode(new byte[] {'f', 'o', 'o'}));
|
||||
assertEquals("MZXW6YQ", Base32.encode(new byte[] {'f', 'o', 'o', 'b'}));
|
||||
assertEquals("MZXW6YTB",
|
||||
Base32.encode(new byte[] {'f', 'o', 'o', 'b', 'a'}));
|
||||
assertEquals("MZXW6YTBOI",
|
||||
Base32.encode(new byte[] {'f', 'o', 'o', 'b', 'a', 'r'}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrictDecoding() {
|
||||
testDecoding(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonStrictDecoding() {
|
||||
testDecoding(false);
|
||||
}
|
||||
|
||||
private void testDecoding(boolean strict) {
|
||||
assertArrayEquals(new byte[0], Base32.decode("", strict));
|
||||
assertArrayEquals(new byte[] {'f'}, Base32.decode("MY", strict));
|
||||
assertArrayEquals(new byte[] {'f', 'o'}, Base32.decode("MZXQ", strict));
|
||||
assertArrayEquals(new byte[] {'f', 'o', 'o'},
|
||||
Base32.decode("MZXW6", strict));
|
||||
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b'},
|
||||
Base32.decode("MZXW6YQ", strict));
|
||||
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b', 'a'},
|
||||
Base32.decode("MZXW6YTB", strict));
|
||||
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b', 'a', 'r'},
|
||||
Base32.decode("MZXW6YTBOI", strict));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testStrictDecodingRejectsNonZeroUnusedBits() {
|
||||
Base32.decode("MZ", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonStrictDecodingAcceptsNonZeroUnusedBits() {
|
||||
assertArrayEquals(new byte[] {'f'}, Base32.decode("MZ", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoundTrip() {
|
||||
Random random = new Random();
|
||||
byte[] data = new byte[100 + random.nextInt(100)];
|
||||
random.nextBytes(data);
|
||||
assertArrayEquals(data, Base32.decode(Base32.encode(data), true));
|
||||
assertArrayEquals(data, Base32.decode(Base32.encode(data), false));
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -105,7 +105,7 @@ public class AddContactViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getAddContactResult() {
|
||||
LiveData<Boolean> getAddContactResult() {
|
||||
return addContactResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import javax.inject.Inject;
|
||||
import static android.content.Context.CLIPBOARD_SERVICE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
||||
@@ -22,7 +23,6 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
||||
|
||||
Reference in New Issue
Block a user