Massive refactoring to merge handling of simplex and duplex connections.

This commit is contained in:
akwizgran
2014-11-04 16:51:25 +00:00
parent b24f153704
commit 7b8181e309
67 changed files with 1981 additions and 2288 deletions

View File

@@ -3,8 +3,11 @@ package org.briarproject.plugins.droidtooth;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
@@ -13,30 +16,69 @@ class DroidtoothTransportConnection implements DuplexTransportConnection {
private final Plugin plugin; private final Plugin plugin;
private final BluetoothSocket socket; private final BluetoothSocket socket;
private final Reader reader;
private final Writer writer;
private final AtomicBoolean halfClosed, closed;
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) { DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
this.plugin = plugin; this.plugin = plugin;
this.socket = socket; this.socket = socket;
reader = new Reader();
writer = new Writer();
halfClosed = new AtomicBoolean(false);
closed = new AtomicBoolean(false);
} }
public int getMaxFrameLength() { public TransportConnectionReader getReader() {
return plugin.getMaxFrameLength(); return reader;
} }
public long getMaxLatency() { public TransportConnectionWriter getWriter() {
return plugin.getMaxLatency(); return writer;
} }
public InputStream getInputStream() throws IOException { private class Reader implements TransportConnectionReader {
return socket.getInputStream();
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) socket.close();
}
} }
public OutputStream getOutputStream() throws IOException { private class Writer implements TransportConnectionWriter {
return socket.getOutputStream();
}
public void dispose(boolean exception, boolean recognised) public int getMaxFrameLength() {
throws IOException { return plugin.getMaxFrameLength();
socket.close(); }
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public long getCapacity() {
return Long.MAX_VALUE;
}
public OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
}
public void dispose(boolean exception) throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) socket.close();
}
} }
} }

View File

@@ -4,38 +4,80 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;
import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
class TorTransportConnection implements DuplexTransportConnection { class TorTransportConnection implements DuplexTransportConnection {
private final Plugin plugin; private final Plugin plugin;
private final Socket socket; private final Socket socket;
private final Reader reader;
private final Writer writer;
private final AtomicBoolean halfClosed, closed;
TorTransportConnection(Plugin plugin, Socket socket) { TorTransportConnection(Plugin plugin, Socket socket) {
this.plugin = plugin; this.plugin = plugin;
this.socket = socket; this.socket = socket;
reader = new Reader();
writer = new Writer();
halfClosed = new AtomicBoolean(false);
closed = new AtomicBoolean(false);
} }
public int getMaxFrameLength() { public TransportConnectionReader getReader() {
return plugin.getMaxFrameLength(); return reader;
} }
public long getMaxLatency() { public TransportConnectionWriter getWriter() {
return plugin.getMaxLatency(); return writer;
} }
public InputStream getInputStream() throws IOException { private class Reader implements TransportConnectionReader {
return socket.getInputStream();
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) socket.close();
}
} }
public OutputStream getOutputStream() throws IOException { private class Writer implements TransportConnectionWriter {
return socket.getOutputStream();
}
public void dispose(boolean exception, boolean recognised) public int getMaxFrameLength() {
throws IOException { return plugin.getMaxFrameLength();
socket.close(); }
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public long getCapacity() {
return Long.MAX_VALUE;
}
public OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
}
public void dispose(boolean exception) throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) socket.close();
}
} }
} }

View File

@@ -72,13 +72,10 @@ public interface CryptoComponent {
/** /**
* Derives a frame key from the given temporary secret and stream number. * Derives a frame key from the given temporary secret and stream number.
* @param alice indicates whether the key is for a connection initiated by * @param alice indicates whether the key is for a stream initiated by
* Alice or Bob. * Alice or Bob.
* @param initiator indicates whether the key is for the initiator's or the
* responder's side of the connection.
*/ */
SecretKey deriveFrameKey(byte[] secret, long streamNumber, boolean alice, SecretKey deriveFrameKey(byte[] secret, long streamNumber, boolean alice);
boolean initiator);
/** Returns a cipher for encrypting and authenticating frames. */ /** Returns a cipher for encrypting and authenticating frames. */
AuthenticatedCipher getFrameCipher(); AuthenticatedCipher getFrameCipher();

View File

@@ -0,0 +1,19 @@
package org.briarproject.api.messaging;
import java.io.IOException;
public interface MessagingSession {
/**
* Runs the session. This method returns when there are no more packets to
* send or when the {@link #interrupt()} method has been called.
*/
void run() throws IOException;
/**
* Interrupts the session, causing the {@link #run()} method to return at
* the next opportunity or throw an {@link java.io.IOException IOException}
* if it cannot return cleanly.
*/
void interrupt();
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.api.messaging;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.transport.StreamContext;
public interface MessagingSessionFactory {
MessagingSession createIncomingSession(StreamContext ctx,
TransportConnectionReader r);
MessagingSession createOutgoingSession(StreamContext ctx,
TransportConnectionWriter w, boolean duplex);
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.api.messaging.duplex;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.transport.StreamContext;
public interface DuplexConnectionFactory {
void createIncomingConnection(StreamContext ctx,
DuplexTransportConnection d);
void createOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d);
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.api.messaging.simplex;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.transport.StreamContext;
public interface SimplexConnectionFactory {
void createIncomingConnection(StreamContext ctx,
SimplexTransportReader r);
void createOutgoingConnection(ContactId c, TransportId t,
SimplexTransportWriter w);
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.api.plugins;
import java.io.IOException;
import java.io.InputStream;
/**
* An interface for reading data from a transport connection. The reader is not
* responsible for decrypting or authenticating the data.
*/
public interface TransportConnectionReader {
/** Returns the maximum frame length of the transport in bytes. */
int getMaxFrameLength();
/** Returns the maximum latency of the transport in milliseconds. */
long getMaxLatency();
/** Returns an input stream for reading from the transport connection. */
InputStream getInputStream() throws IOException;
/**
* Marks this side of the transport connection closed. If the transport is
* simplex, the connection is closed. If the transport is duplex, the
* connection is closed if <tt>exception</tt> is true or the other side of
* the connection has been marked as closed.
* @param exception true if the connection is being closed because of an
* exception. This may affect how resources are disposed of.
* @param recognised true if the pseudo-random tag was recognised. This may
* affect how resources are disposed of.
*/
void dispose(boolean exception, boolean recognised) throws IOException;
}

View File

@@ -0,0 +1,33 @@
package org.briarproject.api.plugins;
import java.io.IOException;
import java.io.OutputStream;
/**
* An interface for writing data to a transport connection. The writer is not
* responsible for authenticating or encrypting the data.
*/
public interface TransportConnectionWriter {
/** Returns the maximum frame length of the transport in bytes. */
int getMaxFrameLength();
/** Returns the maximum latency of the transport in milliseconds. */
long getMaxLatency();
/** Returns the capacity of the transport connection in bytes. */
long getCapacity();
/** Returns an output stream for writing to the transport connection. */
OutputStream getOutputStream() throws IOException;
/**
* Marks this side of the transport connection closed. If the transport is
* simplex, the connection is closed. If the transport is duplex, the
* connection is closed if <tt>exception</tt> is true or the other side of
* the connection has been marked as closed.
* @param exception true if the connection is being closed because of an
* exception. This may affect how resources are disposed of.
*/
void dispose(boolean exception) throws IOException;
}

View File

@@ -1,8 +1,7 @@
package org.briarproject.api.plugins.duplex; package org.briarproject.api.plugins.duplex;
import java.io.IOException; import org.briarproject.api.plugins.TransportConnectionReader;
import java.io.InputStream; import org.briarproject.api.plugins.TransportConnectionWriter;
import java.io.OutputStream;
/** /**
* An interface for reading and writing data over a duplex transport. The * An interface for reading and writing data over a duplex transport. The
@@ -11,23 +10,11 @@ import java.io.OutputStream;
*/ */
public interface DuplexTransportConnection { public interface DuplexTransportConnection {
/** Returns the maximum frame length of the transport in bytes. */ /** Returns a {@link org.briarproject.api.plugins.TransportConnectionReader
int getMaxFrameLength(); * TransportConnectionReader} for reading from the connection. */
TransportConnectionReader getReader();
/** Returns the maximum latency of the transport in milliseconds. */ /** Returns a {@link org.briarproject.api.plugins.TransportConnectionWriter
long getMaxLatency(); * TransportConnectionWriter} for writing to the connection. */
TransportConnectionWriter getWriter();
/** Returns an input stream for reading from the connection. */
InputStream getInputStream() throws IOException;
/** Returns an output stream for writing to the connection. */
OutputStream getOutputStream() throws IOException;
/**
* Closes the connection and disposes of any associated resources. The
* first argument indicates whether the connection is being closed because
* of an exception and the second argument indicates whether the connection
* was recognised, which may affect how resources are disposed of.
*/
void dispose(boolean exception, boolean recognised) throws IOException;
} }

View File

@@ -2,6 +2,8 @@ package org.briarproject.api.plugins.simplex;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
/** An interface for transport plugins that support simplex communication. */ /** An interface for transport plugins that support simplex communication. */
public interface SimplexPlugin extends Plugin { public interface SimplexPlugin extends Plugin {
@@ -11,12 +13,12 @@ public interface SimplexPlugin extends Plugin {
* current transport and configuration properties. Returns null if a reader * current transport and configuration properties. Returns null if a reader
* could not be created. * could not be created.
*/ */
SimplexTransportReader createReader(ContactId c); TransportConnectionReader createReader(ContactId c);
/** /**
* Attempts to create and return a writer for the given contact using the * Attempts to create and return a writer for the given contact using the
* current transport and configuration properties. Returns null if a writer * current transport and configuration properties. Returns null if a writer
* could not be created. * could not be created.
*/ */
SimplexTransportWriter createWriter(ContactId c); TransportConnectionWriter createWriter(ContactId c);
} }

View File

@@ -2,6 +2,8 @@ package org.briarproject.api.plugins.simplex;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.plugins.PluginCallback; import org.briarproject.api.plugins.PluginCallback;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
/** /**
* An interface for handling readers and writers created by a simplex transport * An interface for handling readers and writers created by a simplex transport
@@ -9,7 +11,7 @@ import org.briarproject.api.plugins.PluginCallback;
*/ */
public interface SimplexPluginCallback extends PluginCallback { public interface SimplexPluginCallback extends PluginCallback {
void readerCreated(SimplexTransportReader r); void readerCreated(TransportConnectionReader r);
void writerCreated(ContactId c, SimplexTransportWriter w); void writerCreated(ContactId c, TransportConnectionWriter w);
} }

View File

@@ -1,25 +0,0 @@
package org.briarproject.api.plugins.simplex;
import java.io.IOException;
import java.io.InputStream;
/**
* An interface for reading data from a simplex transport. The reader is not
* responsible for decrypting or authenticating the data before returning it.
*/
public interface SimplexTransportReader {
/** Returns the maximum frame length of the transport in bytes. */
int getMaxFrameLength();
/** Returns an input stream for reading from the transport. */
InputStream getInputStream() throws IOException;
/**
* Closes the reader and disposes of any associated resources. The first
* argument indicates whether the reader is being closed because of an
* exception and the second argument indicates whether the connection was
* recognised, which may affect how resources are disposed of.
*/
void dispose(boolean exception, boolean recognised) throws IOException;
}

View File

@@ -1,30 +0,0 @@
package org.briarproject.api.plugins.simplex;
import java.io.IOException;
import java.io.OutputStream;
/**
* An interface for writing data to a simplex transport. The writer is not
* responsible for authenticating or encrypting the data before writing it.
*/
public interface SimplexTransportWriter {
/** Returns the capacity of the transport in bytes. */
long getCapacity();
/** Returns the maximum frame length of the transport in bytes. */
int getMaxFrameLength();
/** Returns the maximum latency of the transport in milliseconds. */
long getMaxLatency();
/** Returns an output stream for writing to the transport. */
OutputStream getOutputStream() throws IOException;
/**
* Closes the writer and disposes of any associated resources. The
* argument indicates whether the writer is being closed because of an
* exception, which may affect how resources are disposed of.
*/
void dispose(boolean exception) throws IOException;
}

View File

@@ -2,18 +2,18 @@ package org.briarproject.api.transport;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
public interface ConnectionDispatcher { public interface ConnectionDispatcher {
void dispatchIncomingConnection(TransportId t, SimplexTransportReader r); void dispatchIncomingConnection(TransportId t, TransportConnectionReader r);
void dispatchIncomingConnection(TransportId t, DuplexTransportConnection d); void dispatchIncomingConnection(TransportId t, DuplexTransportConnection d);
void dispatchOutgoingConnection(ContactId c, TransportId t, void dispatchOutgoingConnection(ContactId c, TransportId t,
SimplexTransportWriter w); TransportConnectionWriter w);
void dispatchOutgoingConnection(ContactId c, TransportId t, void dispatchOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d); DuplexTransportConnection d);

View File

@@ -6,7 +6,7 @@ public interface StreamReaderFactory {
/** Creates a {@link StreamReader} for a transport connection. */ /** Creates a {@link StreamReader} for a transport connection. */
StreamReader createStreamReader(InputStream in, int maxFrameLength, StreamReader createStreamReader(InputStream in, int maxFrameLength,
StreamContext ctx, boolean incoming, boolean initiator); StreamContext ctx);
/** Creates a {@link StreamReader} for an invitation connection. */ /** Creates a {@link StreamReader} for an invitation connection. */
StreamReader createInvitationStreamReader(InputStream in, StreamReader createInvitationStreamReader(InputStream in,

View File

@@ -10,10 +10,4 @@ public interface StreamWriter {
* be written. * be written.
*/ */
OutputStream getOutputStream(); OutputStream getOutputStream();
/**
* Returns the maximum number of bytes that can be written to the output
* stream.
*/
long getRemainingCapacity();
} }

View File

@@ -6,8 +6,7 @@ public interface StreamWriterFactory {
/** Creates a {@link StreamWriter} for a transport connection. */ /** Creates a {@link StreamWriter} for a transport connection. */
StreamWriter createStreamWriter(OutputStream out, int maxFrameLength, StreamWriter createStreamWriter(OutputStream out, int maxFrameLength,
long capacity, StreamContext ctx, boolean incoming, StreamContext ctx);
boolean initiator);
/** Creates a {@link StreamWriter} for an invitation connection. */ /** Creates a {@link StreamWriter} for an invitation connection. */
StreamWriter createInvitationStreamWriter(OutputStream out, StreamWriter createInvitationStreamWriter(OutputStream out,

View File

@@ -4,12 +4,13 @@ import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
/** Maintains the table of expected tags for recognising incoming streams. */ /** Keeps track of expected tags and uses them to recognise incoming streams. */
public interface TagRecogniser { public interface TagRecogniser {
/** /**
* Returns a {@link StreamContext} for reading from the stream with the * Looks up the given tag and returns a {@link StreamContext} for reading
* given tag if the tag was expected, or null if the tag was unexpected. * from the stream if the tag was expected, or null if the tag was
* unexpected.
*/ */
StreamContext recogniseTag(TransportId t, byte[] tag) throws DbException; StreamContext recogniseTag(TransportId t, byte[] tag) throws DbException;

View File

@@ -76,14 +76,10 @@ class CryptoComponentImpl implements CryptoComponent {
// Labels for key derivation // Labels for key derivation
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' }; private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' }; private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' };
private static final byte[] A_FRAME_A = private static final byte[] A_FRAME =
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' }; { 'A', '_', 'F', 'R', 'A', 'M', 'E', '\0' };
private static final byte[] A_FRAME_B = private static final byte[] B_FRAME =
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' }; { 'B', '_', 'F', 'R', 'A', 'M', 'E', '\0' };
private static final byte[] B_FRAME_A =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
private static final byte[] B_FRAME_B =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
// Blank secret for argument validation // Blank secret for argument validation
private static final byte[] BLANK_SECRET = new byte[CIPHER_KEY_BYTES]; private static final byte[] BLANK_SECRET = new byte[CIPHER_KEY_BYTES];
@@ -288,20 +284,15 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public SecretKey deriveFrameKey(byte[] secret, long streamNumber, public SecretKey deriveFrameKey(byte[] secret, long streamNumber,
boolean alice, boolean initiator) { boolean alice) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET)) if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED) if(streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(alice) { if(alice) return deriveKey(secret, A_FRAME, streamNumber);
if(initiator) return deriveKey(secret, A_FRAME_A, streamNumber); else return deriveKey(secret, B_FRAME, streamNumber);
else return deriveKey(secret, A_FRAME_B, streamNumber);
} else {
if(initiator) return deriveKey(secret, B_FRAME_A, streamNumber);
else return deriveKey(secret, B_FRAME_B, streamNumber);
}
} }
private SecretKey deriveKey(byte[] secret, byte[] label, long context) { private SecretKey deriveKey(byte[] secret, byte[] label, long context) {

View File

@@ -75,8 +75,8 @@ class AliceConnector extends Connector {
Writer w; Writer w;
byte[] secret; byte[] secret;
try { try {
in = conn.getInputStream(); in = conn.getReader().getInputStream();
out = conn.getOutputStream(); out = conn.getWriter().getOutputStream();
r = readerFactory.createReader(in); r = readerFactory.createReader(in);
w = writerFactory.createWriter(out); w = writerFactory.createWriter(out);
// Alice goes first // Alice goes first
@@ -130,7 +130,7 @@ class AliceConnector extends Connector {
// Confirmation succeeded - upgrade to a secure connection // Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded"); LOG.info(pluginName + " confirmation succeeded");
int maxFrameLength = conn.getMaxFrameLength(); int maxFrameLength = conn.getReader().getMaxFrameLength();
StreamReader streamReader = StreamReader streamReader =
streamReaderFactory.createInvitationStreamReader(in, streamReaderFactory.createInvitationStreamReader(in,
maxFrameLength, secret, false); maxFrameLength, secret, false);

View File

@@ -69,8 +69,8 @@ class BobConnector extends Connector {
Writer w; Writer w;
byte[] secret; byte[] secret;
try { try {
in = conn.getInputStream(); in = conn.getReader().getInputStream();
out = conn.getOutputStream(); out = conn.getWriter().getOutputStream();
r = readerFactory.createReader(in); r = readerFactory.createReader(in);
w = writerFactory.createWriter(out); w = writerFactory.createWriter(out);
// Alice goes first // Alice goes first
@@ -130,7 +130,7 @@ class BobConnector extends Connector {
// Confirmation succeeded - upgrade to a secure connection // Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded"); LOG.info(pluginName + " confirmation succeeded");
int maxFrameLength = conn.getMaxFrameLength(); int maxFrameLength = conn.getReader().getMaxFrameLength();
StreamReader streamReader = StreamReader streamReader =
streamReaderFactory.createInvitationStreamReader(in, streamReaderFactory.createInvitationStreamReader(in,
maxFrameLength, secret, true); maxFrameLength, secret, true);

View File

@@ -311,7 +311,8 @@ abstract class Connector extends Thread {
boolean exception) { boolean exception) {
try { try {
LOG.info("Closing connection"); LOG.info("Closing connection");
conn.dispose(exception, true); conn.getReader().dispose(exception, true);
conn.getWriter().dispose(exception);
} catch(IOException e) { } catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.messaging.simplex; package org.briarproject.messaging;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -10,12 +10,12 @@ import java.util.logging.Logger;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.messaging.Ack; import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.Message; import org.briarproject.api.messaging.Message;
import org.briarproject.api.messaging.MessageVerifier; import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketReader; import org.briarproject.api.messaging.PacketReader;
import org.briarproject.api.messaging.PacketReaderFactory; import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.RetentionAck; import org.briarproject.api.messaging.RetentionAck;
@@ -25,103 +25,91 @@ import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportAck; import org.briarproject.api.messaging.TransportAck;
import org.briarproject.api.messaging.TransportUpdate; import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.messaging.UnverifiedMessage; import org.briarproject.api.messaging.UnverifiedMessage;
import org.briarproject.api.plugins.simplex.SimplexTransportReader; import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader; import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.util.ByteUtils;
class IncomingSimplexConnection { /**
* An incoming {@link org.briarproject.api.messaging.MessagingSession
* MessagingSession}.
*/
class IncomingSession implements MessagingSession {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(IncomingSimplexConnection.class.getName()); Logger.getLogger(IncomingSession.class.getName());
private final DatabaseComponent db;
private final Executor dbExecutor, cryptoExecutor; private final Executor dbExecutor, cryptoExecutor;
private final MessageVerifier messageVerifier; private final MessageVerifier messageVerifier;
private final DatabaseComponent db; private final StreamReaderFactory streamReaderFactory;
private final ConnectionRegistry connRegistry;
private final StreamReaderFactory connReaderFactory;
private final PacketReaderFactory packetReaderFactory; private final PacketReaderFactory packetReaderFactory;
private final StreamContext ctx; private final StreamContext ctx;
private final SimplexTransportReader transport; private final TransportConnectionReader transportReader;
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId;
IncomingSimplexConnection(Executor dbExecutor, Executor cryptoExecutor, private volatile boolean interrupted = false;
MessageVerifier messageVerifier, DatabaseComponent db,
ConnectionRegistry connRegistry, IncomingSession(DatabaseComponent db, Executor dbExecutor,
StreamReaderFactory connReaderFactory, Executor cryptoExecutor, MessageVerifier messageVerifier,
StreamReaderFactory streamReaderFactory,
PacketReaderFactory packetReaderFactory, StreamContext ctx, PacketReaderFactory packetReaderFactory, StreamContext ctx,
SimplexTransportReader transport) { TransportConnectionReader transportReader) {
this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.messageVerifier = messageVerifier; this.messageVerifier = messageVerifier;
this.db = db; this.streamReaderFactory = streamReaderFactory;
this.connRegistry = connRegistry;
this.connReaderFactory = connReaderFactory;
this.packetReaderFactory = packetReaderFactory; this.packetReaderFactory = packetReaderFactory;
this.ctx = ctx; this.ctx = ctx;
this.transport = transport; this.transportReader = transportReader;
contactId = ctx.getContactId(); contactId = ctx.getContactId();
transportId = ctx.getTransportId();
} }
void read() { public void run() throws IOException {
connRegistry.registerConnection(contactId, transportId); InputStream in = transportReader.getInputStream();
try { int maxFrameLength = transportReader.getMaxFrameLength();
InputStream in = transport.getInputStream(); StreamReader streamReader = streamReaderFactory.createStreamReader(in,
int maxFrameLength = transport.getMaxFrameLength(); maxFrameLength, ctx);
StreamReader conn = connReaderFactory.createStreamReader(in, in = streamReader.getInputStream();
maxFrameLength, ctx, true, true); PacketReader packetReader = packetReaderFactory.createPacketReader(in);
in = conn.getInputStream(); // Read packets until interrupted or EOF
PacketReader reader = packetReaderFactory.createPacketReader(in); while(!interrupted && !packetReader.eof()) {
// Read packets until EOF if(packetReader.hasAck()) {
while(!reader.eof()) { Ack a = packetReader.readAck();
if(reader.hasAck()) { dbExecutor.execute(new ReceiveAck(a));
Ack a = reader.readAck(); } else if(packetReader.hasMessage()) {
dbExecutor.execute(new ReceiveAck(a)); UnverifiedMessage m = packetReader.readMessage();
} else if(reader.hasMessage()) { cryptoExecutor.execute(new VerifyMessage(m));
UnverifiedMessage m = reader.readMessage(); } else if(packetReader.hasRetentionAck()) {
cryptoExecutor.execute(new VerifyMessage(m)); RetentionAck a = packetReader.readRetentionAck();
} else if(reader.hasRetentionAck()) { dbExecutor.execute(new ReceiveRetentionAck(a));
RetentionAck a = reader.readRetentionAck(); } else if(packetReader.hasRetentionUpdate()) {
dbExecutor.execute(new ReceiveRetentionAck(a)); RetentionUpdate u = packetReader.readRetentionUpdate();
} else if(reader.hasRetentionUpdate()) { dbExecutor.execute(new ReceiveRetentionUpdate(u));
RetentionUpdate u = reader.readRetentionUpdate(); } else if(packetReader.hasSubscriptionAck()) {
dbExecutor.execute(new ReceiveRetentionUpdate(u)); SubscriptionAck a = packetReader.readSubscriptionAck();
} else if(reader.hasSubscriptionAck()) { dbExecutor.execute(new ReceiveSubscriptionAck(a));
SubscriptionAck a = reader.readSubscriptionAck(); } else if(packetReader.hasSubscriptionUpdate()) {
dbExecutor.execute(new ReceiveSubscriptionAck(a)); SubscriptionUpdate u = packetReader.readSubscriptionUpdate();
} else if(reader.hasSubscriptionUpdate()) { dbExecutor.execute(new ReceiveSubscriptionUpdate(u));
SubscriptionUpdate u = reader.readSubscriptionUpdate(); } else if(packetReader.hasTransportAck()) {
dbExecutor.execute(new ReceiveSubscriptionUpdate(u)); TransportAck a = packetReader.readTransportAck();
} else if(reader.hasTransportAck()) { dbExecutor.execute(new ReceiveTransportAck(a));
TransportAck a = reader.readTransportAck(); } else if(packetReader.hasTransportUpdate()) {
dbExecutor.execute(new ReceiveTransportAck(a)); TransportUpdate u = packetReader.readTransportUpdate();
} else if(reader.hasTransportUpdate()) { dbExecutor.execute(new ReceiveTransportUpdate(u));
TransportUpdate u = reader.readTransportUpdate(); } else {
dbExecutor.execute(new ReceiveTransportUpdate(u)); throw new FormatException();
} else {
throw new FormatException();
}
} }
dispose(false, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
} finally {
connRegistry.unregisterConnection(contactId, transportId);
} }
in.close();
} }
private void dispose(boolean exception, boolean recognised) { public void interrupt() {
ByteUtils.erase(ctx.getSecret()); // This won't interrupt a blocking read, but the read will throw an
try { // exception when the transport connection is closed
transport.dispose(exception, recognised); interrupted = true;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
private class ReceiveAck implements Runnable { private class ReceiveAck implements Runnable {

View File

@@ -1,5 +1,7 @@
package org.briarproject.messaging; package org.briarproject.messaging;
import javax.inject.Singleton;
import org.briarproject.api.Author; import org.briarproject.api.Author;
import org.briarproject.api.AuthorFactory; import org.briarproject.api.AuthorFactory;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
@@ -9,6 +11,7 @@ import org.briarproject.api.messaging.MessageFactory;
import org.briarproject.api.messaging.MessageVerifier; import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.PacketReaderFactory; import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory; import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.MessagingSessionFactory;
import org.briarproject.api.messaging.SubscriptionUpdate; import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.UnverifiedMessage; import org.briarproject.api.messaging.UnverifiedMessage;
import org.briarproject.api.serial.StructReader; import org.briarproject.api.serial.StructReader;
@@ -18,6 +21,7 @@ import com.google.inject.Provides;
public class MessagingModule extends AbstractModule { public class MessagingModule extends AbstractModule {
@Override
protected void configure() { protected void configure() {
bind(AuthorFactory.class).to(AuthorFactoryImpl.class); bind(AuthorFactory.class).to(AuthorFactoryImpl.class);
bind(GroupFactory.class).to(GroupFactoryImpl.class); bind(GroupFactory.class).to(GroupFactoryImpl.class);
@@ -25,6 +29,8 @@ public class MessagingModule extends AbstractModule {
bind(MessageVerifier.class).to(MessageVerifierImpl.class); bind(MessageVerifier.class).to(MessageVerifierImpl.class);
bind(PacketReaderFactory.class).to(PacketReaderFactoryImpl.class); bind(PacketReaderFactory.class).to(PacketReaderFactoryImpl.class);
bind(PacketWriterFactory.class).to(PacketWriterFactoryImpl.class); bind(PacketWriterFactory.class).to(PacketWriterFactoryImpl.class);
bind(MessagingSessionFactory.class).to(
MessagingSessionFactoryImpl.class).in(Singleton.class);
} }
@Provides @Provides

View File

@@ -0,0 +1,67 @@
package org.briarproject.messaging;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.MessagingSessionFactory;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriterFactory;
class MessagingSessionFactoryImpl implements MessagingSessionFactory {
private final DatabaseComponent db;
private final Executor dbExecutor, cryptoExecutor;
private final MessageVerifier messageVerifier;
private final EventBus eventBus;
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
private final PacketReaderFactory packetReaderFactory;
private final PacketWriterFactory packetWriterFactory;
@Inject
MessagingSessionFactoryImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
MessageVerifier messageVerifier, EventBus eventBus,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory) {
this.db = db;
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.messageVerifier = messageVerifier;
this.eventBus = eventBus;
this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory;
this.packetReaderFactory = packetReaderFactory;
this.packetWriterFactory = packetWriterFactory;
}
public MessagingSession createIncomingSession(StreamContext ctx,
TransportConnectionReader r) {
return new IncomingSession(db, dbExecutor, cryptoExecutor,
messageVerifier, streamReaderFactory, packetReaderFactory,
ctx, r);
}
public MessagingSession createOutgoingSession(StreamContext ctx,
TransportConnectionWriter w, boolean duplex) {
if(duplex) return new ReactiveOutgoingSession(db, dbExecutor, eventBus,
streamWriterFactory, packetWriterFactory, ctx, w);
else return new SinglePassOutgoingSession(db, dbExecutor,
streamWriterFactory, packetWriterFactory, ctx, w);
}
}

View File

@@ -0,0 +1,518 @@
package org.briarproject.messaging;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
import org.briarproject.api.event.LocalTransportsUpdatedEvent;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageExpiredEvent;
import org.briarproject.api.event.MessageRequestedEvent;
import org.briarproject.api.event.MessageToAckEvent;
import org.briarproject.api.event.MessageToRequestEvent;
import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent;
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
import org.briarproject.api.event.TransportRemovedEvent;
import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.Offer;
import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.Request;
import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate;
import org.briarproject.api.messaging.SubscriptionAck;
import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportAck;
import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory;
/**
* An outgoing {@link org.briarproject.api.messaging.MessagingSession
* MessagingSession} that keeps its output stream open and reacts to events
* that make packets available to send.
*/
class ReactiveOutgoingSession implements MessagingSession, EventListener {
private static final Logger LOG =
Logger.getLogger(ReactiveOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE =
new ThrowingRunnable<IOException>() {
public void run() {}
};
private final DatabaseComponent db;
private final Executor dbExecutor;
private final EventBus eventBus;
private final StreamWriterFactory streamWriterFactory;
private final PacketWriterFactory packetWriterFactory;
private final StreamContext ctx;
private final TransportConnectionWriter transportWriter;
private final ContactId contactId;
private final long maxLatency;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
private volatile PacketWriter packetWriter = null;
private volatile boolean interrupted = false;
ReactiveOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, StreamWriterFactory streamWriterFactory,
PacketWriterFactory packetWriterFactory, StreamContext ctx,
TransportConnectionWriter transportWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.streamWriterFactory = streamWriterFactory;
this.packetWriterFactory = packetWriterFactory;
this.ctx = ctx;
this.transportWriter = transportWriter;
contactId = ctx.getContactId();
maxLatency = transportWriter.getMaxLatency();
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
}
public void run() throws IOException {
eventBus.addListener(this);
try {
OutputStream out = transportWriter.getOutputStream();
int maxFrameLength = transportWriter.getMaxFrameLength();
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
out, maxFrameLength, ctx);
out = streamWriter.getOutputStream();
packetWriter = packetWriterFactory.createPacketWriter(out, true);
// Start a query for each type of packet, in order of urgency
dbExecutor.execute(new GenerateTransportAcks());
dbExecutor.execute(new GenerateTransportUpdates());
dbExecutor.execute(new GenerateSubscriptionAck());
dbExecutor.execute(new GenerateSubscriptionUpdate());
dbExecutor.execute(new GenerateRetentionAck());
dbExecutor.execute(new GenerateRetentionUpdate());
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
dbExecutor.execute(new GenerateRequest());
// Write packets until interrupted
try {
while(!interrupted) {
ThrowingRunnable<IOException> task = writerTasks.take();
if(task == CLOSE) break;
task.run();
}
out.flush();
out.close();
} catch(InterruptedException e) {
LOG.info("Interrupted while waiting for a packet to write");
Thread.currentThread().interrupt();
}
} finally {
eventBus.removeListener(this);
}
}
public void interrupt() {
interrupted = true;
writerTasks.add(CLOSE);
}
public void eventOccurred(Event e) {
if(e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if(contactId.equals(c.getContactId())) {
LOG.info("Contact removed, closing");
interrupt();
}
} else if(e instanceof MessageAddedEvent) {
dbExecutor.execute(new GenerateOffer());
} else if(e instanceof MessageExpiredEvent) {
dbExecutor.execute(new GenerateRetentionUpdate());
} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
LocalSubscriptionsUpdatedEvent l =
(LocalSubscriptionsUpdatedEvent) e;
if(l.getAffectedContacts().contains(contactId)) {
dbExecutor.execute(new GenerateSubscriptionUpdate());
dbExecutor.execute(new GenerateOffer());
}
} else if(e instanceof LocalTransportsUpdatedEvent) {
dbExecutor.execute(new GenerateTransportUpdates());
} else if(e instanceof MessageRequestedEvent) {
if(((MessageRequestedEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateBatch());
} else if(e instanceof MessageToAckEvent) {
if(((MessageToAckEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateAck());
} else if(e instanceof MessageToRequestEvent) {
if(((MessageToRequestEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateRequest());
} else if(e instanceof RemoteRetentionTimeUpdatedEvent) {
dbExecutor.execute(new GenerateRetentionAck());
} else if(e instanceof RemoteSubscriptionsUpdatedEvent) {
dbExecutor.execute(new GenerateSubscriptionAck());
dbExecutor.execute(new GenerateOffer());
} else if(e instanceof RemoteTransportsUpdatedEvent) {
dbExecutor.execute(new GenerateTransportAcks());
} else if(e instanceof TransportRemovedEvent) {
TransportRemovedEvent t = (TransportRemovedEvent) e;
if(ctx.getTransportId().equals(t.getTransportId())) {
LOG.info("Transport removed, closing");
interrupt();
}
}
}
// This task runs on the database thread
private class GenerateAck implements Runnable {
public void run() {
int maxMessages = packetWriter.getMaxMessagesForAck(Long.MAX_VALUE);
try {
Ack a = db.generateAck(contactId, maxMessages);
if(LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if(a != null) writerTasks.add(new WriteAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteAck implements ThrowingRunnable<IOException> {
private final Ack ack;
private WriteAck(Ack ack) {
this.ack = ack;
}
public void run() throws IOException {
packetWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(new GenerateAck());
}
}
// This task runs on the database thread
private class GenerateBatch implements Runnable {
public void run() {
try {
Collection<byte[]> b = db.generateRequestedBatch(contactId,
MAX_PACKET_LENGTH, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if(b != null) writerTasks.add(new WriteBatch(b));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteBatch implements ThrowingRunnable<IOException> {
private final Collection<byte[]> batch;
private WriteBatch(Collection<byte[]> batch) {
this.batch = batch;
}
public void run() throws IOException {
for(byte[] raw : batch) packetWriter.writeMessage(raw);
LOG.info("Sent batch");
dbExecutor.execute(new GenerateBatch());
}
}
// This task runs on the database thread
private class GenerateOffer implements Runnable {
public void run() {
int maxMessages = packetWriter.getMaxMessagesForOffer(
Long.MAX_VALUE);
try {
Offer o = db.generateOffer(contactId, maxMessages, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated offer: " + (o != null));
if(o != null) writerTasks.add(new WriteOffer(o));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteOffer implements ThrowingRunnable<IOException> {
private final Offer offer;
private WriteOffer(Offer offer) {
this.offer = offer;
}
public void run() throws IOException {
packetWriter.writeOffer(offer);
LOG.info("Sent offer");
dbExecutor.execute(new GenerateOffer());
}
}
// This task runs on the database thread
private class GenerateRequest implements Runnable {
public void run() {
int maxMessages = packetWriter.getMaxMessagesForRequest(
Long.MAX_VALUE);
try {
Request r = db.generateRequest(contactId, maxMessages);
if(LOG.isLoggable(INFO))
LOG.info("Generated request: " + (r != null));
if(r != null) writerTasks.add(new WriteRequest(r));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteRequest implements ThrowingRunnable<IOException> {
private final Request request;
private WriteRequest(Request request) {
this.request = request;
}
public void run() throws IOException {
packetWriter.writeRequest(request);
LOG.info("Sent request");
dbExecutor.execute(new GenerateRequest());
}
}
// This task runs on the database thread
private class GenerateRetentionAck implements Runnable {
public void run() {
try {
RetentionAck a = db.generateRetentionAck(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated retention ack: " + (a != null));
if(a != null) writerTasks.add(new WriteRetentionAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This tasks runs on the writer thread
private class WriteRetentionAck implements ThrowingRunnable<IOException> {
private final RetentionAck ack;
private WriteRetentionAck(RetentionAck ack) {
this.ack = ack;
}
public void run() throws IOException {
packetWriter.writeRetentionAck(ack);
LOG.info("Sent retention ack");
dbExecutor.execute(new GenerateRetentionAck());
}
}
// This task runs on the database thread
private class GenerateRetentionUpdate implements Runnable {
public void run() {
try {
RetentionUpdate u =
db.generateRetentionUpdate(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated retention update: " + (u != null));
if(u != null) writerTasks.add(new WriteRetentionUpdate(u));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteRetentionUpdate
implements ThrowingRunnable<IOException> {
private final RetentionUpdate update;
private WriteRetentionUpdate(RetentionUpdate update) {
this.update = update;
}
public void run() throws IOException {
packetWriter.writeRetentionUpdate(update);
LOG.info("Sent retention update");
dbExecutor.execute(new GenerateRetentionUpdate());
}
}
// This task runs on the database thread
private class GenerateSubscriptionAck implements Runnable {
public void run() {
try {
SubscriptionAck a = db.generateSubscriptionAck(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated subscription ack: " + (a != null));
if(a != null) writerTasks.add(new WriteSubscriptionAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This tasks runs on the writer thread
private class WriteSubscriptionAck
implements ThrowingRunnable<IOException> {
private final SubscriptionAck ack;
private WriteSubscriptionAck(SubscriptionAck ack) {
this.ack = ack;
}
public void run() throws IOException {
packetWriter.writeSubscriptionAck(ack);
LOG.info("Sent subscription ack");
dbExecutor.execute(new GenerateSubscriptionAck());
}
}
// This task runs on the database thread
private class GenerateSubscriptionUpdate implements Runnable {
public void run() {
try {
SubscriptionUpdate u =
db.generateSubscriptionUpdate(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated subscription update: " + (u != null));
if(u != null) writerTasks.add(new WriteSubscriptionUpdate(u));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteSubscriptionUpdate
implements ThrowingRunnable<IOException> {
private final SubscriptionUpdate update;
private WriteSubscriptionUpdate(SubscriptionUpdate update) {
this.update = update;
}
public void run() throws IOException {
packetWriter.writeSubscriptionUpdate(update);
LOG.info("Sent subscription update");
dbExecutor.execute(new GenerateSubscriptionUpdate());
}
}
// This task runs on the database thread
private class GenerateTransportAcks implements Runnable {
public void run() {
try {
Collection<TransportAck> acks =
db.generateTransportAcks(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated transport acks: " + (acks != null));
if(acks != null) writerTasks.add(new WriteTransportAcks(acks));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This tasks runs on the writer thread
private class WriteTransportAcks implements ThrowingRunnable<IOException> {
private final Collection<TransportAck> acks;
private WriteTransportAcks(Collection<TransportAck> acks) {
this.acks = acks;
}
public void run() throws IOException {
for(TransportAck a : acks) packetWriter.writeTransportAck(a);
LOG.info("Sent transport acks");
dbExecutor.execute(new GenerateTransportAcks());
}
}
// This task runs on the database thread
private class GenerateTransportUpdates implements Runnable {
public void run() {
try {
Collection<TransportUpdate> t =
db.generateTransportUpdates(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated transport updates: " + (t != null));
if(t != null) writerTasks.add(new WriteTransportUpdates(t));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteTransportUpdates
implements ThrowingRunnable<IOException> {
private final Collection<TransportUpdate> updates;
private WriteTransportUpdates(Collection<TransportUpdate> updates) {
this.updates = updates;
}
public void run() throws IOException {
for(TransportUpdate u : updates)
packetWriter.writeTransportUpdate(u);
LOG.info("Sent transport updates");
dbExecutor.execute(new GenerateTransportUpdates());
}
}
}

View File

@@ -0,0 +1,395 @@
package org.briarproject.messaging;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate;
import org.briarproject.api.messaging.SubscriptionAck;
import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportAck;
import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory;
/**
* An outgoing {@link org.briarproject.api.messaging.MessagingSession
* MessagingSession} that closes its output stream when no more packets are
* available to send.
*/
class SinglePassOutgoingSession implements MessagingSession {
private static final Logger LOG =
Logger.getLogger(SinglePassOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE =
new ThrowingRunnable<IOException>() {
public void run() {}
};
private final DatabaseComponent db;
private final Executor dbExecutor;
private final StreamWriterFactory streamWriterFactory;
private final PacketWriterFactory packetWriterFactory;
private final StreamContext ctx;
private final TransportConnectionWriter transportWriter;
private final ContactId contactId;
private final long maxLatency;
private final AtomicInteger outstandingQueries;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
private volatile StreamWriter streamWriter = null;
private volatile PacketWriter packetWriter = null;
private volatile boolean interrupted = false;
SinglePassOutgoingSession(DatabaseComponent db, Executor dbExecutor,
StreamWriterFactory streamWriterFactory,
PacketWriterFactory packetWriterFactory, StreamContext ctx,
TransportConnectionWriter transportWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.streamWriterFactory = streamWriterFactory;
this.packetWriterFactory = packetWriterFactory;
this.ctx = ctx;
this.transportWriter = transportWriter;
contactId = ctx.getContactId();
maxLatency = transportWriter.getMaxLatency();
outstandingQueries = new AtomicInteger(8); // One per type of packet
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
}
public void run() throws IOException {
OutputStream out = transportWriter.getOutputStream();
int maxFrameLength = transportWriter.getMaxFrameLength();
streamWriter = streamWriterFactory.createStreamWriter(out,
maxFrameLength, ctx);
out = streamWriter.getOutputStream();
packetWriter = packetWriterFactory.createPacketWriter(out, false);
// Start a query for each type of packet, in order of urgency
dbExecutor.execute(new GenerateTransportAcks());
dbExecutor.execute(new GenerateTransportUpdates());
dbExecutor.execute(new GenerateSubscriptionAck());
dbExecutor.execute(new GenerateSubscriptionUpdate());
dbExecutor.execute(new GenerateRetentionAck());
dbExecutor.execute(new GenerateRetentionUpdate());
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
// Write packets until interrupted or there are no more packets to write
try {
while(!interrupted) {
ThrowingRunnable<IOException> task = writerTasks.take();
if(task == CLOSE) break;
task.run();
}
out.flush();
out.close();
} catch(InterruptedException e) {
LOG.info("Interrupted while waiting for a packet to write");
Thread.currentThread().interrupt();
}
}
public void interrupt() {
interrupted = true;
writerTasks.add(CLOSE);
}
private void decrementOutstandingQueries() {
if(outstandingQueries.decrementAndGet() == 0) writerTasks.add(CLOSE);
}
// This task runs on the database thread
private class GenerateAck implements Runnable {
public void run() {
int maxMessages = packetWriter.getMaxMessagesForAck(Long.MAX_VALUE);
try {
Ack a = db.generateAck(contactId, maxMessages);
if(LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if(a == null) decrementOutstandingQueries();
else writerTasks.add(new WriteAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteAck implements ThrowingRunnable<IOException> {
private final Ack ack;
private WriteAck(Ack ack) {
this.ack = ack;
}
public void run() throws IOException {
packetWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(new GenerateAck());
}
}
// This task runs on the database thread
private class GenerateBatch implements Runnable {
public void run() {
try {
Collection<byte[]> b = db.generateBatch(contactId,
MAX_PACKET_LENGTH, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if(b == null) decrementOutstandingQueries();
else writerTasks.add(new WriteBatch(b));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteBatch implements ThrowingRunnable<IOException> {
private final Collection<byte[]> batch;
private WriteBatch(Collection<byte[]> batch) {
this.batch = batch;
}
public void run() throws IOException {
for(byte[] raw : batch) packetWriter.writeMessage(raw);
LOG.info("Sent batch");
dbExecutor.execute(new GenerateBatch());
}
}
// This task runs on the database thread
private class GenerateRetentionAck implements Runnable {
public void run() {
try {
RetentionAck a = db.generateRetentionAck(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated retention ack: " + (a != null));
if(a == null) decrementOutstandingQueries();
else writerTasks.add(new WriteRetentionAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This tasks runs on the writer thread
private class WriteRetentionAck implements ThrowingRunnable<IOException> {
private final RetentionAck ack;
private WriteRetentionAck(RetentionAck ack) {
this.ack = ack;
}
public void run() throws IOException {
packetWriter.writeRetentionAck(ack);
LOG.info("Sent retention ack");
dbExecutor.execute(new GenerateRetentionAck());
}
}
// This task runs on the database thread
private class GenerateRetentionUpdate implements Runnable {
public void run() {
try {
RetentionUpdate u =
db.generateRetentionUpdate(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated retention update: " + (u != null));
if(u == null) decrementOutstandingQueries();
else writerTasks.add(new WriteRetentionUpdate(u));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteRetentionUpdate
implements ThrowingRunnable<IOException> {
private final RetentionUpdate update;
private WriteRetentionUpdate(RetentionUpdate update) {
this.update = update;
}
public void run() throws IOException {
packetWriter.writeRetentionUpdate(update);
LOG.info("Sent retention update");
dbExecutor.execute(new GenerateRetentionUpdate());
}
}
// This task runs on the database thread
private class GenerateSubscriptionAck implements Runnable {
public void run() {
try {
SubscriptionAck a = db.generateSubscriptionAck(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated subscription ack: " + (a != null));
if(a == null) decrementOutstandingQueries();
else writerTasks.add(new WriteSubscriptionAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This tasks runs on the writer thread
private class WriteSubscriptionAck
implements ThrowingRunnable<IOException> {
private final SubscriptionAck ack;
private WriteSubscriptionAck(SubscriptionAck ack) {
this.ack = ack;
}
public void run() throws IOException {
packetWriter.writeSubscriptionAck(ack);
LOG.info("Sent subscription ack");
dbExecutor.execute(new GenerateSubscriptionAck());
}
}
// This task runs on the database thread
private class GenerateSubscriptionUpdate implements Runnable {
public void run() {
try {
SubscriptionUpdate u =
db.generateSubscriptionUpdate(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated subscription update: " + (u != null));
if(u == null) decrementOutstandingQueries();
else writerTasks.add(new WriteSubscriptionUpdate(u));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteSubscriptionUpdate
implements ThrowingRunnable<IOException> {
private final SubscriptionUpdate update;
private WriteSubscriptionUpdate(SubscriptionUpdate update) {
this.update = update;
}
public void run() throws IOException {
packetWriter.writeSubscriptionUpdate(update);
LOG.info("Sent subscription update");
dbExecutor.execute(new GenerateSubscriptionUpdate());
}
}
// This task runs on the database thread
private class GenerateTransportAcks implements Runnable {
public void run() {
try {
Collection<TransportAck> acks =
db.generateTransportAcks(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated transport acks: " + (acks != null));
if(acks == null) decrementOutstandingQueries();
else writerTasks.add(new WriteTransportAcks(acks));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This tasks runs on the writer thread
private class WriteTransportAcks implements ThrowingRunnable<IOException> {
private final Collection<TransportAck> acks;
private WriteTransportAcks(Collection<TransportAck> acks) {
this.acks = acks;
}
public void run() throws IOException {
for(TransportAck a : acks) packetWriter.writeTransportAck(a);
LOG.info("Sent transport acks");
dbExecutor.execute(new GenerateTransportAcks());
}
}
// This task runs on the database thread
private class GenerateTransportUpdates implements Runnable {
public void run() {
try {
Collection<TransportUpdate> t =
db.generateTransportUpdates(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated transport updates: " + (t != null));
if(t == null) decrementOutstandingQueries();
else writerTasks.add(new WriteTransportUpdates(t));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt();
}
}
}
// This task runs on the writer thread
private class WriteTransportUpdates
implements ThrowingRunnable<IOException> {
private final Collection<TransportUpdate> updates;
private WriteTransportUpdates(Collection<TransportUpdate> updates) {
this.updates = updates;
}
public void run() throws IOException {
for(TransportUpdate u : updates)
packetWriter.writeTransportUpdate(u);
LOG.info("Sent transport updates");
dbExecutor.execute(new GenerateTransportUpdates());
}
}
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.messaging;
interface ThrowingRunnable<T extends Throwable> {
public void run() throws T;
}

View File

@@ -1,871 +0,0 @@
package org.briarproject.messaging.duplex;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
import org.briarproject.api.event.LocalTransportsUpdatedEvent;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageExpiredEvent;
import org.briarproject.api.event.MessageRequestedEvent;
import org.briarproject.api.event.MessageToAckEvent;
import org.briarproject.api.event.MessageToRequestEvent;
import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent;
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.Message;
import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.Offer;
import org.briarproject.api.messaging.PacketReader;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.Request;
import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate;
import org.briarproject.api.messaging.SubscriptionAck;
import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportAck;
import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.messaging.UnverifiedMessage;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.util.ByteUtils;
abstract class DuplexConnection implements EventListener {
private static final Logger LOG =
Logger.getLogger(DuplexConnection.class.getName());
private static final Runnable CLOSE = new Runnable() {
public void run() {}
};
private static final Runnable DIE = new Runnable() {
public void run() {}
};
protected final DatabaseComponent db;
protected final EventBus eventBus;
protected final ConnectionRegistry connRegistry;
protected final StreamReaderFactory connReaderFactory;
protected final StreamWriterFactory connWriterFactory;
protected final PacketReaderFactory packetReaderFactory;
protected final PacketWriterFactory packetWriterFactory;
protected final StreamContext ctx;
protected final DuplexTransportConnection transport;
protected final ContactId contactId;
protected final TransportId transportId;
private final Executor dbExecutor, cryptoExecutor;
private final MessageVerifier messageVerifier;
private final long maxLatency;
private final AtomicBoolean disposed;
private final BlockingQueue<Runnable> writerTasks;
private volatile PacketWriter writer = null;
DuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
MessageVerifier messageVerifier, DatabaseComponent db,
EventBus eventBus, ConnectionRegistry connRegistry,
StreamReaderFactory connReaderFactory,
StreamWriterFactory connWriterFactory,
PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory, StreamContext ctx,
DuplexTransportConnection transport) {
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.messageVerifier = messageVerifier;
this.db = db;
this.eventBus = eventBus;
this.connRegistry = connRegistry;
this.connReaderFactory = connReaderFactory;
this.connWriterFactory = connWriterFactory;
this.packetReaderFactory = packetReaderFactory;
this.packetWriterFactory = packetWriterFactory;
this.ctx = ctx;
this.transport = transport;
contactId = ctx.getContactId();
transportId = ctx.getTransportId();
maxLatency = transport.getMaxLatency();
disposed = new AtomicBoolean(false);
writerTasks = new LinkedBlockingQueue<Runnable>();
}
protected abstract StreamReader createStreamReader() throws IOException;
protected abstract StreamWriter createStreamWriter() throws IOException;
public void eventOccurred(Event e) {
if(e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if(contactId.equals(c.getContactId())) writerTasks.add(CLOSE);
} else if(e instanceof MessageAddedEvent) {
dbExecutor.execute(new GenerateOffer());
} else if(e instanceof MessageExpiredEvent) {
dbExecutor.execute(new GenerateRetentionUpdate());
} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
LocalSubscriptionsUpdatedEvent l =
(LocalSubscriptionsUpdatedEvent) e;
if(l.getAffectedContacts().contains(contactId)) {
dbExecutor.execute(new GenerateSubscriptionUpdate());
dbExecutor.execute(new GenerateOffer());
}
} else if(e instanceof LocalTransportsUpdatedEvent) {
dbExecutor.execute(new GenerateTransportUpdates());
} else if(e instanceof MessageRequestedEvent) {
if(((MessageRequestedEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateBatch());
} else if(e instanceof MessageToAckEvent) {
if(((MessageToAckEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateAck());
} else if(e instanceof MessageToRequestEvent) {
if(((MessageToRequestEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateRequest());
} else if(e instanceof RemoteRetentionTimeUpdatedEvent) {
dbExecutor.execute(new GenerateRetentionAck());
} else if(e instanceof RemoteSubscriptionsUpdatedEvent) {
dbExecutor.execute(new GenerateSubscriptionAck());
dbExecutor.execute(new GenerateOffer());
} else if(e instanceof RemoteTransportsUpdatedEvent) {
dbExecutor.execute(new GenerateTransportAcks());
}
}
void read() {
try {
InputStream in = createStreamReader().getInputStream();
PacketReader reader = packetReaderFactory.createPacketReader(in);
LOG.info("Starting to read");
while(!reader.eof()) {
if(reader.hasAck()) {
Ack a = reader.readAck();
LOG.info("Received ack");
dbExecutor.execute(new ReceiveAck(a));
} else if(reader.hasMessage()) {
UnverifiedMessage m = reader.readMessage();
LOG.info("Received message");
cryptoExecutor.execute(new VerifyMessage(m));
} else if(reader.hasOffer()) {
Offer o = reader.readOffer();
LOG.info("Received offer");
dbExecutor.execute(new ReceiveOffer(o));
} else if(reader.hasRequest()) {
Request r = reader.readRequest();
LOG.info("Received request");
dbExecutor.execute(new ReceiveRequest(r));
} else if(reader.hasRetentionAck()) {
RetentionAck a = reader.readRetentionAck();
LOG.info("Received retention ack");
dbExecutor.execute(new ReceiveRetentionAck(a));
} else if(reader.hasRetentionUpdate()) {
RetentionUpdate u = reader.readRetentionUpdate();
LOG.info("Received retention update");
dbExecutor.execute(new ReceiveRetentionUpdate(u));
} else if(reader.hasSubscriptionAck()) {
SubscriptionAck a = reader.readSubscriptionAck();
LOG.info("Received subscription ack");
dbExecutor.execute(new ReceiveSubscriptionAck(a));
} else if(reader.hasSubscriptionUpdate()) {
SubscriptionUpdate u = reader.readSubscriptionUpdate();
LOG.info("Received subscription update");
dbExecutor.execute(new ReceiveSubscriptionUpdate(u));
} else if(reader.hasTransportAck()) {
TransportAck a = reader.readTransportAck();
LOG.info("Received transport ack");
dbExecutor.execute(new ReceiveTransportAck(a));
} else if(reader.hasTransportUpdate()) {
TransportUpdate u = reader.readTransportUpdate();
LOG.info("Received transport update");
dbExecutor.execute(new ReceiveTransportUpdate(u));
} else {
throw new FormatException();
}
}
LOG.info("Finished reading");
writerTasks.add(CLOSE);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
writerTasks.add(DIE);
}
}
void write() {
connRegistry.registerConnection(contactId, transportId);
eventBus.addListener(this);
try {
OutputStream out = createStreamWriter().getOutputStream();
writer = packetWriterFactory.createPacketWriter(out, true);
LOG.info("Starting to write");
// Ensure the tag is sent
out.flush();
// Send the initial packets
dbExecutor.execute(new GenerateTransportAcks());
dbExecutor.execute(new GenerateTransportUpdates());
dbExecutor.execute(new GenerateSubscriptionAck());
dbExecutor.execute(new GenerateSubscriptionUpdate());
dbExecutor.execute(new GenerateRetentionAck());
dbExecutor.execute(new GenerateRetentionUpdate());
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
dbExecutor.execute(new GenerateRequest());
// Main loop
Runnable task = null;
while(true) {
LOG.info("Waiting for something to write");
task = writerTasks.take();
if(task == CLOSE || task == DIE) break;
task.run();
}
LOG.info("Finished writing");
if(task == CLOSE) {
writer.flush();
writer.close();
dispose(false, true);
} else {
dispose(true, true);
}
} catch(InterruptedException e) {
LOG.warning("Interrupted while waiting for task");
Thread.currentThread().interrupt();
dispose(true, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
eventBus.removeListener(this);
connRegistry.unregisterConnection(contactId, transportId);
}
private void dispose(boolean exception, boolean recognised) {
if(disposed.getAndSet(true)) return;
if(LOG.isLoggable(INFO))
LOG.info("Disposing: " + exception + ", " + recognised);
ByteUtils.erase(ctx.getSecret());
try {
transport.dispose(exception, recognised);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
// This task runs on the database thread
private class ReceiveAck implements Runnable {
private final Ack ack;
private ReceiveAck(Ack ack) {
this.ack = ack;
}
public void run() {
try {
db.receiveAck(contactId, ack);
LOG.info("DB received ack");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on a crypto thread
private class VerifyMessage implements Runnable {
private final UnverifiedMessage message;
private VerifyMessage(UnverifiedMessage message) {
this.message = message;
}
public void run() {
try {
Message m = messageVerifier.verifyMessage(message);
LOG.info("Verified message");
dbExecutor.execute(new ReceiveMessage(m));
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveMessage implements Runnable {
private final Message message;
private ReceiveMessage(Message message) {
this.message = message;
}
public void run() {
try {
db.receiveMessage(contactId, message);
LOG.info("DB received message");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveOffer implements Runnable {
private final Offer offer;
private ReceiveOffer(Offer offer) {
this.offer = offer;
}
public void run() {
try {
db.receiveOffer(contactId, offer);
LOG.info("DB received offer");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveRequest implements Runnable {
private final Request request;
private ReceiveRequest(Request request) {
this.request = request;
}
public void run() {
try {
db.receiveRequest(contactId, request);
LOG.info("DB received request");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveRetentionAck implements Runnable {
private final RetentionAck ack;
private ReceiveRetentionAck(RetentionAck ack) {
this.ack = ack;
}
public void run() {
try {
db.receiveRetentionAck(contactId, ack);
LOG.info("DB received retention ack");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveRetentionUpdate implements Runnable {
private final RetentionUpdate update;
private ReceiveRetentionUpdate(RetentionUpdate update) {
this.update = update;
}
public void run() {
try {
db.receiveRetentionUpdate(contactId, update);
LOG.info("DB received retention update");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveSubscriptionAck implements Runnable {
private final SubscriptionAck ack;
private ReceiveSubscriptionAck(SubscriptionAck ack) {
this.ack = ack;
}
public void run() {
try {
db.receiveSubscriptionAck(contactId, ack);
LOG.info("DB received subscription ack");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveSubscriptionUpdate implements Runnable {
private final SubscriptionUpdate update;
private ReceiveSubscriptionUpdate(SubscriptionUpdate update) {
this.update = update;
}
public void run() {
try {
db.receiveSubscriptionUpdate(contactId, update);
LOG.info("DB received subscription update");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveTransportAck implements Runnable {
private final TransportAck ack;
private ReceiveTransportAck(TransportAck ack) {
this.ack = ack;
}
public void run() {
try {
db.receiveTransportAck(contactId, ack);
LOG.info("DB received transport ack");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class ReceiveTransportUpdate implements Runnable {
private final TransportUpdate update;
private ReceiveTransportUpdate(TransportUpdate update) {
this.update = update;
}
public void run() {
try {
db.receiveTransportUpdate(contactId, update);
LOG.info("DB received transport update");
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the database thread
private class GenerateAck implements Runnable {
public void run() {
assert writer != null;
int maxMessages = writer.getMaxMessagesForAck(Long.MAX_VALUE);
try {
Ack a = db.generateAck(contactId, maxMessages);
if(LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if(a != null) writerTasks.add(new WriteAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteAck implements Runnable {
private final Ack ack;
private WriteAck(Ack ack) {
this.ack = ack;
}
public void run() {
assert writer != null;
try {
writer.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(new GenerateAck());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateBatch implements Runnable {
public void run() {
assert writer != null;
try {
Collection<byte[]> b = db.generateRequestedBatch(contactId,
MAX_PACKET_LENGTH, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if(b != null) writerTasks.add(new WriteBatch(b));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteBatch implements Runnable {
private final Collection<byte[]> batch;
private WriteBatch(Collection<byte[]> batch) {
this.batch = batch;
}
public void run() {
assert writer != null;
try {
for(byte[] raw : batch) writer.writeMessage(raw);
LOG.info("Sent batch");
dbExecutor.execute(new GenerateBatch());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateOffer implements Runnable {
public void run() {
assert writer != null;
int maxMessages = writer.getMaxMessagesForOffer(Long.MAX_VALUE);
try {
Offer o = db.generateOffer(contactId, maxMessages, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated offer: " + (o != null));
if(o != null) writerTasks.add(new WriteOffer(o));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteOffer implements Runnable {
private final Offer offer;
private WriteOffer(Offer offer) {
this.offer = offer;
}
public void run() {
assert writer != null;
try {
writer.writeOffer(offer);
LOG.info("Sent offer");
dbExecutor.execute(new GenerateOffer());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateRequest implements Runnable {
public void run() {
assert writer != null;
int maxMessages = writer.getMaxMessagesForRequest(Long.MAX_VALUE);
try {
Request r = db.generateRequest(contactId, maxMessages);
if(LOG.isLoggable(INFO))
LOG.info("Generated request: " + (r != null));
if(r != null) writerTasks.add(new WriteRequest(r));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteRequest implements Runnable {
private final Request request;
private WriteRequest(Request request) {
this.request = request;
}
public void run() {
assert writer != null;
try {
writer.writeRequest(request);
LOG.info("Sent request");
dbExecutor.execute(new GenerateRequest());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateRetentionAck implements Runnable {
public void run() {
try {
RetentionAck a = db.generateRetentionAck(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated retention ack: " + (a != null));
if(a != null) writerTasks.add(new WriteRetentionAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This tasks runs on the writer thread
private class WriteRetentionAck implements Runnable {
private final RetentionAck ack;
private WriteRetentionAck(RetentionAck ack) {
this.ack = ack;
}
public void run() {
assert writer != null;
try {
writer.writeRetentionAck(ack);
LOG.info("Sent retention ack");
dbExecutor.execute(new GenerateRetentionAck());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateRetentionUpdate implements Runnable {
public void run() {
try {
RetentionUpdate u =
db.generateRetentionUpdate(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated retention update: " + (u != null));
if(u != null) writerTasks.add(new WriteRetentionUpdate(u));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteRetentionUpdate implements Runnable {
private final RetentionUpdate update;
private WriteRetentionUpdate(RetentionUpdate update) {
this.update = update;
}
public void run() {
assert writer != null;
try {
writer.writeRetentionUpdate(update);
LOG.info("Sent retention update");
dbExecutor.execute(new GenerateRetentionUpdate());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateSubscriptionAck implements Runnable {
public void run() {
try {
SubscriptionAck a = db.generateSubscriptionAck(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated subscription ack: " + (a != null));
if(a != null) writerTasks.add(new WriteSubscriptionAck(a));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This tasks runs on the writer thread
private class WriteSubscriptionAck implements Runnable {
private final SubscriptionAck ack;
private WriteSubscriptionAck(SubscriptionAck ack) {
this.ack = ack;
}
public void run() {
assert writer != null;
try {
writer.writeSubscriptionAck(ack);
LOG.info("Sent subscription ack");
dbExecutor.execute(new GenerateSubscriptionAck());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateSubscriptionUpdate implements Runnable {
public void run() {
try {
SubscriptionUpdate u =
db.generateSubscriptionUpdate(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated subscription update: " + (u != null));
if(u != null) writerTasks.add(new WriteSubscriptionUpdate(u));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteSubscriptionUpdate implements Runnable {
private final SubscriptionUpdate update;
private WriteSubscriptionUpdate(SubscriptionUpdate update) {
this.update = update;
}
public void run() {
assert writer != null;
try {
writer.writeSubscriptionUpdate(update);
LOG.info("Sent subscription update");
dbExecutor.execute(new GenerateSubscriptionUpdate());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateTransportAcks implements Runnable {
public void run() {
try {
Collection<TransportAck> acks =
db.generateTransportAcks(contactId);
if(LOG.isLoggable(INFO))
LOG.info("Generated transport acks: " + (acks != null));
if(acks != null) writerTasks.add(new WriteTransportAcks(acks));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This tasks runs on the writer thread
private class WriteTransportAcks implements Runnable {
private final Collection<TransportAck> acks;
private WriteTransportAcks(Collection<TransportAck> acks) {
this.acks = acks;
}
public void run() {
assert writer != null;
try {
for(TransportAck a : acks) writer.writeTransportAck(a);
LOG.info("Sent transport acks");
dbExecutor.execute(new GenerateTransportAcks());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
// This task runs on the database thread
private class GenerateTransportUpdates implements Runnable {
public void run() {
try {
Collection<TransportUpdate> t =
db.generateTransportUpdates(contactId, maxLatency);
if(LOG.isLoggable(INFO))
LOG.info("Generated transport updates: " + (t != null));
if(t != null) writerTasks.add(new WriteTransportUpdates(t));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
// This task runs on the writer thread
private class WriteTransportUpdates implements Runnable {
private final Collection<TransportUpdate> updates;
private WriteTransportUpdates(Collection<TransportUpdate> updates) {
this.updates = updates;
}
public void run() {
assert writer != null;
try {
for(TransportUpdate u : updates) writer.writeTransportUpdate(u);
LOG.info("Sent transport updates");
dbExecutor.execute(new GenerateTransportUpdates());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, true);
}
}
}
}

View File

@@ -1,108 +0,0 @@
package org.briarproject.messaging.duplex;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.duplex.DuplexConnectionFactory;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriterFactory;
class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
private static final Logger LOG =
Logger.getLogger(DuplexConnectionFactoryImpl.class.getName());
private final Executor dbExecutor, cryptoExecutor;
private final MessageVerifier messageVerifier;
private final DatabaseComponent db;
private final EventBus eventBus;
private final KeyManager keyManager;
private final ConnectionRegistry connRegistry;
private final StreamReaderFactory connReaderFactory;
private final StreamWriterFactory connWriterFactory;
private final PacketReaderFactory packetReaderFactory;
private final PacketWriterFactory packetWriterFactory;
@Inject
DuplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
MessageVerifier messageVerifier, DatabaseComponent db,
EventBus eventBus, KeyManager keyManager,
ConnectionRegistry connRegistry,
StreamReaderFactory connReaderFactory,
StreamWriterFactory connWriterFactory,
PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory) {
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.messageVerifier = messageVerifier;
this.db = db;
this.eventBus = eventBus;
this.keyManager = keyManager;
this.connRegistry = connRegistry;
this.connReaderFactory = connReaderFactory;
this.connWriterFactory = connWriterFactory;
this.packetReaderFactory = packetReaderFactory;
this.packetWriterFactory = packetWriterFactory;
}
public void createIncomingConnection(StreamContext ctx,
DuplexTransportConnection transport) {
final DuplexConnection conn = new IncomingDuplexConnection(dbExecutor,
cryptoExecutor, messageVerifier, db, eventBus, connRegistry,
connReaderFactory, connWriterFactory, packetReaderFactory,
packetWriterFactory, ctx, transport);
Runnable write = new Runnable() {
public void run() {
conn.write();
}
};
new Thread(write, "DuplexConnectionWriter").start();
Runnable read = new Runnable() {
public void run() {
conn.read();
}
};
new Thread(read, "DuplexConnectionReader").start();
}
public void createOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection transport) {
StreamContext ctx = keyManager.getStreamContext(c, t);
if(ctx == null) {
LOG.warning("Could not create outgoing stream context");
return;
}
final DuplexConnection conn = new OutgoingDuplexConnection(dbExecutor,
cryptoExecutor, messageVerifier, db, eventBus, connRegistry,
connReaderFactory, connWriterFactory, packetReaderFactory,
packetWriterFactory, ctx, transport);
Runnable write = new Runnable() {
public void run() {
conn.write();
}
};
new Thread(write, "DuplexConnectionWriter").start();
Runnable read = new Runnable() {
public void run() {
conn.read();
}
};
new Thread(read, "DuplexConnectionReader").start();
}
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.messaging.duplex;
import javax.inject.Singleton;
import org.briarproject.api.messaging.duplex.DuplexConnectionFactory;
import com.google.inject.AbstractModule;
public class DuplexMessagingModule extends AbstractModule {
protected void configure() {
bind(DuplexConnectionFactory.class).to(
DuplexConnectionFactoryImpl.class).in(Singleton.class);
}
}

View File

@@ -1,51 +0,0 @@
package org.briarproject.messaging.duplex;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory;
class IncomingDuplexConnection extends DuplexConnection {
IncomingDuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
MessageVerifier messageVerifier, DatabaseComponent db,
EventBus eventBus, ConnectionRegistry connRegistry,
StreamReaderFactory connReaderFactory,
StreamWriterFactory connWriterFactory,
PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory,
StreamContext ctx, DuplexTransportConnection transport) {
super(dbExecutor, cryptoExecutor, messageVerifier, db, eventBus,
connRegistry, connReaderFactory, connWriterFactory,
packetReaderFactory, packetWriterFactory, ctx, transport);
}
@Override
protected StreamReader createStreamReader() throws IOException {
InputStream in = transport.getInputStream();
int maxFrameLength = transport.getMaxFrameLength();
return connReaderFactory.createStreamReader(in, maxFrameLength,
ctx, true, true);
}
@Override
protected StreamWriter createStreamWriter() throws IOException {
OutputStream out = transport.getOutputStream();
int maxFrameLength = transport.getMaxFrameLength();
return connWriterFactory.createStreamWriter(out, maxFrameLength,
Long.MAX_VALUE, ctx, true, false);
}
}

View File

@@ -1,51 +0,0 @@
package org.briarproject.messaging.duplex;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory;
class OutgoingDuplexConnection extends DuplexConnection {
OutgoingDuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
MessageVerifier messageVerifier, DatabaseComponent db,
EventBus eventBus, ConnectionRegistry connRegistry,
StreamReaderFactory connReaderFactory,
StreamWriterFactory connWriterFactory,
PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory, StreamContext ctx,
DuplexTransportConnection transport) {
super(dbExecutor, cryptoExecutor, messageVerifier, db, eventBus,
connRegistry, connReaderFactory, connWriterFactory,
packetReaderFactory, packetWriterFactory, ctx, transport);
}
@Override
protected StreamReader createStreamReader() throws IOException {
InputStream in = transport.getInputStream();
int maxFrameLength = transport.getMaxFrameLength();
return connReaderFactory.createStreamReader(in, maxFrameLength,
ctx, false, false);
}
@Override
protected StreamWriter createStreamWriter() throws IOException {
OutputStream out = transport.getOutputStream();
int maxFrameLength = transport.getMaxFrameLength();
return connWriterFactory.createStreamWriter(out, maxFrameLength,
Long.MAX_VALUE, ctx, false, true);
}
}

View File

@@ -1,187 +0,0 @@
package org.briarproject.messaging.simplex;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate;
import org.briarproject.api.messaging.SubscriptionAck;
import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportAck;
import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.util.ByteUtils;
class OutgoingSimplexConnection {
private static final Logger LOG =
Logger.getLogger(OutgoingSimplexConnection.class.getName());
private final DatabaseComponent db;
private final ConnectionRegistry connRegistry;
private final StreamWriterFactory connWriterFactory;
private final PacketWriterFactory packetWriterFactory;
private final StreamContext ctx;
private final SimplexTransportWriter transport;
private final ContactId contactId;
private final TransportId transportId;
private final long maxLatency;
OutgoingSimplexConnection(DatabaseComponent db,
ConnectionRegistry connRegistry,
StreamWriterFactory connWriterFactory,
PacketWriterFactory packetWriterFactory, StreamContext ctx,
SimplexTransportWriter transport) {
this.db = db;
this.connRegistry = connRegistry;
this.connWriterFactory = connWriterFactory;
this.packetWriterFactory = packetWriterFactory;
this.ctx = ctx;
this.transport = transport;
contactId = ctx.getContactId();
transportId = ctx.getTransportId();
maxLatency = transport.getMaxLatency();
}
void write() {
connRegistry.registerConnection(contactId, transportId);
try {
OutputStream out = transport.getOutputStream();
long capacity = transport.getCapacity();
int maxFrameLength = transport.getMaxFrameLength();
StreamWriter conn = connWriterFactory.createStreamWriter(
out, maxFrameLength, capacity, ctx, false, true);
out = conn.getOutputStream();
if(conn.getRemainingCapacity() < MAX_PACKET_LENGTH)
throw new EOFException();
PacketWriter writer = packetWriterFactory.createPacketWriter(out,
false);
// Send the initial packets: updates and acks
boolean hasSpace = writeTransportAcks(conn, writer);
if(hasSpace) hasSpace = writeTransportUpdates(conn, writer);
if(hasSpace) hasSpace = writeSubscriptionAck(conn, writer);
if(hasSpace) hasSpace = writeSubscriptionUpdate(conn, writer);
if(hasSpace) hasSpace = writeRetentionAck(conn, writer);
if(hasSpace) hasSpace = writeRetentionUpdate(conn, writer);
// Write acks until you can't write acks no more
capacity = conn.getRemainingCapacity();
int maxMessages = writer.getMaxMessagesForAck(capacity);
Ack a = db.generateAck(contactId, maxMessages);
while(a != null) {
writer.writeAck(a);
capacity = conn.getRemainingCapacity();
maxMessages = writer.getMaxMessagesForAck(capacity);
a = db.generateAck(contactId, maxMessages);
}
// Write messages until you can't write messages no more
capacity = conn.getRemainingCapacity();
int maxLength = (int) Math.min(capacity, MAX_PACKET_LENGTH);
Collection<byte[]> batch = db.generateBatch(contactId, maxLength,
maxLatency);
while(batch != null) {
for(byte[] raw : batch) writer.writeMessage(raw);
capacity = conn.getRemainingCapacity();
maxLength = (int) Math.min(capacity, MAX_PACKET_LENGTH);
batch = db.generateBatch(contactId, maxLength, maxLatency);
}
writer.flush();
writer.close();
dispose(false);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true);
}
connRegistry.unregisterConnection(contactId, transportId);
}
private boolean writeTransportAcks(StreamWriter conn,
PacketWriter writer) throws DbException, IOException {
assert conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
Collection<TransportAck> acks = db.generateTransportAcks(contactId);
if(acks == null) return true;
for(TransportAck a : acks) {
writer.writeTransportAck(a);
if(conn.getRemainingCapacity() < MAX_PACKET_LENGTH) return false;
}
return true;
}
private boolean writeTransportUpdates(StreamWriter conn,
PacketWriter writer) throws DbException, IOException {
assert conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
Collection<TransportUpdate> updates =
db.generateTransportUpdates(contactId, maxLatency);
if(updates == null) return true;
for(TransportUpdate u : updates) {
writer.writeTransportUpdate(u);
if(conn.getRemainingCapacity() < MAX_PACKET_LENGTH) return false;
}
return true;
}
private boolean writeSubscriptionAck(StreamWriter conn,
PacketWriter writer) throws DbException, IOException {
assert conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
SubscriptionAck a = db.generateSubscriptionAck(contactId);
if(a == null) return true;
writer.writeSubscriptionAck(a);
return conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
}
private boolean writeSubscriptionUpdate(StreamWriter conn,
PacketWriter writer) throws DbException, IOException {
assert conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
SubscriptionUpdate u =
db.generateSubscriptionUpdate(contactId, maxLatency);
if(u == null) return true;
writer.writeSubscriptionUpdate(u);
return conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
}
private boolean writeRetentionAck(StreamWriter conn,
PacketWriter writer) throws DbException, IOException {
assert conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
RetentionAck a = db.generateRetentionAck(contactId);
if(a == null) return true;
writer.writeRetentionAck(a);
return conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
}
private boolean writeRetentionUpdate(StreamWriter conn,
PacketWriter writer) throws DbException, IOException {
assert conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
RetentionUpdate u = db.generateRetentionUpdate(contactId, maxLatency);
if(u == null) return true;
writer.writeRetentionUpdate(u);
return conn.getRemainingCapacity() >= MAX_PACKET_LENGTH;
}
private void dispose(boolean exception) {
ByteUtils.erase(ctx.getSecret());
try {
transport.dispose(exception);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}

View File

@@ -1,90 +0,0 @@
package org.briarproject.messaging.simplex;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.simplex.SimplexConnectionFactory;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriterFactory;
class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
private static final Logger LOG =
Logger.getLogger(SimplexConnectionFactoryImpl.class.getName());
private final Executor dbExecutor, cryptoExecutor;
private final MessageVerifier messageVerifier;
private final DatabaseComponent db;
private final KeyManager keyManager;
private final ConnectionRegistry connRegistry;
private final StreamReaderFactory connReaderFactory;
private final StreamWriterFactory connWriterFactory;
private final PacketReaderFactory packetReaderFactory;
private final PacketWriterFactory packetWriterFactory;
@Inject
SimplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
MessageVerifier messageVerifier, DatabaseComponent db,
KeyManager keyManager, ConnectionRegistry connRegistry,
StreamReaderFactory connReaderFactory,
StreamWriterFactory connWriterFactory,
PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory) {
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.messageVerifier = messageVerifier;
this.db = db;
this.keyManager = keyManager;
this.connRegistry = connRegistry;
this.connReaderFactory = connReaderFactory;
this.connWriterFactory = connWriterFactory;
this.packetReaderFactory = packetReaderFactory;
this.packetWriterFactory = packetWriterFactory;
}
public void createIncomingConnection(StreamContext ctx,
SimplexTransportReader r) {
final IncomingSimplexConnection conn = new IncomingSimplexConnection(
dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
connReaderFactory, packetReaderFactory, ctx, r);
Runnable read = new Runnable() {
public void run() {
conn.read();
}
};
new Thread(read, "SimplexConnectionReader").start();
}
public void createOutgoingConnection(ContactId c, TransportId t,
SimplexTransportWriter w) {
StreamContext ctx = keyManager.getStreamContext(c, t);
if(ctx == null) {
LOG.warning("Could not create outgoing connection context");
return;
}
final OutgoingSimplexConnection conn = new OutgoingSimplexConnection(db,
connRegistry, connWriterFactory, packetWriterFactory, ctx, w);
Runnable write = new Runnable() {
public void run() {
conn.write();
}
};
new Thread(write, "SimplexConnectionWriter").start();
}
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.messaging.simplex;
import javax.inject.Singleton;
import org.briarproject.api.messaging.simplex.SimplexConnectionFactory;
import com.google.inject.AbstractModule;
public class SimplexMessagingModule extends AbstractModule {
protected void configure() {
bind(SimplexConnectionFactory.class).to(
SimplexConnectionFactoryImpl.class).in(Singleton.class);
}
}

View File

@@ -27,6 +27,8 @@ import org.briarproject.api.lifecycle.IoExecutor;
import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.PluginCallback; import org.briarproject.api.plugins.PluginCallback;
import org.briarproject.api.plugins.PluginManager; import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexPluginConfig; import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
@@ -36,8 +38,6 @@ import org.briarproject.api.plugins.simplex.SimplexPlugin;
import org.briarproject.api.plugins.simplex.SimplexPluginCallback; import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexPluginConfig; import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
import org.briarproject.api.plugins.simplex.SimplexPluginFactory; import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionDispatcher; import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.ui.UiCallback; import org.briarproject.api.ui.UiCallback;
@@ -377,11 +377,11 @@ class PluginManagerImpl implements PluginManager {
super(id); super(id);
} }
public void readerCreated(SimplexTransportReader r) { public void readerCreated(TransportConnectionReader r) {
dispatcher.dispatchIncomingConnection(id, r); dispatcher.dispatchIncomingConnection(id, r);
} }
public void writerCreated(ContactId c, SimplexTransportWriter w) { public void writerCreated(ContactId c, TransportConnectionWriter w) {
dispatcher.dispatchOutgoingConnection(c, id, w); dispatcher.dispatchOutgoingConnection(c, id, w);
} }
} }

View File

@@ -20,14 +20,14 @@ class PollerImpl implements Poller {
Logger.getLogger(PollerImpl.class.getName()); Logger.getLogger(PollerImpl.class.getName());
private final Executor ioExecutor; private final Executor ioExecutor;
private final ConnectionRegistry connRegistry; private final ConnectionRegistry connectionRegistry;
private final Timer timer; private final Timer timer;
@Inject @Inject
PollerImpl(@IoExecutor Executor ioExecutor, ConnectionRegistry connRegistry, PollerImpl(@IoExecutor Executor ioExecutor,
Timer timer) { ConnectionRegistry connectionRegistry, Timer timer) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.connRegistry = connRegistry; this.connectionRegistry = connectionRegistry;
this.timer = timer; this.timer = timer;
} }
@@ -53,7 +53,7 @@ class PollerImpl implements Poller {
public void run() { public void run() {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Polling " + p.getClass().getSimpleName()); LOG.info("Polling " + p.getClass().getSimpleName());
p.poll(connRegistry.getConnectedContacts(p.getId())); p.poll(connectionRegistry.getConnectedContacts(p.getId()));
} }
}); });
} }

View File

@@ -14,10 +14,10 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.simplex.SimplexPlugin; import org.briarproject.api.plugins.simplex.SimplexPlugin;
import org.briarproject.api.plugins.simplex.SimplexPluginCallback; import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.system.FileUtils; import org.briarproject.api.system.FileUtils;
public abstract class FilePlugin implements SimplexPlugin { public abstract class FilePlugin implements SimplexPlugin {
@@ -60,11 +60,11 @@ public abstract class FilePlugin implements SimplexPlugin {
return running; return running;
} }
public SimplexTransportReader createReader(ContactId c) { public TransportConnectionReader createReader(ContactId c) {
return null; return null;
} }
public SimplexTransportWriter createWriter(ContactId c) { public TransportConnectionWriter createWriter(ContactId c) {
if(!running) return null; if(!running) return null;
return createWriter(createConnectionFilename()); return createWriter(createConnectionFilename());
} }
@@ -81,7 +81,7 @@ public abstract class FilePlugin implements SimplexPlugin {
return filename.toLowerCase(Locale.US).matches("[a-z]{8}\\.dat"); return filename.toLowerCase(Locale.US).matches("[a-z]{8}\\.dat");
} }
private SimplexTransportWriter createWriter(String filename) { private TransportConnectionWriter createWriter(String filename) {
if(!running) return null; if(!running) return null;
File dir = chooseOutputDirectory(); File dir = chooseOutputDirectory();
if(dir == null || !dir.exists() || !dir.isDirectory()) return null; if(dir == null || !dir.exists() || !dir.isDirectory()) return null;

View File

@@ -7,9 +7,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.briarproject.api.plugins.simplex.SimplexTransportReader; import org.briarproject.api.plugins.TransportConnectionReader;
class FileTransportReader implements SimplexTransportReader { class FileTransportReader implements TransportConnectionReader {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(FileTransportReader.class.getName()); Logger.getLogger(FileTransportReader.class.getName());
@@ -28,6 +28,10 @@ class FileTransportReader implements SimplexTransportReader {
return plugin.getMaxFrameLength(); return plugin.getMaxFrameLength();
} }
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() { public InputStream getInputStream() {
return in; return in;
} }

View File

@@ -7,9 +7,9 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter; import org.briarproject.api.plugins.TransportConnectionWriter;
class FileTransportWriter implements SimplexTransportWriter { class FileTransportWriter implements TransportConnectionWriter {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(FileTransportWriter.class.getName()); Logger.getLogger(FileTransportWriter.class.getName());
@@ -27,10 +27,6 @@ class FileTransportWriter implements SimplexTransportWriter {
this.plugin = plugin; this.plugin = plugin;
} }
public long getCapacity() {
return capacity;
}
public int getMaxFrameLength() { public int getMaxFrameLength() {
return plugin.getMaxFrameLength(); return plugin.getMaxFrameLength();
} }
@@ -39,6 +35,10 @@ class FileTransportWriter implements SimplexTransportWriter {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
public long getCapacity() {
return capacity;
}
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return out; return out;
} }

View File

@@ -4,38 +4,80 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;
import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
class TcpTransportConnection implements DuplexTransportConnection { class TcpTransportConnection implements DuplexTransportConnection {
private final Plugin plugin; private final Plugin plugin;
private final Socket socket; private final Socket socket;
private final Reader reader;
private final Writer writer;
private final AtomicBoolean halfClosed, closed;
TcpTransportConnection(Plugin plugin, Socket socket) { TcpTransportConnection(Plugin plugin, Socket socket) {
this.plugin = plugin; this.plugin = plugin;
this.socket = socket; this.socket = socket;
reader = new Reader();
writer = new Writer();
halfClosed = new AtomicBoolean(false);
closed = new AtomicBoolean(false);
} }
public int getMaxFrameLength() { public TransportConnectionReader getReader() {
return plugin.getMaxFrameLength(); return reader;
} }
public long getMaxLatency() { public TransportConnectionWriter getWriter() {
return plugin.getMaxLatency(); return writer;
} }
public InputStream getInputStream() throws IOException { private class Reader implements TransportConnectionReader {
return socket.getInputStream();
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) socket.close();
}
} }
public OutputStream getOutputStream() throws IOException { private class Writer implements TransportConnectionWriter {
return socket.getOutputStream();
}
public void dispose(boolean exception, boolean recognised) public int getMaxFrameLength() {
throws IOException { return plugin.getMaxFrameLength();
socket.close(); }
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public long getCapacity() {
return Long.MAX_VALUE;
}
public OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
}
public void dispose(boolean exception) throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) socket.close();
}
} }
} }

View File

@@ -13,14 +13,16 @@ import javax.inject.Inject;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.IoExecutor; import org.briarproject.api.lifecycle.IoExecutor;
import org.briarproject.api.messaging.duplex.DuplexConnectionFactory; import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.simplex.SimplexConnectionFactory; import org.briarproject.api.messaging.MessagingSessionFactory;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.transport.ConnectionDispatcher; import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.TagRecogniser; import org.briarproject.api.transport.TagRecogniser;
@@ -30,132 +32,280 @@ class ConnectionDispatcherImpl implements ConnectionDispatcher {
Logger.getLogger(ConnectionDispatcherImpl.class.getName()); Logger.getLogger(ConnectionDispatcherImpl.class.getName());
private final Executor ioExecutor; private final Executor ioExecutor;
private final KeyManager keyManager;
private final TagRecogniser tagRecogniser; private final TagRecogniser tagRecogniser;
private final SimplexConnectionFactory simplexConnFactory; private final MessagingSessionFactory messagingSessionFactory;
private final DuplexConnectionFactory duplexConnFactory; private final ConnectionRegistry connectionRegistry;
@Inject @Inject
ConnectionDispatcherImpl(@IoExecutor Executor ioExecutor, ConnectionDispatcherImpl(@IoExecutor Executor ioExecutor,
TagRecogniser tagRecogniser, KeyManager keyManager, TagRecogniser tagRecogniser,
SimplexConnectionFactory simplexConnFactory, MessagingSessionFactory messagingSessionFactory,
DuplexConnectionFactory duplexConnFactory) { ConnectionRegistry connectionRegistry) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.keyManager = keyManager;
this.tagRecogniser = tagRecogniser; this.tagRecogniser = tagRecogniser;
this.simplexConnFactory = simplexConnFactory; this.messagingSessionFactory = messagingSessionFactory;
this.duplexConnFactory = duplexConnFactory; this.connectionRegistry = connectionRegistry;
} }
public void dispatchIncomingConnection(TransportId t, public void dispatchIncomingConnection(TransportId t,
SimplexTransportReader r) { TransportConnectionReader r) {
ioExecutor.execute(new DispatchSimplexConnection(t, r)); ioExecutor.execute(new DispatchIncomingSimplexConnection(t, r));
} }
public void dispatchIncomingConnection(TransportId t, public void dispatchIncomingConnection(TransportId t,
DuplexTransportConnection d) { DuplexTransportConnection d) {
ioExecutor.execute(new DispatchDuplexConnection(t, d)); ioExecutor.execute(new DispatchIncomingDuplexConnection(t, d));
} }
public void dispatchOutgoingConnection(ContactId c, TransportId t, public void dispatchOutgoingConnection(ContactId c, TransportId t,
SimplexTransportWriter w) { TransportConnectionWriter w) {
simplexConnFactory.createOutgoingConnection(c, t, w); ioExecutor.execute(new DispatchOutgoingSimplexConnection(c, t, w));
} }
public void dispatchOutgoingConnection(ContactId c, TransportId t, public void dispatchOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d) { DuplexTransportConnection d) {
duplexConnFactory.createOutgoingConnection(c, t, d); ioExecutor.execute(new DispatchOutgoingDuplexConnection(c, t, d));
} }
private byte[] readTag(InputStream in) throws IOException { private StreamContext readAndRecogniseTag(TransportId t,
byte[] b = new byte[TAG_LENGTH]; TransportConnectionReader r) {
int offset = 0; // Read the tag
while(offset < b.length) { byte[] tag = new byte[TAG_LENGTH];
int read = in.read(b, offset, b.length - offset); try {
if(read == -1) throw new EOFException(); InputStream in = r.getInputStream();
offset += read; int offset = 0;
while(offset < tag.length) {
int read = in.read(tag, offset, tag.length - offset);
if(read == -1) throw new EOFException();
offset += read;
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(r, true, false);
return null;
} }
return b; // Recognise the tag
StreamContext ctx = null;
try {
ctx = tagRecogniser.recogniseTag(t, tag);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(r, true, false);
return null;
}
if(ctx == null) dispose(r, false, false);
return ctx;
} }
private class DispatchSimplexConnection implements Runnable { private void runAndDispose(StreamContext ctx, TransportConnectionReader r) {
MessagingSession in =
messagingSessionFactory.createIncomingSession(ctx, r);
ContactId contactId = ctx.getContactId();
TransportId transportId = ctx.getTransportId();
connectionRegistry.registerConnection(contactId, transportId);
try {
in.run();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(r, true, true);
return;
} finally {
connectionRegistry.unregisterConnection(contactId, transportId);
}
dispose(r, false, true);
}
private void dispose(TransportConnectionReader r, boolean exception,
boolean recognised) {
try {
r.dispose(exception, recognised);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void runAndDispose(StreamContext ctx, TransportConnectionWriter w,
boolean duplex) {
MessagingSession out =
messagingSessionFactory.createOutgoingSession(ctx, w, duplex);
ContactId contactId = ctx.getContactId();
TransportId transportId = ctx.getTransportId();
connectionRegistry.registerConnection(contactId, transportId);
try {
out.run();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(w, true);
return;
} finally {
connectionRegistry.unregisterConnection(contactId, transportId);
}
dispose(w, false);
}
private void dispose(TransportConnectionWriter w, boolean exception) {
try {
w.dispose(exception);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private class DispatchIncomingSimplexConnection implements Runnable {
private final TransportId transportId; private final TransportId transportId;
private final SimplexTransportReader transport; private final TransportConnectionReader reader;
private DispatchSimplexConnection(TransportId transportId, private DispatchIncomingSimplexConnection(TransportId transportId,
SimplexTransportReader transport) { TransportConnectionReader reader) {
this.transportId = transportId; this.transportId = transportId;
this.transport = transport; this.reader = reader;
} }
public void run() { public void run() {
try { // Read and recognise the tag
byte[] tag = readTag(transport.getInputStream()); StreamContext ctx = readAndRecogniseTag(transportId, reader);
StreamContext ctx = tagRecogniser.recogniseTag(transportId, if(ctx == null) return;
tag); // Run the incoming session
if(ctx == null) { runAndDispose(ctx, reader);
transport.dispose(false, false);
} else {
simplexConnFactory.createIncomingConnection(ctx,
transport);
}
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
try {
transport.dispose(true, false);
} catch(IOException e1) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e1.toString(), e1);
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
try {
transport.dispose(true, false);
} catch(IOException e1) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e1.toString(), e1);
}
}
} }
} }
private class DispatchDuplexConnection implements Runnable { private class DispatchOutgoingSimplexConnection implements Runnable {
private final ContactId contactId;
private final TransportId transportId;
private final TransportConnectionWriter writer;
private DispatchOutgoingSimplexConnection(ContactId contactId,
TransportId transportId, TransportConnectionWriter writer) {
this.contactId = contactId;
this.transportId = transportId;
this.writer = writer;
}
public void run() {
// Allocate a stream context
StreamContext ctx = keyManager.getStreamContext(contactId,
transportId);
if(ctx == null) {
dispose(writer, false);
return;
}
// Run the outgoing session
runAndDispose(ctx, writer, false);
}
}
private class DispatchIncomingDuplexConnection implements Runnable {
private final TransportId transportId; private final TransportId transportId;
private final DuplexTransportConnection transport; private final TransportConnectionReader reader;
private final TransportConnectionWriter writer;
private DispatchDuplexConnection(TransportId transportId, private DispatchIncomingDuplexConnection(TransportId transportId,
DuplexTransportConnection transport) { DuplexTransportConnection transport) {
this.transportId = transportId; this.transportId = transportId;
this.transport = transport; reader = transport.getReader();
writer = transport.getWriter();
} }
public void run() { public void run() {
byte[] tag; // Read and recognise the tag
try { StreamContext ctx = readAndRecogniseTag(transportId, reader);
tag = readTag(transport.getInputStream()); if(ctx == null) return;
} catch(IOException e) { // Start the outgoing session on another thread
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); ioExecutor.execute(new DispatchIncomingDuplexConnectionSide2(
dispose(true, false); ctx.getContactId(), transportId, writer));
return; // Run the incoming session
} runAndDispose(ctx, reader);
StreamContext ctx = null; }
try { }
ctx = tagRecogniser.recogniseTag(transportId, tag);
} catch(DbException e) { private class DispatchIncomingDuplexConnectionSide2 implements Runnable {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
dispose(true, false); private final ContactId contactId;
return; private final TransportId transportId;
} private final TransportConnectionWriter writer;
if(ctx == null) dispose(false, false);
else duplexConnFactory.createIncomingConnection(ctx, transport); private DispatchIncomingDuplexConnectionSide2(ContactId contactId,
TransportId transportId, TransportConnectionWriter writer) {
this.contactId = contactId;
this.transportId = transportId;
this.writer = writer;
} }
private void dispose(boolean exception, boolean recognised) { public void run() {
try { // Allocate a stream context
transport.dispose(exception, recognised); StreamContext ctx = keyManager.getStreamContext(contactId,
} catch(IOException e) { transportId);
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(ctx == null) {
dispose(writer, false);
return;
} }
// Run the outgoing session
runAndDispose(ctx, writer, true);
}
}
private class DispatchOutgoingDuplexConnection implements Runnable {
private final ContactId contactId;
private final TransportId transportId;
private final TransportConnectionReader reader;
private final TransportConnectionWriter writer;
private DispatchOutgoingDuplexConnection(ContactId contactId,
TransportId transportId, DuplexTransportConnection transport) {
this.contactId = contactId;
this.transportId = transportId;
reader = transport.getReader();
writer = transport.getWriter();
}
public void run() {
// Allocate a stream context
StreamContext ctx = keyManager.getStreamContext(contactId,
transportId);
if(ctx == null) {
dispose(writer, false);
return;
}
// Start the incoming session on another thread
ioExecutor.execute(new DispatchOutgoingDuplexConnectionSide2(
contactId, transportId, reader));
// Run the outgoing session
runAndDispose(ctx, writer, true);
}
}
private class DispatchOutgoingDuplexConnectionSide2 implements Runnable {
private final ContactId contactId;
private final TransportId transportId;
private final TransportConnectionReader reader;
private DispatchOutgoingDuplexConnectionSide2(ContactId contactId,
TransportId transportId, TransportConnectionReader reader) {
this.contactId = contactId;
this.transportId = transportId;
this.reader = reader;
}
public void run() {
// Read and recognise the tag
StreamContext ctx = readAndRecogniseTag(transportId, reader);
if(ctx == null) return;
// Check that the stream comes from the expected contact
if(!ctx.getContactId().equals(contactId)) {
LOG.warning("Wrong contact ID for duplex connection");
dispose(reader, true, true);
return;
}
// Run the incoming session
runAndDispose(ctx, reader);
} }
} }
} }

View File

@@ -8,9 +8,6 @@ interface FrameWriter {
void writeFrame(byte[] frame, int payloadLength, boolean finalFrame) void writeFrame(byte[] frame, int payloadLength, boolean finalFrame)
throws IOException; throws IOException;
/** Flushes the stack. */ /** Flushes the stream. */
void flush() throws IOException; void flush() throws IOException;
/** Returns the maximum number of bytes that can be written. */
long getRemainingCapacity();
} }

View File

@@ -19,22 +19,18 @@ class OutgoingEncryptionLayer implements FrameWriter {
private final AuthenticatedCipher frameCipher; private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey; private final SecretKey frameKey;
private final byte[] tag, iv, aad, ciphertext; private final byte[] tag, iv, aad, ciphertext;
private final int frameLength, maxPayloadLength; private final int frameLength;
private long capacity, frameNumber; private long frameNumber;
private boolean writeTag; private boolean writeTag;
/** Constructor for the initiator's side of a connection. */ OutgoingEncryptionLayer(OutputStream out, AuthenticatedCipher frameCipher,
OutgoingEncryptionLayer(OutputStream out, long capacity, SecretKey frameKey, int frameLength, byte[] tag) {
AuthenticatedCipher frameCipher, SecretKey frameKey,
int frameLength, byte[] tag) {
this.out = out; this.out = out;
this.capacity = capacity;
this.frameCipher = frameCipher; this.frameCipher = frameCipher;
this.frameKey = frameKey; this.frameKey = frameKey;
this.frameLength = frameLength; this.frameLength = frameLength;
this.tag = tag; this.tag = tag;
maxPayloadLength = frameLength - HEADER_LENGTH - MAC_LENGTH;
iv = new byte[IV_LENGTH]; iv = new byte[IV_LENGTH];
aad = new byte[AAD_LENGTH]; aad = new byte[AAD_LENGTH];
ciphertext = new byte[frameLength]; ciphertext = new byte[frameLength];
@@ -42,24 +38,6 @@ class OutgoingEncryptionLayer implements FrameWriter {
writeTag = true; writeTag = true;
} }
/** Constructor for the responder's side of a connection. */
OutgoingEncryptionLayer(OutputStream out, long capacity,
AuthenticatedCipher frameCipher, SecretKey frameKey,
int frameLength) {
this.out = out;
this.capacity = capacity;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
this.frameLength = frameLength;
tag = null;
maxPayloadLength = frameLength - HEADER_LENGTH - MAC_LENGTH;
iv = new byte[IV_LENGTH];
aad = new byte[AAD_LENGTH];
ciphertext = new byte[frameLength];
frameNumber = 0;
writeTag = false;
}
public void writeFrame(byte[] frame, int payloadLength, boolean finalFrame) public void writeFrame(byte[] frame, int payloadLength, boolean finalFrame)
throws IOException { throws IOException {
if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
@@ -71,7 +49,6 @@ class OutgoingEncryptionLayer implements FrameWriter {
frameKey.erase(); frameKey.erase();
throw e; throw e;
} }
capacity -= tag.length;
writeTag = false; writeTag = false;
} }
// Encode the header // Encode the header
@@ -107,7 +84,6 @@ class OutgoingEncryptionLayer implements FrameWriter {
frameKey.erase(); frameKey.erase();
throw e; throw e;
} }
capacity -= ciphertextLength;
frameNumber++; frameNumber++;
} }
@@ -120,33 +96,8 @@ class OutgoingEncryptionLayer implements FrameWriter {
frameKey.erase(); frameKey.erase();
throw e; throw e;
} }
capacity -= tag.length;
writeTag = false; writeTag = false;
} }
out.flush(); out.flush();
} }
public long getRemainingCapacity() {
// How many frame numbers can we use?
long frameNumbers = MAX_32_BIT_UNSIGNED - frameNumber + 1;
// How many full frames do we have space for?
long bytes = writeTag ? capacity - tag.length : capacity;
long fullFrames = bytes / frameLength;
// Are we limited by frame numbers or space?
if(frameNumbers > fullFrames) {
// Can we send a partial frame after the full frames?
int partialFrame = (int) (bytes - fullFrames * frameLength);
if(partialFrame > HEADER_LENGTH + MAC_LENGTH) {
// Send full frames and a partial frame, limited by space
int partialPayload = partialFrame - HEADER_LENGTH - MAC_LENGTH;
return maxPayloadLength * fullFrames + partialPayload;
} else {
// Send full frames only, limited by space
return maxPayloadLength * fullFrames;
}
} else {
// Send full frames only, limited by frame numbers
return maxPayloadLength * frameNumbers;
}
}
} }

View File

@@ -20,24 +20,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
} }
public StreamReader createStreamReader(InputStream in, public StreamReader createStreamReader(InputStream in,
int maxFrameLength, StreamContext ctx, boolean incoming, int maxFrameLength, StreamContext ctx) {
boolean initiator) {
byte[] secret = ctx.getSecret(); byte[] secret = ctx.getSecret();
long streamNumber = ctx.getStreamNumber(); long streamNumber = ctx.getStreamNumber();
boolean weAreAlice = ctx.getAlice(); boolean alice = !ctx.getAlice();
boolean initiatorIsAlice = incoming ? !weAreAlice : weAreAlice; SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, FrameReader frameReader = new IncomingEncryptionLayer(in,
initiatorIsAlice, initiator);
FrameReader encryption = new IncomingEncryptionLayer(in,
crypto.getFrameCipher(), frameKey, maxFrameLength); crypto.getFrameCipher(), frameKey, maxFrameLength);
return new StreamReaderImpl(encryption, maxFrameLength); return new StreamReaderImpl(frameReader, maxFrameLength);
} }
public StreamReader createInvitationStreamReader(InputStream in, public StreamReader createInvitationStreamReader(InputStream in,
int maxFrameLength, byte[] secret, boolean alice) { int maxFrameLength, byte[] secret, boolean alice) {
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, true, alice); SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
FrameReader encryption = new IncomingEncryptionLayer(in, FrameReader frameReader = new IncomingEncryptionLayer(in,
crypto.getFrameCipher(), frameKey, maxFrameLength); crypto.getFrameCipher(), frameKey, maxFrameLength);
return new StreamReaderImpl(encryption, maxFrameLength); return new StreamReaderImpl(frameReader, maxFrameLength);
} }
} }

View File

@@ -22,35 +22,29 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
} }
public StreamWriter createStreamWriter(OutputStream out, public StreamWriter createStreamWriter(OutputStream out,
int maxFrameLength, long capacity, StreamContext ctx, int maxFrameLength, StreamContext ctx) {
boolean incoming, boolean initiator) {
byte[] secret = ctx.getSecret(); byte[] secret = ctx.getSecret();
long streamNumber = ctx.getStreamNumber(); long streamNumber = ctx.getStreamNumber();
boolean weAreAlice = ctx.getAlice(); boolean alice = ctx.getAlice();
boolean initiatorIsAlice = incoming ? !weAreAlice : weAreAlice; byte[] tag = new byte[TAG_LENGTH];
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, SecretKey tagKey = crypto.deriveTagKey(secret, alice);
initiatorIsAlice, initiator); crypto.encodeTag(tag, tagKey, streamNumber);
FrameWriter encryption; tagKey.erase();
if(initiator) { SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
byte[] tag = new byte[TAG_LENGTH]; FrameWriter frameWriter = new OutgoingEncryptionLayer(out,
SecretKey tagKey = crypto.deriveTagKey(secret, initiatorIsAlice); crypto.getFrameCipher(), frameKey, maxFrameLength, tag);
crypto.encodeTag(tag, tagKey, streamNumber); return new StreamWriterImpl(frameWriter, maxFrameLength);
tagKey.erase();
encryption = new OutgoingEncryptionLayer(out, capacity,
crypto.getFrameCipher(), frameKey, maxFrameLength, tag);
} else {
encryption = new OutgoingEncryptionLayer(out, capacity,
crypto.getFrameCipher(), frameKey, maxFrameLength);
}
return new StreamWriterImpl(encryption, maxFrameLength);
} }
public StreamWriter createInvitationStreamWriter(OutputStream out, public StreamWriter createInvitationStreamWriter(OutputStream out,
int maxFrameLength, byte[] secret, boolean alice) { int maxFrameLength, byte[] secret, boolean alice) {
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, true, alice); byte[] tag = new byte[TAG_LENGTH];
FrameWriter encryption = new OutgoingEncryptionLayer(out, SecretKey tagKey = crypto.deriveTagKey(secret, alice);
Long.MAX_VALUE, crypto.getFrameCipher(), frameKey, crypto.encodeTag(tag, tagKey, 0);
maxFrameLength); tagKey.erase();
return new StreamWriterImpl(encryption, maxFrameLength); SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
FrameWriter frameWriter = new OutgoingEncryptionLayer(out,
crypto.getFrameCipher(), frameKey, maxFrameLength, tag);
return new StreamWriterImpl(frameWriter, maxFrameLength);
} }
} }

View File

@@ -33,10 +33,6 @@ class StreamWriterImpl extends OutputStream implements StreamWriter {
return this; return this;
} }
public long getRemainingCapacity() {
return out.getRemainingCapacity();
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
writeFrame(true); writeFrame(true);

View File

@@ -3,40 +3,82 @@ package org.briarproject.plugins.bluetooth;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.microedition.io.StreamConnection; import javax.microedition.io.StreamConnection;
import org.briarproject.api.plugins.Plugin; import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
class BluetoothTransportConnection implements DuplexTransportConnection { class BluetoothTransportConnection implements DuplexTransportConnection {
private final Plugin plugin; private final Plugin plugin;
private final StreamConnection stream; private final StreamConnection stream;
private final Reader reader;
private final Writer writer;
private final AtomicBoolean halfClosed, closed;
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) { BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
this.plugin = plugin; this.plugin = plugin;
this.stream = stream; this.stream = stream;
reader = new Reader();
writer = new Writer();
halfClosed = new AtomicBoolean(false);
closed = new AtomicBoolean(false);
} }
public int getMaxFrameLength() { public TransportConnectionReader getReader() {
return plugin.getMaxFrameLength(); return reader;
} }
public long getMaxLatency() { public TransportConnectionWriter getWriter() {
return plugin.getMaxLatency(); return writer;
} }
public InputStream getInputStream() throws IOException { private class Reader implements TransportConnectionReader {
return stream.openInputStream();
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() throws IOException {
return stream.openInputStream();
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) stream.close();
}
} }
public OutputStream getOutputStream() throws IOException { private class Writer implements TransportConnectionWriter {
return stream.openOutputStream();
}
public void dispose(boolean exception, boolean recognised) public int getMaxFrameLength() {
throws IOException { return plugin.getMaxFrameLength();
stream.close(); }
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public long getCapacity() {
return Long.MAX_VALUE;
}
public OutputStream getOutputStream() throws IOException {
return stream.openOutputStream();
}
public void dispose(boolean exception) throws IOException {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) stream.close();
}
} }
} }

View File

@@ -14,12 +14,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties; import org.briarproject.api.TransportProperties;
import org.briarproject.api.crypto.PseudoRandom; import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
@@ -102,6 +105,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
return running; return running;
} }
// FIXME: Don't poll this plugin
public boolean shouldPoll() { public boolean shouldPoll() {
return true; return true;
} }
@@ -164,7 +168,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
} }
private boolean resetModem() { boolean resetModem() {
if(!running) return false; if(!running) return false;
for(String portName : serialPortList.getPortNames()) { for(String portName : serialPortList.getPortNames()) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
@@ -227,25 +231,21 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private class ModemTransportConnection private class ModemTransportConnection
implements DuplexTransportConnection { implements DuplexTransportConnection {
private final CountDownLatch finished = new CountDownLatch(1); private final AtomicBoolean halfClosed = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
private final CountDownLatch disposalFinished = new CountDownLatch(1);
private final Reader reader = new Reader();
private final Writer writer = new Writer();
public int getMaxFrameLength() { public TransportConnectionReader getReader() {
return maxFrameLength; return reader;
} }
public long getMaxLatency() { public TransportConnectionWriter getWriter() {
return maxLatency; return writer;
} }
public InputStream getInputStream() throws IOException { private void hangUp(boolean exception) {
return modem.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
return modem.getOutputStream();
}
public void dispose(boolean exception, boolean recognised) {
LOG.info("Call disconnected"); LOG.info("Call disconnected");
try { try {
modem.hangUp(); modem.hangUp();
@@ -254,11 +254,55 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
exception = true; exception = true;
} }
if(exception) resetModem(); if(exception) resetModem();
finished.countDown(); disposalFinished.countDown();
} }
private void waitForDisposal() throws InterruptedException { private void waitForDisposal() throws InterruptedException {
finished.await(); disposalFinished.await();
}
private class Reader implements TransportConnectionReader {
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public InputStream getInputStream() throws IOException {
return modem.getInputStream();
}
public void dispose(boolean exception, boolean recognised) {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) hangUp(exception);
}
}
private class Writer implements TransportConnectionWriter {
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public long getCapacity() {
return Long.MAX_VALUE;
}
public OutputStream getOutputStream() throws IOException {
return modem.getOutputStream();
}
public void dispose(boolean exception) {
if(halfClosed.getAndSet(true) || exception)
if(!closed.getAndSet(true)) hangUp(exception);
}
} }
} }
} }

View File

@@ -111,8 +111,8 @@
<test name='org.briarproject.messaging.ConstantsTest'/> <test name='org.briarproject.messaging.ConstantsTest'/>
<test name='org.briarproject.messaging.ConsumersTest'/> <test name='org.briarproject.messaging.ConsumersTest'/>
<test name='org.briarproject.messaging.PacketReaderImplTest'/> <test name='org.briarproject.messaging.PacketReaderImplTest'/>
<test name='org.briarproject.messaging.simplex.OutgoingSimplexConnectionTest'/> <test name='org.briarproject.messaging.SimplexMessagingIntegrationTest'/>
<test name='org.briarproject.messaging.simplex.SimplexMessagingIntegrationTest'/> <test name='org.briarproject.messaging.SinglePassOutgoingSessionTest'/>
<test name='org.briarproject.plugins.PluginManagerImplTest'/> <test name='org.briarproject.plugins.PluginManagerImplTest'/>
<test name='org.briarproject.plugins.file.LinuxRemovableDriveFinderTest'/> <test name='org.briarproject.plugins.file.LinuxRemovableDriveFinderTest'/>
<test name='org.briarproject.plugins.file.MacRemovableDriveFinderTest'/> <test name='org.briarproject.plugins.file.MacRemovableDriveFinderTest'/>

View File

@@ -45,8 +45,6 @@ import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseModule; import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule; import org.briarproject.event.EventModule;
import org.briarproject.messaging.MessagingModule; import org.briarproject.messaging.MessagingModule;
import org.briarproject.messaging.duplex.DuplexMessagingModule;
import org.briarproject.messaging.simplex.SimplexMessagingModule;
import org.briarproject.reliability.ReliabilityModule; import org.briarproject.reliability.ReliabilityModule;
import org.briarproject.serial.SerialModule; import org.briarproject.serial.SerialModule;
import org.briarproject.transport.TransportModule; import org.briarproject.transport.TransportModule;
@@ -81,7 +79,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
new TestLifecycleModule(), new TestSystemModule(), new TestLifecycleModule(), new TestSystemModule(),
new TestUiModule(), new CryptoModule(), new DatabaseModule(), new TestUiModule(), new CryptoModule(), new DatabaseModule(),
new EventModule(), new MessagingModule(), new EventModule(), new MessagingModule(),
new DuplexMessagingModule(), new SimplexMessagingModule(),
new ReliabilityModule(), new SerialModule(), new ReliabilityModule(), new SerialModule(),
new TransportModule()); new TransportModule());
streamReaderFactory = i.getInstance(StreamReaderFactory.class); streamReaderFactory = i.getInstance(StreamReaderFactory.class);
@@ -125,29 +122,29 @@ public class ProtocolIntegrationTest extends BriarTestCase {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamContext ctx = new StreamContext(contactId, transportId, StreamContext ctx = new StreamContext(contactId, transportId,
secret.clone(), 0, true); secret.clone(), 0, true);
StreamWriter conn = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(out,
out, MAX_FRAME_LENGTH, Long.MAX_VALUE, ctx, false, true); MAX_FRAME_LENGTH, ctx);
OutputStream out1 = conn.getOutputStream(); OutputStream out1 = streamWriter.getOutputStream();
PacketWriter writer = packetWriterFactory.createPacketWriter(out1, PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out1,
false); false);
writer.writeAck(new Ack(messageIds)); packetWriter.writeAck(new Ack(messageIds));
writer.writeMessage(message.getSerialised()); packetWriter.writeMessage(message.getSerialised());
writer.writeMessage(message1.getSerialised()); packetWriter.writeMessage(message1.getSerialised());
writer.writeOffer(new Offer(messageIds)); packetWriter.writeOffer(new Offer(messageIds));
writer.writeRequest(new Request(messageIds)); packetWriter.writeRequest(new Request(messageIds));
SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1); SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
writer.writeSubscriptionUpdate(su); packetWriter.writeSubscriptionUpdate(su);
TransportUpdate tu = new TransportUpdate(transportId, TransportUpdate tu = new TransportUpdate(transportId,
transportProperties, 1); transportProperties, 1);
writer.writeTransportUpdate(tu); packetWriter.writeTransportUpdate(tu);
writer.flush(); packetWriter.flush();
return out.toByteArray(); return out.toByteArray();
} }
@@ -158,44 +155,44 @@ public class ProtocolIntegrationTest extends BriarTestCase {
// FIXME: Check that the expected tag was received // FIXME: Check that the expected tag was received
StreamContext ctx = new StreamContext(contactId, transportId, StreamContext ctx = new StreamContext(contactId, transportId,
secret.clone(), 0, false); secret.clone(), 0, false);
StreamReader conn = streamReaderFactory.createStreamReader( StreamReader streamReader = streamReaderFactory.createStreamReader(in,
in, MAX_FRAME_LENGTH, ctx, true, true); MAX_FRAME_LENGTH, ctx);
InputStream in1 = conn.getInputStream(); InputStream in1 = streamReader.getInputStream();
PacketReader reader = packetReaderFactory.createPacketReader(in1); PacketReader packetReader = packetReaderFactory.createPacketReader(in1);
// Read the ack // Read the ack
assertTrue(reader.hasAck()); assertTrue(packetReader.hasAck());
Ack a = reader.readAck(); Ack a = packetReader.readAck();
assertEquals(messageIds, a.getMessageIds()); assertEquals(messageIds, a.getMessageIds());
// Read and verify the messages // Read and verify the messages
assertTrue(reader.hasMessage()); assertTrue(packetReader.hasMessage());
UnverifiedMessage m = reader.readMessage(); UnverifiedMessage m = packetReader.readMessage();
checkMessageEquality(message, messageVerifier.verifyMessage(m)); checkMessageEquality(message, messageVerifier.verifyMessage(m));
assertTrue(reader.hasMessage()); assertTrue(packetReader.hasMessage());
m = reader.readMessage(); m = packetReader.readMessage();
checkMessageEquality(message1, messageVerifier.verifyMessage(m)); checkMessageEquality(message1, messageVerifier.verifyMessage(m));
assertFalse(reader.hasMessage()); assertFalse(packetReader.hasMessage());
// Read the offer // Read the offer
assertTrue(reader.hasOffer()); assertTrue(packetReader.hasOffer());
Offer o = reader.readOffer(); Offer o = packetReader.readOffer();
assertEquals(messageIds, o.getMessageIds()); assertEquals(messageIds, o.getMessageIds());
// Read the request // Read the request
assertTrue(reader.hasRequest()); assertTrue(packetReader.hasRequest());
Request req = reader.readRequest(); Request req = packetReader.readRequest();
assertEquals(messageIds, req.getMessageIds()); assertEquals(messageIds, req.getMessageIds());
// Read the subscription update // Read the subscription update
assertTrue(reader.hasSubscriptionUpdate()); assertTrue(packetReader.hasSubscriptionUpdate());
SubscriptionUpdate su = reader.readSubscriptionUpdate(); SubscriptionUpdate su = packetReader.readSubscriptionUpdate();
assertEquals(Arrays.asList(group), su.getGroups()); assertEquals(Arrays.asList(group), su.getGroups());
assertEquals(1, su.getVersion()); assertEquals(1, su.getVersion());
// Read the transport update // Read the transport update
assertTrue(reader.hasTransportUpdate()); assertTrue(packetReader.hasTransportUpdate());
TransportUpdate tu = reader.readTransportUpdate(); TransportUpdate tu = packetReader.readTransportUpdate();
assertEquals(transportId, tu.getId()); assertEquals(transportId, tu.getId());
assertEquals(transportProperties, tu.getProperties()); assertEquals(transportProperties, tu.getProperties());
assertEquals(1, tu.getVersion()); assertEquals(1, tu.getVersion());

View File

@@ -25,10 +25,8 @@ public class KeyDerivationTest extends BriarTestCase {
@Test @Test
public void testKeysAreDistinct() { public void testKeysAreDistinct() {
List<SecretKey> keys = new ArrayList<SecretKey>(); List<SecretKey> keys = new ArrayList<SecretKey>();
keys.add(crypto.deriveFrameKey(secret, 0, false, false)); keys.add(crypto.deriveFrameKey(secret, 0, true));
keys.add(crypto.deriveFrameKey(secret, 0, false, true)); keys.add(crypto.deriveFrameKey(secret, 0, false));
keys.add(crypto.deriveFrameKey(secret, 0, true, false));
keys.add(crypto.deriveFrameKey(secret, 0, true, true));
keys.add(crypto.deriveTagKey(secret, true)); keys.add(crypto.deriveTagKey(secret, true));
keys.add(crypto.deriveTagKey(secret, false)); keys.add(crypto.deriveTagKey(secret, false));
for(int i = 0; i < 4; i++) { for(int i = 0; i < 4; i++) {

View File

@@ -46,8 +46,6 @@ import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseModule; import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule; import org.briarproject.event.EventModule;
import org.briarproject.messaging.duplex.DuplexMessagingModule;
import org.briarproject.messaging.simplex.SimplexMessagingModule;
import org.briarproject.serial.SerialModule; import org.briarproject.serial.SerialModule;
import org.briarproject.transport.TransportModule; import org.briarproject.transport.TransportModule;
import org.junit.Test; import org.junit.Test;
@@ -67,8 +65,7 @@ public class ConstantsTest extends BriarTestCase {
Injector i = Guice.createInjector(new TestDatabaseModule(), Injector i = Guice.createInjector(new TestDatabaseModule(),
new TestLifecycleModule(), new TestSystemModule(), new TestLifecycleModule(), new TestSystemModule(),
new CryptoModule(), new DatabaseModule(), new EventModule(), new CryptoModule(), new DatabaseModule(), new EventModule(),
new MessagingModule(), new DuplexMessagingModule(), new MessagingModule(), new SerialModule(),
new SimplexMessagingModule(), new SerialModule(),
new TransportModule()); new TransportModule());
crypto = i.getInstance(CryptoComponent.class); crypto = i.getInstance(CryptoComponent.class);
groupFactory = i.getInstance(GroupFactory.class); groupFactory = i.getInstance(GroupFactory.class);

View File

@@ -1,4 +1,4 @@
package org.briarproject.messaging.simplex; package org.briarproject.messaging;
import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH; import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
@@ -30,9 +30,9 @@ import org.briarproject.api.messaging.GroupFactory;
import org.briarproject.api.messaging.Message; import org.briarproject.api.messaging.Message;
import org.briarproject.api.messaging.MessageFactory; import org.briarproject.api.messaging.MessageFactory;
import org.briarproject.api.messaging.MessageVerifier; import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketReaderFactory; import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriterFactory; import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.Endpoint; import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
@@ -41,8 +41,6 @@ import org.briarproject.api.transport.TagRecogniser;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseModule; import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule; import org.briarproject.event.EventModule;
import org.briarproject.messaging.MessagingModule;
import org.briarproject.messaging.duplex.DuplexMessagingModule;
import org.briarproject.plugins.ImmediateExecutor; import org.briarproject.plugins.ImmediateExecutor;
import org.briarproject.serial.SerialModule; import org.briarproject.serial.SerialModule;
import org.briarproject.transport.TransportModule; import org.briarproject.transport.TransportModule;
@@ -88,8 +86,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
return Guice.createInjector(new TestDatabaseModule(dir), return Guice.createInjector(new TestDatabaseModule(dir),
new TestLifecycleModule(), new TestSystemModule(), new TestLifecycleModule(), new TestSystemModule(),
new CryptoModule(), new DatabaseModule(), new EventModule(), new CryptoModule(), new DatabaseModule(), new EventModule(),
new MessagingModule(), new DuplexMessagingModule(), new MessagingModule(), new SerialModule(),
new SimplexMessagingModule(), new SerialModule(),
new TransportModule()); new TransportModule());
} }
@@ -140,29 +137,26 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
Message message = messageFactory.createAnonymousMessage(null, group, Message message = messageFactory.createAnonymousMessage(null, group,
contentType, timestamp, body); contentType, timestamp, body);
db.addLocalMessage(message); db.addLocalMessage(message);
// Create an outgoing simplex connection // Create an outgoing messaging session
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionRegistry connRegistry = StreamWriterFactory streamWriterFactory =
alice.getInstance(ConnectionRegistry.class);
StreamWriterFactory connWriterFactory =
alice.getInstance(StreamWriterFactory.class); alice.getInstance(StreamWriterFactory.class);
PacketWriterFactory packetWriterFactory = PacketWriterFactory packetWriterFactory =
alice.getInstance(PacketWriterFactory.class); alice.getInstance(PacketWriterFactory.class);
TestSimplexTransportWriter transport = new TestSimplexTransportWriter( TestTransportConnectionWriter transport =
out, Long.MAX_VALUE, Long.MAX_VALUE); new TestTransportConnectionWriter(out);
StreamContext ctx = km.getStreamContext(contactId, transportId); StreamContext ctx = km.getStreamContext(contactId, transportId);
assertNotNull(ctx); assertNotNull(ctx);
OutgoingSimplexConnection simplex = new OutgoingSimplexConnection(db, MessagingSession session = new SinglePassOutgoingSession(db,
connRegistry, connWriterFactory, packetWriterFactory, ctx, new ImmediateExecutor(), streamWriterFactory,
transport); packetWriterFactory, ctx, transport);
// Write whatever needs to be written // Write whatever needs to be written
simplex.write(); session.run();
assertTrue(transport.getDisposed()); transport.dispose(false);
assertFalse(transport.getException());
// Clean up // Clean up
km.stop(); km.stop();
db.close(); db.close();
// Return the contents of the simplex connection // Return the contents of the stream
return out.toByteArray(); return out.toByteArray();
} }
@@ -204,28 +198,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
assertEquals(tag.length, read); assertEquals(tag.length, read);
StreamContext ctx = rec.recogniseTag(transportId, tag); StreamContext ctx = rec.recogniseTag(transportId, tag);
assertNotNull(ctx); assertNotNull(ctx);
// Create an incoming simplex connection // Create an incoming messaging session
MessageVerifier messageVerifier = MessageVerifier messageVerifier =
bob.getInstance(MessageVerifier.class); bob.getInstance(MessageVerifier.class);
ConnectionRegistry connRegistry = StreamReaderFactory streamReaderFactory =
bob.getInstance(ConnectionRegistry.class);
StreamReaderFactory connWriterFactory =
bob.getInstance(StreamReaderFactory.class); bob.getInstance(StreamReaderFactory.class);
PacketReaderFactory packetWriterFactory = PacketReaderFactory packetReaderFactory =
bob.getInstance(PacketReaderFactory.class); bob.getInstance(PacketReaderFactory.class);
TestSimplexTransportReader transport = TestTransportConnectionReader transport =
new TestSimplexTransportReader(in); new TestTransportConnectionReader(in);
IncomingSimplexConnection simplex = new IncomingSimplexConnection( MessagingSession session = new IncomingSession(db,
new ImmediateExecutor(), new ImmediateExecutor(), new ImmediateExecutor(), new ImmediateExecutor(),
messageVerifier, db, connRegistry, connWriterFactory, messageVerifier, streamReaderFactory, packetReaderFactory,
packetWriterFactory, ctx, transport); ctx, transport);
// No messages should have been added yet // No messages should have been added yet
assertFalse(listener.messageAdded); assertFalse(listener.messageAdded);
// Read whatever needs to be read // Read whatever needs to be read
simplex.read(); session.run();
assertTrue(transport.getDisposed()); transport.dispose(false, true);
assertFalse(transport.getException());
assertTrue(transport.getRecognised());
// The private message from Alice should have been added // The private message from Alice should have been added
assertTrue(listener.messageAdded); assertTrue(listener.messageAdded);
// Clean up // Clean up

View File

@@ -1,9 +1,7 @@
package org.briarproject.messaging.simplex; package org.briarproject.messaging;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MIN_STREAM_LENGTH;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -23,14 +21,12 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.messaging.Ack; import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.MessageId; import org.briarproject.api.messaging.MessageId;
import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketWriterFactory; import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.transport.ConnectionRegistry;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
import org.briarproject.event.EventModule; import org.briarproject.event.EventModule;
import org.briarproject.messaging.MessagingModule;
import org.briarproject.messaging.duplex.DuplexMessagingModule;
import org.briarproject.serial.SerialModule; import org.briarproject.serial.SerialModule;
import org.briarproject.transport.TransportModule; import org.briarproject.transport.TransportModule;
import org.jmock.Expectations; import org.jmock.Expectations;
@@ -42,39 +38,37 @@ import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Module; import com.google.inject.Module;
public class OutgoingSimplexConnectionTest extends BriarTestCase { public class SinglePassOutgoingSessionTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test // FIXME: This is an integration test, not a unit test
private final Mockery context; private final Mockery context;
private final DatabaseComponent db; private final DatabaseComponent db;
private final ConnectionRegistry connRegistry; private final Executor dbExecutor;
private final StreamWriterFactory connWriterFactory; private final StreamWriterFactory streamWriterFactory;
private final PacketWriterFactory packetWriterFactory; private final PacketWriterFactory packetWriterFactory;
private final ContactId contactId; private final ContactId contactId;
private final MessageId messageId; private final MessageId messageId;
private final TransportId transportId; private final TransportId transportId;
private final byte[] secret; private final byte[] secret;
public OutgoingSimplexConnectionTest() { public SinglePassOutgoingSessionTest() {
context = new Mockery(); context = new Mockery();
db = context.mock(DatabaseComponent.class); db = context.mock(DatabaseComponent.class);
dbExecutor = Executors.newSingleThreadExecutor();
Module testModule = new AbstractModule() { Module testModule = new AbstractModule() {
@Override @Override
public void configure() { public void configure() {
bind(DatabaseComponent.class).toInstance(db); bind(DatabaseComponent.class).toInstance(db);
bind(Executor.class).annotatedWith( bind(Executor.class).annotatedWith(
DatabaseExecutor.class).toInstance( DatabaseExecutor.class).toInstance(dbExecutor);
Executors.newCachedThreadPool());
} }
}; };
Injector i = Guice.createInjector(testModule, Injector i = Guice.createInjector(testModule,
new TestLifecycleModule(), new TestSystemModule(), new TestLifecycleModule(), new TestSystemModule(),
new CryptoModule(), new EventModule(), new MessagingModule(), new CryptoModule(), new EventModule(), new MessagingModule(),
new DuplexMessagingModule(), new SimplexMessagingModule(),
new SerialModule(), new TransportModule()); new SerialModule(), new TransportModule());
connRegistry = i.getInstance(ConnectionRegistry.class); streamWriterFactory = i.getInstance(StreamWriterFactory.class);
connWriterFactory = i.getInstance(StreamWriterFactory.class);
packetWriterFactory = i.getInstance(PacketWriterFactory.class); packetWriterFactory = i.getInstance(PacketWriterFactory.class);
contactId = new ContactId(234); contactId = new ContactId(234);
messageId = new MessageId(TestUtils.getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
@@ -83,34 +77,15 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
new Random().nextBytes(secret); new Random().nextBytes(secret);
} }
@Test
public void testConnectionTooShort() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
out, MAX_PACKET_LENGTH, Long.MAX_VALUE);
StreamContext ctx = new StreamContext(contactId, transportId,
secret, 0, true);
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
connRegistry, connWriterFactory, packetWriterFactory, ctx,
transport);
connection.write();
// Nothing should have been written
assertEquals(0, out.size());
// The transport should have been disposed with exception == true
assertTrue(transport.getDisposed());
assertTrue(transport.getException());
}
@Test @Test
public void testNothingToSend() throws Exception { public void testNothingToSend() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
TestSimplexTransportWriter transport = new TestSimplexTransportWriter( TestTransportConnectionWriter writer =
out, MIN_STREAM_LENGTH, Long.MAX_VALUE); new TestTransportConnectionWriter(out);
StreamContext ctx = new StreamContext(contactId, transportId, StreamContext ctx = new StreamContext(contactId, transportId,
secret, 0, true); secret, 0, true);
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db, MessagingSession session = new SinglePassOutgoingSession(db, dbExecutor,
connRegistry, connWriterFactory, packetWriterFactory, ctx, streamWriterFactory, packetWriterFactory, ctx, writer);
transport);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// No transport acks to send // No transport acks to send
oneOf(db).generateTransportAcks(contactId); oneOf(db).generateTransportAcks(contactId);
@@ -141,25 +116,22 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
with(any(long.class))); with(any(long.class)));
will(returnValue(null)); will(returnValue(null));
}}); }});
connection.write(); session.run();
// Only the tag and an empty final frame should have been written // Only the tag and an empty final frame should have been written
assertEquals(TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH, out.size()); assertEquals(TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH, out.size());
// The transport should have been disposed with exception == false
assertTrue(transport.getDisposed());
assertFalse(transport.getException());
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testSomethingToSend() throws Exception { public void testSomethingToSend() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
TestSimplexTransportWriter transport = new TestSimplexTransportWriter( TestTransportConnectionWriter writer =
out, MIN_STREAM_LENGTH, Long.MAX_VALUE); new TestTransportConnectionWriter(out);
StreamContext ctx = new StreamContext(contactId, transportId, StreamContext ctx = new StreamContext(contactId, transportId,
secret, 0, true); secret, 0, true);
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db, MessagingSession session = new SinglePassOutgoingSession(db, dbExecutor,
connRegistry, connWriterFactory, packetWriterFactory, ctx, streamWriterFactory, packetWriterFactory,
transport); ctx, writer);
final byte[] raw = new byte[1234]; final byte[] raw = new byte[1234];
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// No transport acks to send // No transport acks to send
@@ -198,13 +170,10 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
with(any(long.class))); with(any(long.class)));
will(returnValue(null)); will(returnValue(null));
}}); }});
connection.write(); session.run();
// Something should have been written // Something should have been written
int overhead = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH; int overhead = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH;
assertTrue(out.size() > overhead + UniqueId.LENGTH + raw.length); assertTrue(out.size() > overhead + UniqueId.LENGTH + raw.length);
// The transport should have been disposed with exception == false
assertTrue(transport.getDisposed());
assertFalse(transport.getException());
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
} }

View File

@@ -0,0 +1,35 @@
package org.briarproject.messaging;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.InputStream;
import org.briarproject.api.plugins.TransportConnectionReader;
class TestTransportConnectionReader implements TransportConnectionReader {
private final InputStream in;
private boolean disposed = false;
TestTransportConnectionReader(InputStream in) {
this.in = in;
}
public int getMaxFrameLength() {
return MAX_FRAME_LENGTH;
}
public long getMaxLatency() {
return Long.MAX_VALUE;
}
public InputStream getInputStream() {
return in;
}
public void dispose(boolean exception, boolean recognised) {
assert !disposed;
disposed = true;
}
}

View File

@@ -0,0 +1,40 @@
package org.briarproject.messaging;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import org.briarproject.api.plugins.TransportConnectionWriter;
class TestTransportConnectionWriter implements TransportConnectionWriter {
private final ByteArrayOutputStream out;
private boolean disposed = false;
TestTransportConnectionWriter(ByteArrayOutputStream out) {
this.out = out;
}
public long getCapacity() {
return Long.MAX_VALUE;
}
public int getMaxFrameLength() {
return MAX_FRAME_LENGTH;
}
public long getMaxLatency() {
return Long.MAX_VALUE;
}
public OutputStream getOutputStream() {
return out;
}
public void dispose(boolean exception) {
assert !disposed;
disposed = true;
}
}

View File

@@ -1,45 +0,0 @@
package org.briarproject.messaging.simplex;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.InputStream;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
class TestSimplexTransportReader implements SimplexTransportReader {
private final InputStream in;
private boolean disposed = false, exception = false, recognised = false;
TestSimplexTransportReader(InputStream in) {
this.in = in;
}
public int getMaxFrameLength() {
return MAX_FRAME_LENGTH;
}
public InputStream getInputStream() {
return in;
}
public void dispose(boolean exception, boolean recognised) {
assert !disposed;
disposed = true;
this.exception = exception;
this.recognised = recognised;
}
boolean getDisposed() {
return disposed;
}
boolean getException() {
return exception;
}
boolean getRecognised() {
return recognised;
}
}

View File

@@ -1,53 +0,0 @@
package org.briarproject.messaging.simplex;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
class TestSimplexTransportWriter implements SimplexTransportWriter {
private final ByteArrayOutputStream out;
private final long capacity, maxLatency;
private boolean disposed = false, exception = false;
TestSimplexTransportWriter(ByteArrayOutputStream out, long capacity,
long maxLatency) {
this.out = out;
this.capacity = capacity;
this.maxLatency = maxLatency;
}
public long getCapacity() {
return capacity;
}
public int getMaxFrameLength() {
return MAX_FRAME_LENGTH;
}
public long getMaxLatency() {
return maxLatency;
}
public OutputStream getOutputStream() {
return out;
}
public void dispose(boolean exception) {
assert !disposed;
disposed = true;
this.exception = exception;
}
boolean getDisposed() {
return disposed;
}
boolean getException() {
return exception;
}
}

View File

@@ -7,6 +7,8 @@ import java.util.Scanner;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.crypto.PseudoRandom; import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
@@ -21,12 +23,14 @@ abstract class DuplexTest {
protected void sendChallengeReceiveResponse(DuplexTransportConnection d) { protected void sendChallengeReceiveResponse(DuplexTransportConnection d) {
assert plugin != null; assert plugin != null;
TransportConnectionReader r = d.getReader();
TransportConnectionWriter w = d.getWriter();
try { try {
PrintStream out = new PrintStream(d.getOutputStream()); PrintStream out = new PrintStream(w.getOutputStream());
out.println(CHALLENGE); out.println(CHALLENGE);
out.flush(); out.flush();
System.out.println("Sent challenge: " + CHALLENGE); System.out.println("Sent challenge: " + CHALLENGE);
Scanner in = new Scanner(d.getInputStream()); Scanner in = new Scanner(r.getInputStream());
if(in.hasNextLine()) { if(in.hasNextLine()) {
String response = in.nextLine(); String response = in.nextLine();
System.out.println("Received response: " + response); System.out.println("Received response: " + response);
@@ -38,11 +42,13 @@ abstract class DuplexTest {
} else { } else {
System.out.println("No response"); System.out.println("No response");
} }
d.dispose(false, true); r.dispose(false, true);
w.dispose(false);
} catch(IOException e) { } catch(IOException e) {
e.printStackTrace(); e.printStackTrace();
try { try {
d.dispose(true, true); r.dispose(true, true);
w.dispose(true);
} catch(IOException e1) { } catch(IOException e1) {
e1.printStackTrace(); e1.printStackTrace();
} }
@@ -51,13 +57,16 @@ abstract class DuplexTest {
protected void receiveChallengeSendResponse(DuplexTransportConnection d) { protected void receiveChallengeSendResponse(DuplexTransportConnection d) {
assert plugin != null; assert plugin != null;
TransportConnectionReader r = d.getReader();
TransportConnectionWriter w = d.getWriter();
try { try {
Scanner in = new Scanner(d.getInputStream()); Scanner in = new Scanner(r.getInputStream());
if(in.hasNextLine()) { if(in.hasNextLine()) {
String challenge = in.nextLine(); String challenge = in.nextLine();
System.out.println("Received challenge: " + challenge); System.out.println("Received challenge: " + challenge);
if(CHALLENGE.equals(challenge)) { if(CHALLENGE.equals(challenge)) {
PrintStream out = new PrintStream(d.getOutputStream());
PrintStream out = new PrintStream(w.getOutputStream());
out.println(RESPONSE); out.println(RESPONSE);
out.flush(); out.flush();
System.out.println("Sent response: " + RESPONSE); System.out.println("Sent response: " + RESPONSE);
@@ -67,11 +76,13 @@ abstract class DuplexTest {
} else { } else {
System.out.println("No challenge"); System.out.println("No challenge");
} }
d.dispose(false, true); r.dispose(false, true);
w.dispose(false);
} catch(IOException e) { } catch(IOException e) {
e.printStackTrace(); e.printStackTrace();
try { try {
d.dispose(true, true); r.dispose(true, true);
w.dispose(true);
} catch(IOException e1) { } catch(IOException e1) {
e1.printStackTrace(); e1.printStackTrace();
} }

View File

@@ -15,8 +15,8 @@ import org.briarproject.BriarTestCase;
import org.briarproject.TestFileUtils; import org.briarproject.TestFileUtils;
import org.briarproject.TestUtils; import org.briarproject.TestUtils;
import org.briarproject.api.ContactId; import org.briarproject.api.ContactId;
import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.simplex.SimplexPluginCallback; import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.system.FileUtils; import org.briarproject.api.system.FileUtils;
import org.briarproject.plugins.ImmediateExecutor; import org.briarproject.plugins.ImmediateExecutor;
import org.briarproject.plugins.file.RemovableDriveMonitor.Callback; import org.briarproject.plugins.file.RemovableDriveMonitor.Callback;
@@ -32,6 +32,7 @@ public class RemovableDrivePluginTest extends BriarTestCase {
private final ContactId contactId = new ContactId(234); private final ContactId contactId = new ContactId(234);
private final FileUtils fileUtils = new TestFileUtils(); private final FileUtils fileUtils = new TestFileUtils();
@Override
@Before @Before
public void setUp() { public void setUp() {
testDir.mkdirs(); testDir.mkdirs();
@@ -253,7 +254,7 @@ public class RemovableDrivePluginTest extends BriarTestCase {
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0); fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
plugin.start(); plugin.start();
SimplexTransportWriter writer = plugin.createWriter(contactId); TransportConnectionWriter writer = plugin.createWriter(contactId);
assertNotNull(writer); assertNotNull(writer);
// The output file should exist and should be empty // The output file should exist and should be empty
File[] files = drive1.listFiles(); File[] files = drive1.listFiles();
@@ -352,6 +353,7 @@ public class RemovableDrivePluginTest extends BriarTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Override
@After @After
public void tearDown() { public void tearDown() {
TestUtils.deleteTestDirectory(testDir); TestUtils.deleteTestDirectory(testDir);

View File

@@ -278,7 +278,8 @@ public class ModemPluginTest extends BriarTestCase {
public Object invoke(Invocation invocation) throws Throwable { public Object invoke(Invocation invocation) throws Throwable {
DuplexTransportConnection conn = DuplexTransportConnection conn =
(DuplexTransportConnection) invocation.getParameter(1); (DuplexTransportConnection) invocation.getParameter(1);
conn.dispose(false, true); conn.getReader().dispose(false, true);
conn.getWriter().dispose(false);
invoked.countDown(); invoked.countDown();
return null; return null;
} }

View File

@@ -148,7 +148,8 @@ public class LanTcpPluginTest extends BriarTestCase {
assertTrue(latch.await(5, SECONDS)); assertTrue(latch.await(5, SECONDS));
assertFalse(error.get()); assertFalse(error.get());
// Clean up // Clean up
d.dispose(false, true); d.getReader().dispose(false, true);
d.getWriter().dispose(false);
ss.close(); ss.close();
plugin.stop(); plugin.stop();
} }

View File

@@ -25,8 +25,6 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test // FIXME: This is an integration test, not a unit test
private static final int FRAME_LENGTH = 1024; private static final int FRAME_LENGTH = 1024;
private static final int MAX_PAYLOAD_LENGTH =
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final AuthenticatedCipher frameCipher; private final AuthenticatedCipher frameCipher;
@@ -56,7 +54,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase {
// Check that the actual tag and ciphertext match what's expected // Check that the actual tag and ciphertext match what's expected
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, frameKey, FRAME_LENGTH, tag); frameCipher, frameKey, FRAME_LENGTH, tag);
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], payloadLength, false); o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], payloadLength, false);
byte[] actual = out.toByteArray(); byte[] actual = out.toByteArray();
assertEquals(TAG_LENGTH + FRAME_LENGTH, actual.length); assertEquals(TAG_LENGTH + FRAME_LENGTH, actual.length);
@@ -67,93 +65,14 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase {
} }
@Test @Test
public void testInitiatorClosesConnectionWithoutWriting() throws Exception { public void testCloseConnectionWithoutWriting() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
// Initiator's constructor // Initiator's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(), frameCipher, crypto.generateSecretKey(), FRAME_LENGTH, tag);
FRAME_LENGTH, tag);
// Write an empty final frame without having written any other frames // Write an empty final frame without having written any other frames
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true); o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true);
// The tag and the empty frame should be written to the output stream // The tag and the empty frame should be written to the output stream
assertEquals(TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH, out.size()); assertEquals(TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH, out.size());
} }
@Test
public void testResponderClosesConnectionWithoutWriting() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Responder's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH);
// Write an empty final frame without having written any other frames
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true);
// An empty final frame should be written to the output stream
assertEquals(HEADER_LENGTH + MAC_LENGTH, out.size());
}
@Test
public void testRemainingCapacityWithTag() throws Exception {
int MAX_PAYLOAD_LENGTH = FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Initiator's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH, tag);
// There should be space for nine full frames and one partial frame
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
assertEquals(10 * MAX_PAYLOAD_LENGTH - TAG_LENGTH,
o.getRemainingCapacity());
// Write nine frames, each containing a partial payload
for(int i = 0; i < 9; i++) {
o.writeFrame(frame, 123, false);
assertEquals((9 - i) * MAX_PAYLOAD_LENGTH - TAG_LENGTH,
o.getRemainingCapacity());
}
// Write the final frame, which will not be padded
o.writeFrame(frame, 123, true);
int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH;
assertEquals(MAX_PAYLOAD_LENGTH - TAG_LENGTH - finalFrameLength,
o.getRemainingCapacity());
}
@Test
public void testRemainingCapacityWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Responder's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH);
// There should be space for ten full frames
assertEquals(10 * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());
// Write nine frames, each containing a partial payload
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
for(int i = 0; i < 9; i++) {
o.writeFrame(frame, 123, false);
assertEquals((9 - i) * MAX_PAYLOAD_LENGTH,
o.getRemainingCapacity());
}
// Write the final frame, which will not be padded
o.writeFrame(frame, 123, true);
int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH;
assertEquals(MAX_PAYLOAD_LENGTH - finalFrameLength,
o.getRemainingCapacity());
}
@Test
public void testRemainingCapacityLimitedByFrameNumbers() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// The connection has plenty of space so we're limited by frame numbers
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH);
// There should be enough frame numbers for 2^32 frames
assertEquals((1L << 32) * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());
// Write a frame containing a partial payload
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
o.writeFrame(frame, 123, false);
// There should be enough frame numbers for 2^32 - 1 frames
assertEquals(((1L << 32) - 1) * MAX_PAYLOAD_LENGTH,
o.getRemainingCapacity());
}
} }

View File

@@ -1,12 +1,11 @@
package org.briarproject.transport; package org.briarproject.transport;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MIN_STREAM_LENGTH;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Random; import java.util.Random;
@@ -14,13 +13,9 @@ import java.util.Random;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
import org.briarproject.TestLifecycleModule; import org.briarproject.TestLifecycleModule;
import org.briarproject.TestSystemModule; import org.briarproject.TestSystemModule;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.AuthenticatedCipher; import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
import org.junit.Test; import org.junit.Test;
@@ -35,13 +30,10 @@ public class TransportIntegrationTest extends BriarTestCase {
private final int FRAME_LENGTH = 2048; private final int FRAME_LENGTH = 2048;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final StreamWriterFactory streamWriterFactory;
private final ContactId contactId;
private final TransportId transportId;
private final AuthenticatedCipher frameCipher; private final AuthenticatedCipher frameCipher;
private final Random random; private final Random random;
private final byte[] secret; private final byte[] secret;
private final SecretKey frameKey; private final SecretKey tagKey, frameKey;
public TransportIntegrationTest() { public TransportIntegrationTest() {
Module testModule = new AbstractModule() { Module testModule = new AbstractModule() {
@@ -54,15 +46,13 @@ public class TransportIntegrationTest extends BriarTestCase {
Injector i = Guice.createInjector(testModule, new CryptoModule(), Injector i = Guice.createInjector(testModule, new CryptoModule(),
new TestLifecycleModule(), new TestSystemModule()); new TestLifecycleModule(), new TestSystemModule());
crypto = i.getInstance(CryptoComponent.class); crypto = i.getInstance(CryptoComponent.class);
streamWriterFactory = i.getInstance(StreamWriterFactory.class);
contactId = new ContactId(234);
transportId = new TransportId("id");
frameCipher = crypto.getFrameCipher(); frameCipher = crypto.getFrameCipher();
random = new Random(); random = new Random();
// Since we're sending frames to ourselves, we only need outgoing keys // Since we're sending frames to ourselves, we only need outgoing keys
secret = new byte[32]; secret = new byte[32];
random.nextBytes(secret); random.nextBytes(secret);
frameKey = crypto.deriveFrameKey(secret, 0, true, true); tagKey = crypto.deriveTagKey(secret, true);
frameKey = crypto.deriveFrameKey(secret, 0, true);
} }
@Test @Test
@@ -76,6 +66,9 @@ public class TransportIntegrationTest extends BriarTestCase {
} }
private void testWriteAndRead(boolean initiator) throws Exception { private void testWriteAndRead(boolean initiator) throws Exception {
// Encode the tag
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, 0);
// Generate two random frames // Generate two random frames
byte[] frame = new byte[1234]; byte[] frame = new byte[1234];
random.nextBytes(frame); random.nextBytes(frame);
@@ -83,87 +76,46 @@ public class TransportIntegrationTest extends BriarTestCase {
random.nextBytes(frame1); random.nextBytes(frame1);
// Copy the frame key - the copy will be erased // Copy the frame key - the copy will be erased
SecretKey frameCopy = frameKey.copy(); SecretKey frameCopy = frameKey.copy();
// Write the frames // Write the tag and the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
FrameWriter encryptionOut = new OutgoingEncryptionLayer(out, FrameWriter frameWriter = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, frameCipher, frameCopy, FRAME_LENGTH); frameCipher, frameCopy, FRAME_LENGTH, tag);
StreamWriterImpl writer = new StreamWriterImpl(encryptionOut, StreamWriterImpl streamWriter = new StreamWriterImpl(frameWriter,
FRAME_LENGTH); FRAME_LENGTH);
OutputStream out1 = writer.getOutputStream(); OutputStream out1 = streamWriter.getOutputStream();
out1.write(frame); out1.write(frame);
out1.flush(); out1.flush();
out1.write(frame1); out1.write(frame1);
out1.flush(); out1.flush();
byte[] output = out.toByteArray(); byte[] output = out.toByteArray();
assertEquals(FRAME_LENGTH * 2, output.length); assertEquals(TAG_LENGTH + FRAME_LENGTH * 2, output.length);
// Read the tag and the frames back // Read the tag back
ByteArrayInputStream in = new ByteArrayInputStream(output); ByteArrayInputStream in = new ByteArrayInputStream(output);
FrameReader encryptionIn = new IncomingEncryptionLayer(in, frameCipher, byte[] recoveredTag = new byte[tag.length];
read(in, recoveredTag);
assertArrayEquals(tag, recoveredTag);
// Read the frames back
FrameReader frameReader = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH); frameKey, FRAME_LENGTH);
StreamReaderImpl reader = new StreamReaderImpl(encryptionIn, StreamReaderImpl streamReader = new StreamReaderImpl(frameReader,
FRAME_LENGTH); FRAME_LENGTH);
InputStream in1 = reader.getInputStream(); InputStream in1 = streamReader.getInputStream();
byte[] recovered = new byte[frame.length]; byte[] recoveredFrame = new byte[frame.length];
read(in1, recoveredFrame);
assertArrayEquals(frame, recoveredFrame);
byte[] recoveredFrame1 = new byte[frame1.length];
read(in1, recoveredFrame1);
assertArrayEquals(frame1, recoveredFrame1);
streamWriter.close();
streamReader.close();
}
private void read(InputStream in, byte[] dest) throws IOException {
int offset = 0; int offset = 0;
while(offset < recovered.length) { while(offset < dest.length) {
int read = in1.read(recovered, offset, recovered.length - offset); int read = in.read(dest, offset, dest.length - offset);
if(read == -1) break; if(read == -1) break;
offset += read; offset += read;
} }
assertEquals(recovered.length, offset);
assertArrayEquals(frame, recovered);
byte[] recovered1 = new byte[frame1.length];
offset = 0;
while(offset < recovered1.length) {
int read = in1.read(recovered1, offset, recovered1.length - offset);
if(read == -1) break;
offset += read;
}
assertEquals(recovered1.length, offset);
assertArrayEquals(frame1, recovered1);
writer.close();
reader.close();
}
@Test
public void testOverheadWithTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_STREAM_LENGTH);
StreamContext ctx = new StreamContext(contactId, transportId,
secret, 0, true);
StreamWriter w = streamWriterFactory.createStreamWriter(out,
MAX_FRAME_LENGTH, MIN_STREAM_LENGTH, ctx, false, true);
// Check that the connection writer thinks there's room for a packet
long capacity = w.getRemainingCapacity();
assertTrue(capacity > MAX_PACKET_LENGTH);
assertTrue(capacity < MIN_STREAM_LENGTH);
// Check that there really is room for a packet
byte[] payload = new byte[MAX_PACKET_LENGTH];
w.getOutputStream().write(payload);
w.getOutputStream().close();
long used = out.size();
assertTrue(used > MAX_PACKET_LENGTH);
assertTrue(used <= MIN_STREAM_LENGTH);
}
@Test
public void testOverheadWithoutTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_STREAM_LENGTH);
StreamContext ctx = new StreamContext(contactId, transportId,
secret, 0, true);
StreamWriter w = streamWriterFactory.createStreamWriter(out,
MAX_FRAME_LENGTH, MIN_STREAM_LENGTH, ctx, false, false);
// Check that the connection writer thinks there's room for a packet
long capacity = w.getRemainingCapacity();
assertTrue(capacity > MAX_PACKET_LENGTH);
assertTrue(capacity < MIN_STREAM_LENGTH);
// Check that there really is room for a packet
byte[] payload = new byte[MAX_PACKET_LENGTH];
w.getOutputStream().write(payload);
w.getOutputStream().close();
long used = out.size();
assertTrue(used > MAX_PACKET_LENGTH);
assertTrue(used <= MIN_STREAM_LENGTH);
} }
} }