Implement BQP API

This commit is contained in:
str4d
2016-01-28 23:34:51 +00:00
parent ce7c189923
commit c3997fb06f
10 changed files with 890 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule;
import org.briarproject.invitation.InvitationModule;
import org.briarproject.keyagreement.KeyAgreementModule;
import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.messaging.MessagingModule;
import org.briarproject.plugins.PluginsModule;
@@ -23,7 +24,8 @@ import dagger.Module;
@Module(includes = {DatabaseModule.class,
CryptoModule.class, LifecycleModule.class, ReliabilityModule.class,
MessagingModule.class, InvitationModule.class, ForumModule.class,
MessagingModule.class, InvitationModule.class, KeyAgreementModule.class,
ForumModule.class,
IdentityModule.class, EventModule.class, DataModule.class,
ContactModule.class, PropertiesModule.class, TransportModule.class,
SyncModule.class, SettingsModule.class, ClientsModule.class,

View File

@@ -0,0 +1,23 @@
package org.briarproject.keyagreement;
class AbortException extends Exception {
public boolean receivedAbort;
public AbortException() {
this(false);
}
public AbortException(boolean receivedAbort) {
super();
this.receivedAbort = receivedAbort;
}
public AbortException(Exception e) {
this(e, false);
}
public AbortException(Exception e, boolean receivedAbort) {
super(e);
this.receivedAbort = receivedAbort;
}
}

View File

@@ -0,0 +1,236 @@
package org.briarproject.keyagreement;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.keyagreement.KeyAgreementConnection;
import org.briarproject.api.keyagreement.KeyAgreementListener;
import org.briarproject.api.keyagreement.Payload;
import org.briarproject.api.keyagreement.TransportDescriptor;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.system.Clock;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
class KeyAgreementConnector {
interface Callbacks {
void connectionWaiting();
}
private static final Logger LOG =
Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks;
private final Clock clock;
private final CryptoComponent crypto;
private final PluginManager pluginManager;
private final CompletionService<KeyAgreementConnection> connect;
private final List<KeyAgreementListener> listeners =
new ArrayList<KeyAgreementListener>();
private final List<Future<KeyAgreementConnection>> pending =
new ArrayList<Future<KeyAgreementConnection>>();
private volatile boolean connecting = false;
private volatile boolean alice = false;
public KeyAgreementConnector(Callbacks callbacks, Clock clock,
CryptoComponent crypto, PluginManager pluginManager,
Executor ioExecutor) {
this.callbacks = callbacks;
this.clock = clock;
this.crypto = crypto;
this.pluginManager = pluginManager;
connect = new ExecutorCompletionService<KeyAgreementConnection>(
ioExecutor);
}
public Payload listen(KeyPair localKeyPair) {
LOG.info("Starting BQP listeners");
// Derive commitment
byte[] commitment = crypto.deriveKeyCommitment(
localKeyPair.getPublic().getEncoded());
// Start all listeners and collect their descriptors
List<TransportDescriptor> descriptors =
new ArrayList<TransportDescriptor>();
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
KeyAgreementListener l = plugin.createKeyAgreementListener(
commitment);
if (l != null) {
TransportDescriptor d = l.getDescriptor();
descriptors.add(d);
pending.add(connect.submit(new ReadableTask(l.listen())));
listeners.add(l);
}
}
return new Payload(commitment, descriptors);
}
public void stopListening() {
LOG.info("Stopping BQP listeners");
for (KeyAgreementListener l : listeners) {
l.close();
}
listeners.clear();
}
public KeyAgreementTransport connect(Payload remotePayload,
boolean alice) {
// Let the listeners know if we are Alice
this.connecting = true;
this.alice = alice;
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
// Start connecting over supported transports
LOG.info("Starting outgoing BQP connections");
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
DuplexPlugin plugin = (DuplexPlugin) pluginManager.getPlugin(
d.getIdentifier());
if (plugin != null)
pending.add(connect.submit(new ReadableTask(
new ConnectorTask(plugin, remotePayload.getCommitment(),
d, end))));
}
// Get chosen connection
KeyAgreementConnection chosen = null;
try {
long now = clock.currentTimeMillis();
Future<KeyAgreementConnection> f =
connect.poll(end - now, MILLISECONDS);
if (f == null)
return null; // No task completed within the timeout.
chosen = f.get();
return new KeyAgreementTransport(chosen);
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for connection");
Thread.currentThread().interrupt();
return null;
} catch (ExecutionException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} finally {
stopListening();
// Close all other connections
closePending(chosen);
}
}
private void closePending(KeyAgreementConnection chosen) {
for (Future<KeyAgreementConnection> f : pending) {
try {
if (f.cancel(true))
LOG.info("Cancelled task");
else if (!f.isCancelled()) {
KeyAgreementConnection c = f.get();
if (c != null && c != chosen)
tryToClose(c.getConnection(), false);
}
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
Thread.currentThread().interrupt();
return;
} catch (ExecutionException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
try {
if (LOG.isLoggable(INFO))
LOG.info("Closing connection, exception: " + exception);
conn.getReader().dispose(exception, true);
conn.getWriter().dispose(exception);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
private class ConnectorTask implements Callable<KeyAgreementConnection> {
private final byte[] commitment;
private final TransportDescriptor descriptor;
private final long end;
private final DuplexPlugin plugin;
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
TransportDescriptor descriptor, long end) {
this.plugin = plugin;
this.commitment = commitment;
this.descriptor = descriptor;
this.end = end;
}
@Override
public KeyAgreementConnection call() throws Exception {
// Repeat attempts until we connect or get interrupted
while (true) {
long now = clock.currentTimeMillis();
DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment,
descriptor, end - now);
if (conn != null) {
if (LOG.isLoggable(INFO))
LOG.info(plugin.getId().getString() +
": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId());
}
// Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000);
}
}
}
private class ReadableTask
implements Callable<KeyAgreementConnection> {
private final Callable<KeyAgreementConnection> connectionTask;
private ReadableTask(Callable<KeyAgreementConnection> connectionTask) {
this.connectionTask = connectionTask;
}
@Override
public KeyAgreementConnection call()
throws Exception {
KeyAgreementConnection c = connectionTask.call();
InputStream in = c.getConnection().getReader().getInputStream();
boolean waitingSent = false;
while (!alice && in.available() == 0) {
if (!waitingSent && connecting && !alice) {
// Bob waits here until Alice obtains his payload.
callbacks.connectionWaiting();
waitingSent = true;
}
if (LOG.isLoggable(INFO))
LOG.info(c.getTransportId().toString() +
": Waiting for connection");
Thread.sleep(1000);
}
if (!alice && LOG.isLoggable(INFO))
LOG.info(c.getTransportId().toString() + ": Data available");
return c;
}
}
}

View File

@@ -0,0 +1,43 @@
package org.briarproject.keyagreement;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.BdfWriterFactory;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.api.keyagreement.PayloadEncoder;
import org.briarproject.api.keyagreement.PayloadParser;
import org.briarproject.api.lifecycle.IoExecutor;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.system.Clock;
import java.util.concurrent.Executor;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class KeyAgreementModule {
@Provides
@Singleton
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
CryptoComponent crypto, EventBus eventBus,
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
PluginManager pluginManager) {
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
ioExecutor, payloadEncoder, pluginManager);
}
@Provides
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) {
return new PayloadEncoderImpl(bdfWriterFactory);
}
@Provides
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
return new PayloadParserImpl(bdfReaderFactory);
}
}

View File

@@ -0,0 +1,157 @@
package org.briarproject.keyagreement;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.keyagreement.Payload;
import org.briarproject.api.keyagreement.PayloadEncoder;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
/**
* Implementation of the BQP protocol.
* <p/>
* Alice:
* <ul>
* <li>Send A_KEY</li>
* <li>Receive B_KEY
* <ul>
* <li>Check B_KEY matches B_COMMIT</li>
* </ul></li>
* <li>Calculate s</li>
* <li>Send A_CONFIRM</li>
* <li>Receive B_CONFIRM
* <ul>
* <li>Check B_CONFIRM matches expected</li>
* </ul></li>
* <li>Derive master</li>
* </ul><p/>
* Bob:
* <ul>
* <li>Receive A_KEY
* <ul>
* <li>Check A_KEY matches A_COMMIT</li>
* </ul></li>
* <li>Send B_KEY</li>
* <li>Calculate s</li>
* <li>Receive A_CONFIRM
* <ul>
* <li>Check A_CONFIRM matches expected</li>
* </ul></li>
* <li>Send B_CONFIRM</li>
* <li>Derive master</li>
* </ul>
*/
class KeyAgreementProtocol {
interface Callbacks {
void connectionWaiting();
void initialPacketReceived();
}
private Callbacks callbacks;
private CryptoComponent crypto;
private PayloadEncoder payloadEncoder;
private KeyAgreementTransport transport;
private Payload theirPayload, ourPayload;
private KeyPair ourKeyPair;
private boolean alice;
public KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
boolean alice) {
this.callbacks = callbacks;
this.crypto = crypto;
this.payloadEncoder = payloadEncoder;
this.transport = transport;
this.theirPayload = theirPayload;
this.ourPayload = ourPayload;
this.ourKeyPair = ourKeyPair;
this.alice = alice;
}
/**
* Perform the BQP protocol.
*
* @return the negotiated master secret.
* @throws AbortException when the protocol may have been tampered with.
* @throws IOException for all other other connection errors.
*/
public SecretKey perform() throws AbortException, IOException {
try {
byte[] theirPublicKey;
if (alice) {
sendKey();
// Alice waits here until Bob obtains her payload.
callbacks.connectionWaiting();
theirPublicKey = receiveKey();
} else {
theirPublicKey = receiveKey();
sendKey();
}
SecretKey s = deriveSharedSecret(theirPublicKey);
if (alice) {
sendConfirm(s, theirPublicKey);
receiveConfirm(s, theirPublicKey);
} else {
receiveConfirm(s, theirPublicKey);
sendConfirm(s, theirPublicKey);
}
return crypto.deriveMasterSecret(s);
} catch (AbortException e) {
sendAbort(e.getCause() != null);
throw e;
}
}
private void sendKey() throws IOException {
transport.sendKey(ourKeyPair.getPublic().getEncoded());
}
private byte[] receiveKey() throws AbortException {
byte[] publicKey = transport.receiveKey();
callbacks.initialPacketReceived();
byte[] expected = crypto.deriveKeyCommitment(publicKey);
if (!Arrays.equals(expected, theirPayload.getCommitment()))
throw new AbortException();
return publicKey;
}
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
throws AbortException {
try {
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
} catch (GeneralSecurityException e) {
throw new AbortException(e);
}
}
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
throws IOException {
byte[] confirm = crypto.deriveConfirmationRecord(s,
payloadEncoder.encode(theirPayload),
payloadEncoder.encode(ourPayload),
theirPublicKey, ourKeyPair,
alice, alice);
transport.sendConfirm(confirm);
}
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
throws AbortException {
byte[] confirm = transport.receiveConfirm();
byte[] expected = crypto.deriveConfirmationRecord(s,
payloadEncoder.encode(theirPayload),
payloadEncoder.encode(ourPayload),
theirPublicKey, ourKeyPair,
alice, !alice);
if (!Arrays.equals(expected, confirm))
throw new AbortException();
}
private void sendAbort(boolean exception) {
transport.sendAbort(exception);
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.keyagreement;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.KeyAgreementAbortedEvent;
import org.briarproject.api.event.KeyAgreementFailedEvent;
import org.briarproject.api.event.KeyAgreementFinishedEvent;
import org.briarproject.api.keyagreement.KeyAgreementTask;
import org.briarproject.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.api.keyagreement.PayloadEncoder;
import org.briarproject.api.lifecycle.IoExecutor;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.system.Clock;
import java.util.concurrent.Executor;
import javax.inject.Inject;
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
private final Clock clock;
private final CryptoComponent crypto;
private final EventBus eventBus;
private final Executor ioExecutor;
private final PayloadEncoder payloadEncoder;
private final PluginManager pluginManager;
@Inject
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
EventBus eventBus, @IoExecutor Executor ioExecutor,
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
this.clock = clock;
this.crypto = crypto;
this.eventBus = eventBus;
this.ioExecutor = ioExecutor;
this.payloadEncoder = payloadEncoder;
this.pluginManager = pluginManager;
}
public KeyAgreementTask getTask() {
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
pluginManager, ioExecutor);
}
}

View File

@@ -0,0 +1,135 @@
package org.briarproject.keyagreement;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.KeyAgreementAbortedEvent;
import org.briarproject.api.event.KeyAgreementFailedEvent;
import org.briarproject.api.event.KeyAgreementFinishedEvent;
import org.briarproject.api.event.KeyAgreementListeningEvent;
import org.briarproject.api.event.KeyAgreementStartedEvent;
import org.briarproject.api.event.KeyAgreementWaitingEvent;
import org.briarproject.api.keyagreement.KeyAgreementResult;
import org.briarproject.api.keyagreement.KeyAgreementTask;
import org.briarproject.api.keyagreement.Payload;
import org.briarproject.api.keyagreement.PayloadEncoder;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.system.Clock;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
class KeyAgreementTaskImpl extends Thread implements
KeyAgreementTask, KeyAgreementConnector.Callbacks,
KeyAgreementProtocol.Callbacks {
private static final Logger LOG =
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
private final CryptoComponent crypto;
private final EventBus eventBus;
private final PayloadEncoder payloadEncoder;
private final KeyPair localKeyPair;
private final KeyAgreementConnector connector;
private Payload localPayload;
private Payload remotePayload;
public KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
EventBus eventBus, PayloadEncoder payloadEncoder,
PluginManager pluginManager, Executor ioExecutor) {
this.crypto = crypto;
this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, clock, crypto,
pluginManager, ioExecutor);
}
@Override
public synchronized void listen() {
if (localPayload == null) {
localPayload = connector.listen(localKeyPair);
eventBus.broadcast(new KeyAgreementListeningEvent(localPayload));
}
}
@Override
public synchronized void stopListening() {
if (localPayload != null) {
if (remotePayload == null)
connector.stopListening();
else
interrupt();
}
}
@Override
public synchronized void connectAndRunProtocol(Payload remotePayload) {
if (this.localPayload == null)
throw new IllegalStateException(
"Must listen before connecting");
if (this.remotePayload != null)
throw new IllegalStateException(
"Already provided remote payload for this task");
this.remotePayload = remotePayload;
start();
}
@Override
public void run() {
boolean alice = localPayload.compareTo(remotePayload) < 0;
// Open connection to remote device
KeyAgreementTransport transport =
connector.connect(remotePayload, alice);
if (transport == null) {
// Notify caller that the connection failed
eventBus.broadcast(new KeyAgreementFailedEvent());
return;
}
// Run BQP protocol over the connection
LOG.info("Starting BQP protocol");
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
payloadEncoder, transport, remotePayload, localPayload,
localKeyPair, alice);
try {
SecretKey master = protocol.perform();
KeyAgreementResult result =
new KeyAgreementResult(master, transport.getConnection(),
transport.getTransportId(), alice);
LOG.info("Finished BQP protocol");
// Broadcast result to caller
eventBus.broadcast(new KeyAgreementFinishedEvent(result));
} catch (AbortException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
// Notify caller that the protocol was aborted
eventBus.broadcast(new KeyAgreementAbortedEvent(e.receivedAbort));
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
// Notify caller that the connection failed
eventBus.broadcast(new KeyAgreementFailedEvent());
}
}
@Override
public void connectionWaiting() {
eventBus.broadcast(new KeyAgreementWaitingEvent());
}
@Override
public void initialPacketReceived() {
// We send this here instead of when we create the protocol, so that
// if device A makes a connection after getting device B's payload and
// starts its protocol, device A's UI doesn't change to prevent device B
// from getting device A's payload.
eventBus.broadcast(new KeyAgreementStartedEvent());
}
}

View File

@@ -0,0 +1,131 @@
package org.briarproject.keyagreement;
import org.briarproject.api.TransportId;
import org.briarproject.api.keyagreement.KeyAgreementConnection;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.util.ByteUtils;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_PAYLOAD_LENGTH_OFFSET;
import static org.briarproject.api.keyagreement.RecordTypes.ABORT;
import static org.briarproject.api.keyagreement.RecordTypes.CONFIRM;
import static org.briarproject.api.keyagreement.RecordTypes.KEY;
/**
* Handles the sending and receiving of BQP records.
*/
class KeyAgreementTransport {
private static final Logger LOG =
Logger.getLogger(KeyAgreementTransport.class.getName());
private final KeyAgreementConnection kac;
private final InputStream in;
private final OutputStream out;
public KeyAgreementTransport(KeyAgreementConnection kac)
throws IOException {
this.kac = kac;
in = kac.getConnection().getReader().getInputStream();
out = kac.getConnection().getWriter().getOutputStream();
}
public DuplexTransportConnection getConnection() {
return kac.getConnection();
}
public TransportId getTransportId() {
return kac.getTransportId();
}
public void sendKey(byte[] key) throws IOException {
writeRecord(KEY, key);
}
public byte[] receiveKey() throws AbortException {
return readRecord(KEY);
}
public void sendConfirm(byte[] confirm) throws IOException {
writeRecord(CONFIRM, confirm);
}
public byte[] receiveConfirm() throws AbortException {
return readRecord(CONFIRM);
}
public void sendAbort(boolean exception) {
try {
writeRecord(ABORT, new byte[0]);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
exception = true;
}
tryToClose(exception);
}
public void tryToClose(boolean exception) {
try {
LOG.info("Closing connection");
kac.getConnection().getReader().dispose(exception, true);
kac.getConnection().getWriter().dispose(exception);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void writeRecord(byte type, byte[] payload) throws IOException {
byte[] recordHeader = new byte[RECORD_HEADER_LENGTH];
recordHeader[0] = PROTOCOL_VERSION;
recordHeader[1] = type;
ByteUtils.writeUint16(payload.length, recordHeader,
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
out.write(recordHeader);
out.write(payload);
out.flush();
}
private byte[] readRecord(byte type) throws AbortException {
byte[] header = readHeader();
if (header[0] != PROTOCOL_VERSION)
throw new AbortException(); // TODO handle?
if (header[1] != type) {
// Unexpected packet
throw new AbortException(header[1] == ABORT);
}
int len = ByteUtils.readUint16(header,
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
try {
return readData(len);
} catch (IOException e) {
throw new AbortException(e);
}
}
private byte[] readHeader() throws AbortException {
try {
return readData(RECORD_HEADER_LENGTH);
} catch (IOException e) {
throw new AbortException(e);
}
}
private byte[] readData(int len) throws IOException {
byte[] data = new byte[len];
int offset = 0;
while (offset < data.length) {
int read = in.read(data, offset, data.length - offset);
if (read == -1) throw new EOFException();
offset += read;
}
return data;
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.keyagreement;
import org.briarproject.api.data.BdfWriter;
import org.briarproject.api.data.BdfWriterFactory;
import org.briarproject.api.keyagreement.Payload;
import org.briarproject.api.keyagreement.PayloadEncoder;
import org.briarproject.api.keyagreement.TransportDescriptor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.inject.Inject;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
class PayloadEncoderImpl implements PayloadEncoder {
private final BdfWriterFactory bdfWriterFactory;
@Inject
public PayloadEncoderImpl(BdfWriterFactory bdfWriterFactory) {
this.bdfWriterFactory = bdfWriterFactory;
}
@Override
public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart(); // Payload start
w.writeLong(PROTOCOL_VERSION);
w.writeRaw(p.getCommitment());
w.writeListStart(); // Descriptors start
for (TransportDescriptor d : p.getTransportDescriptors()) {
w.writeListStart();
w.writeString(d.getIdentifier().getString());
w.writeDictionary(d.getProperties());
w.writeListEnd();
}
w.writeListEnd(); // Descriptors end
w.writeListEnd(); // Payload end
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException(e);
}
return out.toByteArray();
}
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.keyagreement;
import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.keyagreement.Payload;
import org.briarproject.api.keyagreement.PayloadParser;
import org.briarproject.api.keyagreement.TransportDescriptor;
import org.briarproject.api.properties.TransportProperties;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
class PayloadParserImpl implements PayloadParser {
private final BdfReaderFactory bdfReaderFactory;
@Inject
public PayloadParserImpl(BdfReaderFactory bdfReaderFactory) {
this.bdfReaderFactory = bdfReaderFactory;
}
@Override
public Payload parse(byte[] raw) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw);
BdfReader r = bdfReaderFactory.createReader(in);
r.readListStart(); // Payload start
int proto = (int) r.readLong();
if (proto != PROTOCOL_VERSION)
throw new FormatException();
byte[] commitment = r.readRaw(COMMIT_LENGTH);
if (commitment.length != COMMIT_LENGTH)
throw new FormatException();
List<TransportDescriptor> descriptors = new ArrayList<TransportDescriptor>();
r.readListStart(); // Descriptors start
while (r.hasList()) {
r.readListStart();
while (!r.hasListEnd()) {
TransportId id =
new TransportId(r.readString(MAX_PROPERTY_LENGTH));
TransportProperties p = new TransportProperties();
r.readDictionaryStart();
while (!r.hasDictionaryEnd()) {
String key = r.readString(MAX_PROPERTY_LENGTH);
String value = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value);
}
r.readDictionaryEnd();
descriptors.add(new TransportDescriptor(id, p));
}
r.readListEnd();
}
r.readListEnd(); // Descriptors end
r.readListEnd(); // Payload end
if (!r.eof())
throw new FormatException();
return new Payload(commitment, descriptors);
}
}