From 64bf1fbbb1cff2871a4e9203c6e9bcc3e009ab20 Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Fri, 25 Jan 2013 15:38:37 +0000 Subject: [PATCH] Part 1 of a major BMP and database refactoring. Tests are broken! The old logic for selecting when to send subscription and transport updates has been removed and not yet replaced. Subscription times have been removed from subscription updates. The database expiry time has been remove from subscription updates and will later get its own update packet. Transport updates have been broken up into one update per transport. Acks for subscription and transport updates have been added. --- .../sf/briar/api/db/DatabaseComponent.java | 40 +- .../event/RemoteTransportsUpdatedEvent.java | 27 - ...Event.java => TransportsUpdatedEvent.java} | 2 +- .../src/net/sf/briar/api/protocol/Ack.java | 16 +- .../src/net/sf/briar/api/protocol/Author.java | 36 +- .../sf/briar/api/protocol/AuthorFactory.java | 2 - .../net/sf/briar/api/protocol/AuthorId.java | 5 +- .../net/sf/briar/api/protocol/GroupId.java | 3 +- .../net/sf/briar/api/protocol/Message.java | 16 +- .../net/sf/briar/api/protocol/MessageId.java | 5 +- .../briar/api/protocol/MessageVerifier.java | 1 + .../src/net/sf/briar/api/protocol/Offer.java | 16 +- .../sf/briar/api/protocol/PacketFactory.java | 20 - .../briar/api/protocol/ProtocolConstants.java | 3 - .../sf/briar/api/protocol/ProtocolReader.java | 6 + .../sf/briar/api/protocol/ProtocolWriter.java | 4 + .../net/sf/briar/api/protocol/Request.java | 23 +- .../briar/api/protocol/SubscriptionAck.java | 16 + .../api/protocol/SubscriptionUpdate.java | 31 +- .../net/sf/briar/api/protocol/Transport.java | 42 - .../sf/briar/api/protocol/TransportAck.java | 23 + .../briar/api/protocol/TransportUpdate.java | 35 +- .../src/net/sf/briar/api/protocol/Types.java | 7 +- .../briar/api/protocol/UnverifiedMessage.java | 33 + .../src/net/sf/briar/api/serial/Reader.java | 2 +- ...vertunnel.org-netlib-0.14-briar-source.jar | Bin 497573 -> 497577 bytes briar-core/src/net/sf/briar/db/Database.java | 165 ++- .../sf/briar/db/DatabaseComponentImpl.java | 266 ++-- .../src/net/sf/briar/db/DatabaseModule.java | 10 +- .../src/net/sf/briar/db/H2Database.java | 4 +- .../src/net/sf/briar/db/JdbcDatabase.java | 1152 +++++++++-------- .../net/sf/briar/invitation/Connector.java | 2 +- .../src/net/sf/briar/protocol/AckImpl.java | 19 - .../src/net/sf/briar/protocol/AckReader.java | 12 +- .../sf/briar/protocol/AuthorFactoryImpl.java | 8 +- .../src/net/sf/briar/protocol/AuthorImpl.java | 29 - .../net/sf/briar/protocol/AuthorReader.java | 8 +- .../net/sf/briar/protocol/GroupReader.java | 1 - .../net/sf/briar/protocol/MessageReader.java | 4 +- .../src/net/sf/briar/protocol/OfferImpl.java | 19 - .../net/sf/briar/protocol/OfferReader.java | 13 +- .../sf/briar/protocol/PacketFactoryImpl.java | 42 - .../net/sf/briar/protocol/ProtocolModule.java | 42 +- .../protocol/ProtocolReaderFactoryImpl.java | 26 +- .../sf/briar/protocol/ProtocolReaderImpl.java | 32 +- .../sf/briar/protocol/ProtocolWriterImpl.java | 62 +- .../net/sf/briar/protocol/RequestImpl.java | 24 - .../net/sf/briar/protocol/RequestReader.java | 13 +- .../briar/protocol/SubscriptionAckReader.java | 20 + .../protocol/SubscriptionUpdateImpl.java | 38 - .../protocol/SubscriptionUpdateReader.java | 44 +- .../sf/briar/protocol/TransportAckReader.java | 24 + .../briar/protocol/TransportUpdateImpl.java | 26 - .../briar/protocol/TransportUpdateReader.java | 61 +- .../protocol/duplex/DuplexConnection.java | 20 +- .../simplex/IncomingSimplexConnection.java | 1 + .../simplex/OutgoingSimplexConnection.java | 17 +- .../src/net/sf/briar/serial/ReaderImpl.java | 4 +- .../net/sf/briar/ProtocolIntegrationTest.java | 43 +- .../briar/db/DatabaseComponentImplTest.java | 30 +- .../sf/briar/db/DatabaseComponentTest.java | 239 ++-- .../net/sf/briar/protocol/AckReaderTest.java | 35 +- .../net/sf/briar/protocol/ConstantsTest.java | 47 +- .../sf/briar/protocol/OfferReaderTest.java | 35 +- .../protocol/ProtocolWriterImplTest.java | 9 +- .../sf/briar/protocol/RequestReaderTest.java | 31 +- .../SimplexProtocolIntegrationTest.java | 16 +- 67 files changed, 1378 insertions(+), 1729 deletions(-) delete mode 100644 briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java rename briar-api/src/net/sf/briar/api/db/event/{LocalTransportsUpdatedEvent.java => TransportsUpdatedEvent.java} (66%) delete mode 100644 briar-api/src/net/sf/briar/api/protocol/PacketFactory.java create mode 100644 briar-api/src/net/sf/briar/api/protocol/SubscriptionAck.java delete mode 100644 briar-api/src/net/sf/briar/api/protocol/Transport.java create mode 100644 briar-api/src/net/sf/briar/api/protocol/TransportAck.java delete mode 100644 briar-core/src/net/sf/briar/protocol/AckImpl.java delete mode 100644 briar-core/src/net/sf/briar/protocol/AuthorImpl.java delete mode 100644 briar-core/src/net/sf/briar/protocol/OfferImpl.java delete mode 100644 briar-core/src/net/sf/briar/protocol/PacketFactoryImpl.java delete mode 100644 briar-core/src/net/sf/briar/protocol/RequestImpl.java create mode 100644 briar-core/src/net/sf/briar/protocol/SubscriptionAckReader.java delete mode 100644 briar-core/src/net/sf/briar/protocol/SubscriptionUpdateImpl.java create mode 100644 briar-core/src/net/sf/briar/protocol/TransportAckReader.java delete mode 100644 briar-core/src/net/sf/briar/protocol/TransportUpdateImpl.java diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java index 37675ac399..c908d3e471 100644 --- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java +++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java @@ -17,8 +17,9 @@ import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.SubscriptionAck; import net.sf.briar.api.protocol.SubscriptionUpdate; -import net.sf.briar.api.protocol.Transport; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.transport.ContactTransport; @@ -66,6 +67,9 @@ public interface DatabaseComponent { */ void addSecrets(Collection<TemporarySecret> secrets) throws DbException; + /** Adds a transport to the database. */ + void addTransport(TransportId t) throws DbException; + /** * Generates an acknowledgement for the given contact. Returns null if * there are no messages to acknowledge. @@ -98,18 +102,32 @@ public interface DatabaseComponent { */ Offer generateOffer(ContactId c, int maxMessages) throws DbException; + /** + * Generates a subscription ack for the given contact. Returns null if no + * ack is due. + */ + SubscriptionAck generateSubscriptionAck(ContactId c) throws DbException; + /** * Generates a subscription update for the given contact. Returns null if - * an update is not due. + * no update is due. */ SubscriptionUpdate generateSubscriptionUpdate(ContactId c) throws DbException; /** - * Generates a transport update for the given contact. Returns null if an - * update is not due. + * Generates a batch of transport acks for the given contact. Returns null + * if no acks are due. */ - TransportUpdate generateTransportUpdate(ContactId c) throws DbException; + Collection<TransportAck> generateTransportAcks(ContactId c) + throws DbException; + + /** + * Generates a batch of transport updates for the given contact. Returns + * null if no updates are due. + */ + Collection<TransportUpdate> generateTransportUpdates(ContactId c) + throws DbException; /** Returns the configuration for the given transport. */ TransportConfig getConfig(TransportId t) throws DbException; @@ -120,9 +138,6 @@ public interface DatabaseComponent { /** Returns the local transport properties for the given transport. */ TransportProperties getLocalProperties(TransportId t) throws DbException; - /** Returns all local transports. */ - Collection<Transport> getLocalTransports() throws DbException; - /** Returns the headers of all messages in the given group. */ Collection<MessageHeader> getMessageHeaders(GroupId g) throws DbException; @@ -168,7 +183,7 @@ public interface DatabaseComponent { void mergeLocalProperties(TransportId t, TransportProperties p) throws DbException; - /** Processes an acknowledgement from the given contact. */ + /** Processes an ack from the given contact. */ void receiveAck(ContactId c, Ack a) throws DbException; /** Processes a message from the given contact. */ @@ -184,10 +199,17 @@ public interface DatabaseComponent { */ Request receiveOffer(ContactId c, Offer o) throws DbException; + /** Processes a subscription ack from the given contact. */ + void receiveSubscriptionAck(ContactId c, SubscriptionAck a) + throws DbException; + /** Processes a subscription update from the given contact. */ void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate s) throws DbException; + /** Processes a transport ack from the given contact. */ + void receiveTransportAck(ContactId c, TransportAck a) throws DbException; + /** Processes a transport update from the given contact. */ void receiveTransportUpdate(ContactId c, TransportUpdate t) throws DbException; diff --git a/briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java deleted file mode 100644 index 501eb886d3..0000000000 --- a/briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.sf.briar.api.db.event; - -import java.util.Collection; - -import net.sf.briar.api.ContactId; -import net.sf.briar.api.protocol.Transport; - -/** An event that is broadcast when a contact's transports are updated. */ -public class RemoteTransportsUpdatedEvent extends DatabaseEvent { - - private final ContactId contactId; - private final Collection<Transport> transports; - - public RemoteTransportsUpdatedEvent(ContactId contactId, - Collection<Transport> transports) { - this.contactId = contactId; - this.transports = transports; - } - - public ContactId getContactId() { - return contactId; - } - - public Collection<Transport> getTransports() { - return transports; - } -} diff --git a/briar-api/src/net/sf/briar/api/db/event/LocalTransportsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/TransportsUpdatedEvent.java similarity index 66% rename from briar-api/src/net/sf/briar/api/db/event/LocalTransportsUpdatedEvent.java rename to briar-api/src/net/sf/briar/api/db/event/TransportsUpdatedEvent.java index c60c23b83b..782b11cfe7 100644 --- a/briar-api/src/net/sf/briar/api/db/event/LocalTransportsUpdatedEvent.java +++ b/briar-api/src/net/sf/briar/api/db/event/TransportsUpdatedEvent.java @@ -4,6 +4,6 @@ package net.sf.briar.api.db.event; * An event that is broadcast when the local transport properties are * updated. */ -public class LocalTransportsUpdatedEvent extends DatabaseEvent { +public class TransportsUpdatedEvent extends DatabaseEvent { } diff --git a/briar-api/src/net/sf/briar/api/protocol/Ack.java b/briar-api/src/net/sf/briar/api/protocol/Ack.java index 4608fee530..8a39f38a98 100644 --- a/briar-api/src/net/sf/briar/api/protocol/Ack.java +++ b/briar-api/src/net/sf/briar/api/protocol/Ack.java @@ -2,9 +2,17 @@ package net.sf.briar.api.protocol; import java.util.Collection; -/** A packet acknowledging receipt of one or more messages. */ -public interface Ack { +/** A packet acknowledging receipt of one or more {@link Message}s. */ +public class Ack { - /** Returns the IDs of the acknowledged messages. */ - Collection<MessageId> getMessageIds(); + private final Collection<MessageId> acked; + + public Ack(Collection<MessageId> acked) { + this.acked = acked; + } + + /** Returns the identifiers of the acknowledged messages. */ + public Collection<MessageId> getMessageIds() { + return acked; + } } diff --git a/briar-api/src/net/sf/briar/api/protocol/Author.java b/briar-api/src/net/sf/briar/api/protocol/Author.java index 52266b9621..1532155c32 100644 --- a/briar-api/src/net/sf/briar/api/protocol/Author.java +++ b/briar-api/src/net/sf/briar/api/protocol/Author.java @@ -1,17 +1,43 @@ package net.sf.briar.api.protocol; -/** A pseudonymous author of messages. */ -public interface Author { +/** A pseudonymous author of {@link Message}s. */ +public class Author { + + private final AuthorId id; + private final String name; + private final byte[] publicKey; + + public Author(AuthorId id, String name, byte[] publicKey) { + this.id = id; + this.name = name; + this.publicKey = publicKey; + } /** Returns the author's unique identifier. */ - AuthorId getId(); + public AuthorId getId() { + return id; + } /** Returns the author's name. */ - String getName(); + public String getName() { + return name; + } /** * Returns the public key that is used to verify messages signed by the * author. */ - byte[] getPublicKey(); + public byte[] getPublicKey() { + return publicKey; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Author && id.equals(((Author) o).id); + } } diff --git a/briar-api/src/net/sf/briar/api/protocol/AuthorFactory.java b/briar-api/src/net/sf/briar/api/protocol/AuthorFactory.java index 24b211197d..59c21df227 100644 --- a/briar-api/src/net/sf/briar/api/protocol/AuthorFactory.java +++ b/briar-api/src/net/sf/briar/api/protocol/AuthorFactory.java @@ -5,6 +5,4 @@ import java.io.IOException; public interface AuthorFactory { Author createAuthor(String name, byte[] publicKey) throws IOException; - - Author createAuthor(AuthorId id, String name, byte[] publicKey); } diff --git a/briar-api/src/net/sf/briar/api/protocol/AuthorId.java b/briar-api/src/net/sf/briar/api/protocol/AuthorId.java index 578c89c2ac..3d572d5ada 100644 --- a/briar-api/src/net/sf/briar/api/protocol/AuthorId.java +++ b/briar-api/src/net/sf/briar/api/protocol/AuthorId.java @@ -2,7 +2,10 @@ package net.sf.briar.api.protocol; import java.util.Arrays; -/** Type-safe wrapper for a byte array that uniquely identifies an author. */ +/** + * Type-safe wrapper for a byte array that uniquely identifies an + * {@link Author}. + */ public class AuthorId extends UniqueId { public AuthorId(byte[] id) { diff --git a/briar-api/src/net/sf/briar/api/protocol/GroupId.java b/briar-api/src/net/sf/briar/api/protocol/GroupId.java index 5329418679..b530f8c2f5 100644 --- a/briar-api/src/net/sf/briar/api/protocol/GroupId.java +++ b/briar-api/src/net/sf/briar/api/protocol/GroupId.java @@ -3,8 +3,7 @@ package net.sf.briar.api.protocol; import java.util.Arrays; /** - * Type-safe wrapper for a byte array that uniquely identifies a group to which - * users may subscribe. + * Type-safe wrapper for a byte array that uniquely identifies a {@link Group}. */ public class GroupId extends UniqueId { diff --git a/briar-api/src/net/sf/briar/api/protocol/Message.java b/briar-api/src/net/sf/briar/api/protocol/Message.java index 23b0e745ad..c5dca006ef 100644 --- a/briar-api/src/net/sf/briar/api/protocol/Message.java +++ b/briar-api/src/net/sf/briar/api/protocol/Message.java @@ -6,21 +6,27 @@ public interface Message { MessageId getId(); /** - * Returns the message's parent, or null if this is the first message in a - * thread. + * Returns the identifier of the message's parent, or null if this is the + * first message in a thread. */ MessageId getParent(); - /** Returns the group to which the message belongs. */ + /** + * Returns the identifier of the {@link Group} to which the message + * belongs, or null if this is a private message. + */ GroupId getGroup(); - /** Returns the message's author. */ + /** + * Returns the identifier of the message's {@link Author}, or null if this + * is an anonymous message. + */ AuthorId getAuthor(); /** Returns the message's subject line. */ String getSubject(); - /** Returns the timestamp created by the message's author. */ + /** Returns the timestamp created by the message's {@link Author}. */ long getTimestamp(); /** Returns the serialised message. */ diff --git a/briar-api/src/net/sf/briar/api/protocol/MessageId.java b/briar-api/src/net/sf/briar/api/protocol/MessageId.java index e540f769ea..88cdb1c312 100644 --- a/briar-api/src/net/sf/briar/api/protocol/MessageId.java +++ b/briar-api/src/net/sf/briar/api/protocol/MessageId.java @@ -2,7 +2,10 @@ package net.sf.briar.api.protocol; import java.util.Arrays; -/** Type-safe wrapper for a byte array that uniquely identifies a message. */ +/** + * Type-safe wrapper for a byte array that uniquely identifies a + * {@link Message}. + */ public class MessageId extends UniqueId { public MessageId(byte[] id) { diff --git a/briar-api/src/net/sf/briar/api/protocol/MessageVerifier.java b/briar-api/src/net/sf/briar/api/protocol/MessageVerifier.java index 59313414fb..f99b348754 100644 --- a/briar-api/src/net/sf/briar/api/protocol/MessageVerifier.java +++ b/briar-api/src/net/sf/briar/api/protocol/MessageVerifier.java @@ -2,6 +2,7 @@ package net.sf.briar.api.protocol; import java.security.GeneralSecurityException; +/** Verifies the signatures on an {@link UnverifiedMessage}. */ public interface MessageVerifier { Message verifyMessage(UnverifiedMessage m) throws GeneralSecurityException; diff --git a/briar-api/src/net/sf/briar/api/protocol/Offer.java b/briar-api/src/net/sf/briar/api/protocol/Offer.java index 0d12875ca5..d3be3b55e1 100644 --- a/briar-api/src/net/sf/briar/api/protocol/Offer.java +++ b/briar-api/src/net/sf/briar/api/protocol/Offer.java @@ -2,9 +2,17 @@ package net.sf.briar.api.protocol; import java.util.Collection; -/** A packet offering the recipient some messages. */ -public interface Offer { +/** A packet offering the recipient one or more {@link Messages}. */ +public class Offer { - /** Returns the message IDs contained in the offer. */ - Collection<MessageId> getMessageIds(); + private final Collection<MessageId> offered; + + public Offer(Collection<MessageId> offered) { + this.offered = offered; + } + + /** Returns the identifiers of the offered messages. */ + public Collection<MessageId> getMessageIds() { + return offered; + } } diff --git a/briar-api/src/net/sf/briar/api/protocol/PacketFactory.java b/briar-api/src/net/sf/briar/api/protocol/PacketFactory.java deleted file mode 100644 index 9c3c51116d..0000000000 --- a/briar-api/src/net/sf/briar/api/protocol/PacketFactory.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.sf.briar.api.protocol; - -import java.util.BitSet; -import java.util.Collection; -import java.util.Map; - -public interface PacketFactory { - - Ack createAck(Collection<MessageId> acked); - - Offer createOffer(Collection<MessageId> offered); - - Request createRequest(BitSet requested, int length); - - SubscriptionUpdate createSubscriptionUpdate(Map<GroupId, GroupId> holes, - Map<Group, Long> subs, long expiry, long timestamp); - - TransportUpdate createTransportUpdate(Collection<Transport> transports, - long timestamp); -} diff --git a/briar-api/src/net/sf/briar/api/protocol/ProtocolConstants.java b/briar-api/src/net/sf/briar/api/protocol/ProtocolConstants.java index a2aa0d79a0..5dffe995c7 100644 --- a/briar-api/src/net/sf/briar/api/protocol/ProtocolConstants.java +++ b/briar-api/src/net/sf/briar/api/protocol/ProtocolConstants.java @@ -11,9 +11,6 @@ public interface ProtocolConstants { */ int MAX_PACKET_LENGTH = MIN_CONNECTION_LENGTH / 2; - /** The maximum number of transports a node may support. */ - int MAX_TRANSPORTS = 25; - /** The maximum number of properties per transport. */ int MAX_PROPERTIES_PER_TRANSPORT = 100; diff --git a/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java b/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java index 24f4c8aac8..4712c83457 100644 --- a/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java +++ b/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java @@ -18,9 +18,15 @@ public interface ProtocolReader { boolean hasRequest() throws IOException; Request readRequest() throws IOException; + boolean hasSubscriptionAck() throws IOException; + SubscriptionAck readSubscriptionAck() throws IOException; + boolean hasSubscriptionUpdate() throws IOException; SubscriptionUpdate readSubscriptionUpdate() throws IOException; + boolean hasTransportAck() throws IOException; + TransportAck readTransportAck() throws IOException; + boolean hasTransportUpdate() throws IOException; TransportUpdate readTransportUpdate() throws IOException; } diff --git a/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java b/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java index 4ddd4c4fb9..4637fdffc5 100644 --- a/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java +++ b/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java @@ -16,8 +16,12 @@ public interface ProtocolWriter { void writeRequest(Request r) throws IOException; + void writeSubscriptionAck(SubscriptionAck a) throws IOException; + void writeSubscriptionUpdate(SubscriptionUpdate s) throws IOException; + void writeTransportAck(TransportAck a) throws IOException; + void writeTransportUpdate(TransportUpdate t) throws IOException; void flush() throws IOException; diff --git a/briar-api/src/net/sf/briar/api/protocol/Request.java b/briar-api/src/net/sf/briar/api/protocol/Request.java index 242e59bbd2..7e5d86fdd5 100644 --- a/briar-api/src/net/sf/briar/api/protocol/Request.java +++ b/briar-api/src/net/sf/briar/api/protocol/Request.java @@ -2,15 +2,30 @@ package net.sf.briar.api.protocol; import java.util.BitSet; -/** A packet requesting some or all of the messages from an offer. */ -public interface Request { +/** + * A packet requesting some or all of the {@link Message}s from an + * {@link Offer}. + */ +public class Request { + + private final BitSet requested; + private final int length; + + public Request(BitSet requested, int length) { + this.requested = requested; + this.length = length; + } /** * Returns a sequence of bits corresponding to the sequence of messages in * the offer, where the i^th bit is set if the i^th message should be sent. */ - BitSet getBitmap(); + public BitSet getBitmap() { + return requested; + } /** Returns the length of the bitmap in bits. */ - int getLength(); + public int getLength() { + return length; + } } diff --git a/briar-api/src/net/sf/briar/api/protocol/SubscriptionAck.java b/briar-api/src/net/sf/briar/api/protocol/SubscriptionAck.java new file mode 100644 index 0000000000..ba2c41a7cf --- /dev/null +++ b/briar-api/src/net/sf/briar/api/protocol/SubscriptionAck.java @@ -0,0 +1,16 @@ +package net.sf.briar.api.protocol; + +/** A packet acknowledging a {@link SubscriptionUpdate}. */ +public class SubscriptionAck { + + final long version; + + public SubscriptionAck(long version) { + this.version = version; + } + + /** Returns the version number of the acknowledged update. */ + public long getVersionNumber() { + return version; + } +} diff --git a/briar-api/src/net/sf/briar/api/protocol/SubscriptionUpdate.java b/briar-api/src/net/sf/briar/api/protocol/SubscriptionUpdate.java index 30e0e56397..8b7c1e8733 100644 --- a/briar-api/src/net/sf/briar/api/protocol/SubscriptionUpdate.java +++ b/briar-api/src/net/sf/briar/api/protocol/SubscriptionUpdate.java @@ -1,25 +1,28 @@ package net.sf.briar.api.protocol; -import java.util.Map; +import java.util.Collection; /** A packet updating the sender's subscriptions. */ -public interface SubscriptionUpdate { +public class SubscriptionUpdate { - /** Returns the holes contained in the update. */ - Map<GroupId, GroupId> getHoles(); + private final Collection<Group> subs; + private final long version; - /** Returns the subscriptions contained in the update. */ - Map<Group, Long> getSubscriptions(); + public SubscriptionUpdate(Collection<Group> subs, long version) { + this.subs = subs; + this.version = version; + } /** - * Returns the expiry time of the contact's database. Messages that are - * older than the expiry time must not be sent to the contact. + * Returns the groups to which the sender subscribes, and which the sender + * has made visible to the recipient. */ - long getExpiryTime(); + public Collection<Group> getGroups() { + return subs; + } - /** - * Returns the update's timestamp. Updates that are older than the newest - * update received from the same contact must be ignored. - */ - long getTimestamp(); + /** Returns the update's version number. */ + public long getVersionNumber() { + return version; + } } diff --git a/briar-api/src/net/sf/briar/api/protocol/Transport.java b/briar-api/src/net/sf/briar/api/protocol/Transport.java deleted file mode 100644 index a8ea4a3151..0000000000 --- a/briar-api/src/net/sf/briar/api/protocol/Transport.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.sf.briar.api.protocol; - -import java.util.Map; -import java.util.TreeMap; - -public class Transport { - - private final TransportId id; - private final TreeMap<String, String> properties; - - public Transport(TransportId id, Map<String, String> p) { - this.id = id; - properties = new TreeMap<String, String>(p); - } - - public Transport(TransportId id) { - this.id = id; - properties = new TreeMap<String, String>(); - } - - public TransportId getId() { - return id; - } - - public Map<String, String> getProperties() { - return properties; - } - - @Override - public int hashCode() { - return id.hashCode() ^ properties.hashCode(); - } - - @Override - public boolean equals(Object o) { - if(o instanceof Transport) { - Transport t = (Transport) o; - return id.equals(t.id) && properties.equals(t.properties); - } - return false; - } -} diff --git a/briar-api/src/net/sf/briar/api/protocol/TransportAck.java b/briar-api/src/net/sf/briar/api/protocol/TransportAck.java new file mode 100644 index 0000000000..11a9dfdc7f --- /dev/null +++ b/briar-api/src/net/sf/briar/api/protocol/TransportAck.java @@ -0,0 +1,23 @@ +package net.sf.briar.api.protocol; + +/** A packet acknowledging a {@link TransportUpdate}. */ +public class TransportAck { + + private final TransportId id; + private final long version; + + public TransportAck(TransportId id, long version) { + this.id = id; + this.version = version; + } + + /** Returns the identifier of the updated transport. */ + public TransportId getId() { + return id; + } + + /** Returns the version number of the acknowledged update. */ + public long getVersionNumber() { + return version; + } +} diff --git a/briar-api/src/net/sf/briar/api/protocol/TransportUpdate.java b/briar-api/src/net/sf/briar/api/protocol/TransportUpdate.java index 48615ccd7e..b36c32eaa9 100644 --- a/briar-api/src/net/sf/briar/api/protocol/TransportUpdate.java +++ b/briar-api/src/net/sf/briar/api/protocol/TransportUpdate.java @@ -1,16 +1,33 @@ package net.sf.briar.api.protocol; -import java.util.Collection; +import net.sf.briar.api.TransportProperties; /** A packet updating the sender's transport properties. */ -public interface TransportUpdate { +public class TransportUpdate { - /** Returns the transports contained in the update. */ - Collection<Transport> getTransports(); + private final TransportId id; + private final TransportProperties properties; + private final long version; - /** - * Returns the update's timestamp. Updates that are older than the newest - * update received from the same contact must be ignored. - */ - long getTimestamp(); + public TransportUpdate(TransportId id, TransportProperties properties, + long version) { + this.id = id; + this.properties = properties; + this.version = version; + } + + /** Returns the identifier of the updated transport. */ + public TransportId getId() { + return id; + } + + /** Returns the transport's updated properties. */ + public TransportProperties getProperties() { + return properties; + } + + /** Returns the update's version number. */ + public long getVersionNumber() { + return version; + } } diff --git a/briar-api/src/net/sf/briar/api/protocol/Types.java b/briar-api/src/net/sf/briar/api/protocol/Types.java index 71ec94a397..248d6731b6 100644 --- a/briar-api/src/net/sf/briar/api/protocol/Types.java +++ b/briar-api/src/net/sf/briar/api/protocol/Types.java @@ -9,7 +9,8 @@ public interface Types { int MESSAGE = 4; int OFFER = 5; int REQUEST = 6; - int SUBSCRIPTION_UPDATE = 7; - int TRANSPORT = 8; - int TRANSPORT_UPDATE = 9; + int SUBSCRIPTION_ACK = 7; + int SUBSCRIPTION_UPDATE = 8; + int TRANSPORT_ACK = 9; + int TRANSPORT_UPDATE = 10; } diff --git a/briar-api/src/net/sf/briar/api/protocol/UnverifiedMessage.java b/briar-api/src/net/sf/briar/api/protocol/UnverifiedMessage.java index 8fd391d2f8..89e30e7438 100644 --- a/briar-api/src/net/sf/briar/api/protocol/UnverifiedMessage.java +++ b/briar-api/src/net/sf/briar/api/protocol/UnverifiedMessage.java @@ -1,28 +1,61 @@ package net.sf.briar.api.protocol; +/** A {@link Message} that has not yet had its signatures verified. */ public interface UnverifiedMessage { + /** + * Returns the identifier of the message's parent, or null if this is the + * first message in a thread. + */ MessageId getParent(); + /** + * Returns the {@link Group} to which the message belongs, or null if this + * is a private message. + */ Group getGroup(); + /** + * Returns the message's {@link Author}, or null if this is an anonymous + * message. + */ Author getAuthor(); + /** Returns the message's subject line. */ String getSubject(); + /** Returns the timestamp created by the message's {@link Author}. */ long getTimestamp(); + /** Returns the serialised message. */ byte[] getSerialised(); + /** + * Returns the author's signature, or null if this is an anonymous message. + */ byte[] getAuthorSignature(); + /** + * Returns the group's signature, or null if this is a private message or + * a message belonging to an unrestricted group. + */ byte[] getGroupSignature(); + /** Returns the offset of the message body within the serialised message. */ int getBodyStart(); + /** Returns the length of the message body in bytes. */ int getBodyLength(); + /** + * Returns the length in bytes of the data covered by the author's + * signature. + */ int getLengthSignedByAuthor(); + /** + * Returns the length in bytes of the data covered by the group's + * signature. + */ int getLengthSignedByGroup(); } \ No newline at end of file diff --git a/briar-api/src/net/sf/briar/api/serial/Reader.java b/briar-api/src/net/sf/briar/api/serial/Reader.java index e0610feb83..17146a950d 100644 --- a/briar-api/src/net/sf/briar/api/serial/Reader.java +++ b/briar-api/src/net/sf/briar/api/serial/Reader.java @@ -18,7 +18,7 @@ public interface Reader { void addConsumer(Consumer c); void removeConsumer(Consumer c); - void addStructReader(int id, StructReader<?> o); + void addStructReader(int id, StructReader<?> r); void removeStructReader(int id); boolean hasBoolean() throws IOException; diff --git a/briar-core/libs/source/silvertunnel.org-netlib-0.14-briar-source.jar b/briar-core/libs/source/silvertunnel.org-netlib-0.14-briar-source.jar index 5bce0d4a2ef4d4c9b686b182f0102d904ff38f00..d9bd9fc00289586406f4630f739f0b2a261d29a6 100644 GIT binary patch delta 13821 zcmZ8n2|QI>7kBSD_Z%~kd7g=)P!f_NbD|;?ib_f}Dnex_C33OTT(7xQy47hC(V)^i zYE)^`pb-u7t$hyH?R)R{d(Zv<*IIk+wb$O~?6uC0gZg_8>MI2K5oB*EY54c{fdOk2 zoC)|}g|7k+{l3Ci7VdX7ML(|a)rA{_hN2(~N*87Jr`1tqKU!Y&ahx-OKHldmqm_FJ zmXJZ2wzQFGd2AFZlFNJ`EwTvLkrgJ<94YFT`h?^Z4yJIHSOroT9WE~#S~7z`ZCTU+ zb_9`C{4D|M5i_)>)kGny7-@;I-21^;k3#P7kwSKtIg#(YYQ9KubS@udws4q%NHwLE z-(yN>IExbLulUVJ@&-ITq4EkvblQ&A6AhnLFA(MisUrh*o*rtnrxir+*U|#yW=QK6 zZKYA>AX*?&bW5j&H#|(ZU)q~4bB2KzUH>nED9NLGWCQ&M3y}9CN*$f~LFpdex}Psv z<+)BET;*vZy#608irq!?(VP2xb(E$f9zsFnQ60w@<_Fmc8}|rAJNL+u=t?oRO0q!I zQIRZ~IE<$!x>i1pMxJl^!6>nw8i8)OQ^qLs5v7EfBDxo|;6C++K*|$H7Sv?EzMy#W z+-UAugfRn>PBc1nu3IUfh*o0CZc-Zrc$HZt73Ot4HGxFT1}XySZo!sLQ5tA@0j<Hr zGt>_PJH~=4jMqj=iDV|8qS~anLF^_GEG5H4@1&MXce!_pl0ooW9%&cQa*Vx*dQJ2& zOy*YVD2Z1^7bWEq6dsNfXABNfrwP0`5IkK}=tHTZ`7V?;D|;+}iX+%T8HmAQJ~P*c zN+Os-7s{PP;E^%<n@#I7FTJQWe6(RF7zcf0rg}JaozJd!^cb2%NzQ};S|cFzgsY8o zOUKbh45d!o=j^hn1eh{9IF_cEMgbv1;gmB*Hw1(-W3NJ#5_nCpo*JVqPu%1Z>7+ai zmKjVdpz61LZKnJ$e;>s<ipRi&2J^0!f39m8uK(rJO!Qa&IFefd{2Re<nvs3S*O2K> zP2JKkS{~_W6WZMBRgHs4zOo~dWw^kzOA26brqP1fM{?nX--gTrZNgpwL0pUx<`M7$ zbRd+LXKvXLH`wr^h6rG{tb(*x%7pmB{$qb?G8fiTUkK!Vic)8{GlWnY9a>AtqKuWa zEMvcdx<@k*l1~KZJT}4Dt)bQvDC0Jt4LK`Mqw+jji<y~8Z6O&*pM3;QFDI0Ljxs@P z&XgB|>x!2s->x-<`KXZW<iWRL40z;2ur9DE@_7X<!+1tgEd=_xf>vbur%_u8#%nSa zuf}C3E8q}Ksc`dyy{(IGjG^S2|AMH2a;SUl&6(Y6iFGn)W)*3UMookbbal6&T!7|B zL!h3?)0*fI{Kd@cLn;YS;3-O(lMGr-sxg}{5*A(OAiE)yXOZ%Z!!<&QK<)mdvM`}e z7eyZ@R8aI}QovZ6kz)nSBpEW4U`jp6aU@eQo*YFm#*;~JY39OZ!j4sQV-EEqs};G^ zlFee)ta~b%LEu1HpwF=|--8LHEjKokT1~v8xP-@|sP7a=MtKEt3duPPXF)%-IuWw5 zY9=gM=4`S^8o^m$j<zI_O6YGafJHY6BNYa`&X!?To+3VzTn4cAqT?V_I$jcSW}H)O zqOlAZRbe{QNoyr8C0S2fhmu?POp+lvk7TT($)9{qi{0Is7YPF<ZZ(-mG7H9$&IFUX zm*|vX1sHHsi(sB6k^uzcaGZcB$@r2jd}h%R;v~T&>>`v2=7Sfh#|0O`rZPsX7h7aV z8Rm*D*`dVlMSO%Pp!$ilG`b!J$@s#VaFl~{RT&?buya!gX{I=Z=p-13L2rWdoJA|s zh-EapMSDz4jE2c)Y$G<2+%97`Ms*mRnGyK}CEX)!7e$lIr)`4W0`7zW8CfR3gfJq= z4F?8zpd=mI61yggl!T-y^W-+UhrqdHhgSb0DQ3?%atFnH9W7{+N1;`u7y2_B&aQ9u zWHW`f<Os}B#%$Vz*-}Tg5xuZIDT#kVkWmz2h9%@mUz64uPAZ*4H!uCnlRJ^zWV*7l z_Ep8H#1{@{0zOpF51*@)yL8R8h={T~qsK(H-DpX-zq4}D!}zw=Ptr%$n9X+DQ1<Nf z_P_gxuX+B*E!s=MY&1<q##RfS9q{a3WL+_F<l{NJm%bTMzI}PA^?=R(m)7pdFEmL0 zQX+lqR>Xnc6$ef>eQ(cb&oyapv5ozenC?g28Q(up|3q7T?Atvv3s$vz)HxRZ@%Eav zM(XeSgQpA0U>maus>dU}qs@BD9o<^AQCPlw`S8JO>VJ5cRqpcc|H8`S#3KzAnf=-C z`yNZNebG-?ZDW#mX`c5vkIoePbkkXyQ;U8z87=zin)`U6;!C62SL=p2WxTZ7T1~qt zhSr;nil}U*PL!EFF4W;gOk49H;KQ59ljmNpyWmS!-^}aNl>G9i+rmkvvJJ*xQ@#1- z!Ug~R{MeKnNWE-pdJ^rdx9UyH^_auqGAWlgjrYGCL7$<Em+1JMcv#bGO-)DAqIq(D z)WMOUlWJ^wLhI_q+J;q+GcQvaO)+*G3l8YFP$6GGJ1t&YpEJ<NK4(U}k;BU6nU~$q zC8Z#%=ASY1zqV~DQ-1Jm!r^V#<sF*rZXETqOs=+T6}tTpL^aQ;X^Jd*b1p);-<X0K z+iBU*ZF?0WH(8VAS1MAQa#h!Q{v2tLH8jM|VA|spxh)wl<&09Bj!LTqRv9Q~%uZW0 zdv@B~uN_}}3S6#H?iCqHH||$UyBgRG_uOFrOnvr>`7NoVq>8F;PW5dJ4nE;|K{NYA zgoj>kw$TGK59#`ipEWIGWUN+uU5?^eS?nsA5dCS;WMi5vTGZcMJ0|na^unY-&1p)l zbxVprtWo~3b#QoeNA)pN<7&?d<*8@PGTg@g3_kU|-S%9ujHPMYZ2!Abz7dZC{mlv= zF4^@ULCxsLDZ@TFszuX>jgfYlxW8ET+OLkQQzqLk%-VUsZL)Su=g1+Hhx^8YxtAJ0 z`<;s39Xb4L>$_5)2$}d(`iEpS9(`LzDDNuF`$Jh)xW*Ki*N#%W?K{-gWo7Z3N1-)s zF>x6kwF!sv*F9R@7-(wt^72mK{jL}F-wri7@Sy0@I<+%l{anu+X>os({%B77O7fOg z0KIF;)MZ1C*M8gFGI*i+h64YOjuGD+7VvE|=Mncy8bjyzt1nq%n%L;%Pu+TXim4BE zR{qY*o?~>7j7qtpVRP3-=>%~gw<<+%MwnyK&ZE8=($6*-O>5|@`9&>yLBa6m#Yuln zA~SQ&UP-81a+&AWcs{hzWwCTWe>Xz0&QVV9ouGQ#{4(q7^UhAl`f}Ta`nxLH!zNp1 zZ8T5n)1X7~`BtG$i&6H|A_IX#^kJpz1ut}3eakzBX4elULgqKTnd*H?)^y2>TOm5F zW6bkrFS8h$ar25r6pWD8{N>x-+pj(*B`rR4{G4lT=HSKKQ?o*k{n|cYbv*fZl*isT zzOxdZo|wGZ<y%`GWl+{i4*L+RyV)ai{glV%hcA1U$hN?tMuk1IH(Z+~`yy+&x;~wu zHhh)Ti@Lb4wG;Wp-%F0JKUQ8=Z8rJjlyQvQ$^8v8l+0Z-6z2?%jP%(4^g~6>(#j<3 z4uiF$rYkg_CnD=N_LaTTWbWuxK5B7t*^^rPRVw<IxBrL=I`0%dbjcBNQDQo|TB%-O zGd`D+4p+>YennI^VR_-!Fp&ZoTN>=+VeYgnE_BzN-eG}CQ!PZlt~wJI9y@El{P=vI z5ngP_{qnNo{qye`wN0;UQwID^a@4w>6I$@?=A-Ai?>;|Ioa?yutktM|!tAxEYPxKZ z&wiQQ*gi!rw<AxVN8y*A$TWKlxOYq^cI>2bGhyuXJ(k|T+TLVNqP}WnJU@M1YSQ=q z$(lPWrc$d8@-nlQxz?PWW2fVB%|CALgl*-)Q4uFMd){gO<M6eyBjbbix1hqjz(?-4 zik?UM7nCl|Uf4nHH_go+p?=FuL*tjBwXkLHc4@0{pZzxmHW*(meKW}L*Zc3w)tlZ$ zuWy=D5WCev!@nR_b<Upe^}?wt@AS{DE1IOLV)Dh#-?E`k>l>K}r9#aR#nG`%XLcI> z{@eRb`%jk$m1R=GUhdD$lQlBh?LB9@S#AAmr&n|2+RE4)t|l8-MS7`k9a%Ezy?eI% z0M!D8phA7Q=NdGBu)+G0H}=*OzP{{Vy~{o5Ti#En;J^)auf8l?wD61ZhN)K+l>*l8 zd>Oy*!L!bo>gc8#t#H$#ljqYeAFa4IJv7`lZfEZFX5)=D(}#~Yn{205FK>7?F2k_> zLYvK;hw0}M8+I9+9q%<tN3JeS@XYhJ_e<F<dh1WqW8N>XNf-U+d`s<Dhui1@2R>I1 zb2n3OU0D{m+G#Ouer{s+(nGaUFV?g*UF^%>9+Nn}!FtpW+ntMSo4#J08|5_OnA+aN zGfVTnjr&#<6RSSwv!d_%KZmXiYJ8trqpRYSv2V1O<KerOb%TwHHteXsqh-H3IB>P@ zF4Wk5^nBcc<_`)ZV-%XpN8Gt=VR$y8SD#fjs)J>x`TjgK=iXqOYpIz{p;L#?OZ7Mw zmzri|ljNVPcc*=+)zs|Q>)zNc49m*c81DSeDXTEW+c|kneBGaQF7dm+hkm#n_vR24 zmb=j{X<|l}_wR9flitk?j=DS8e{tY0ospjvKO28(I+_)%I;v*FM5C1HEvM=$OJkRQ zCwb0GR;5)<{C<2vW&h`P-scoM%EoTk7Dpc66ysK@{bk_WPPt^ux9h{E_{Ro}TTS$x zGTbb8&IK>k<UDV>zjJckSwAED6DP;q3_BjzYkG&~IHjD)8PEIXOi5K*@K#0Sa{S3h zhtCDXv9bCEVYM>vJ9HH7GRM?Ujs5EPQFqob8-*fexn3@NcBBeguTFFM@JXfjxpZ~E z_$3o6v@0Xk^WDz#eQbuTN{f3p;K06*LtY>ADT|7kI<qYNbjPPYb-q8FwPb=;))d9M znr|KxUn1I`6F*;Senf`(AHVPR+RywKjku!UC-%mpb?%#Vja~MYq<v^O($-wn+^cnO zXMXFw^fzxU18VaezLxzQEBExszQpghQ?y_9c5b`-tN)h7#R^Al=~qS5bNXIt&Fizr zL8$WI$U_4IP>9C*0TY*(Fhw=_bNjjTc|+$FWS5?_33&BjP_h4<HKz4*GDg?+2~uy0 z*t=)$6^(+^vT`q1&x%kR=N#7Gpww7eBy(4L_n*2EkrR^JY+N)Bj9qzB!6-acBk{cN zh|qv}J0e%^T-0<dEy=+q$bYP1fLhGKr1aV+?ng2Y^{%pQ*!AL|t<-{2bSlp<_g7$6 zi&y&jKK&EFcGesl_an>s=ao$htj#jsh2AOuolqHcq=*Q~^SPzw{x^KDLd~t$*`F4F zGM_T~k4N9vr|d2{4;=NG9wYj3({W1GRfC*Q`pHN8la<?_e*EaO<3jABfRGmxqu+1n zb@D6yu_i&d<d?ep+fN}IHT=J|?YXJr^Y`G`=CL~!<f?8C>UVqdUA6pq>3#KoJSyne zo1SrL{=JT--H8iA8&ktSmua3z+x)XL<0G%))X+jFQnX7dT;+$=_5&TyHSZjK_;rH4 z?v>@twew$6zgyn=^6c%{?f*R#Wggmo=49NO)wL5UekwI5#Qt$;9prF%U%=|QM}lo% zRL+~O?sj{U;N{dsi*G(iy0-HUlYZsDN*70Zz~hHRe8yGztF7K)Z~WhT4Ssd#YzqB& zac%Lu!xl2iPMHS#1Url>?{7Qq@w9T-IJ1`V#_K0nd0orcJ$3Tx)<q|i9^cctI_mxA zw2G~T!!tsj96!Egt44CL#k)zWFW`ktKmXZxX=$`r*1ag`W<~LcUAZIXGg9?U3y1%> zzpA|^Y+*vqX6xq}2@#%YUPkGYJ8ykXZ(pOdCFj*Rv`hZm&5oq6PMuN>pJLPUXH6Lu zSa9Xt(7@9M`T4pFlRD-*Z7i5l`}=vyfzZ3AkBx{OCm+U`B8^uc&-^}+*U!#qU+T?c zQC<g|BZHsch`p>IzeqRs<Bh2{hXPj@6kNGnck;iW3Eo-)!%N1uyfZr0rr1SVKC#cZ zYuo3>oycJ$8*biLj!C~9)!cXaSX;H(H}~fHjxU;*>c9S|^Qowc&1Yl0HD^^W|Ks}? z>4ZL)H-Av2+ti67Ja2C=E5AD?|DNWJX?Nebr1n<2b*s&1vis3*lVe@7wr4L2XYMcf z@$FSf(xw3Kb!)x12M&x0_WSTD<5cgV2kV|1I{f(aKHRwS<l}ywcN6-Roc!2wZeIGC z=g9^`BQ~`4w%+YKP}Z`kFWSD$!1TwrYj@+LMi$6w?9fko*k^Q~wXY8jyKFFDt*Ypz zrB#(uf6q*tlE#g@Z#=%(7+V!;G9Fdk-ao)(wDm)iDaCTlKkHph?iz-_7?|U*uV#P7 zt$e}4`?Yc}ehyl)&+P2O>{A!M3~<j$w60iseEO0VAIFqZxwB^EniuB(-g@-XyOqDu zkfg^?M>miA7*};~%GRGt+{=>AjgK67|Ao^}`O_iSG`<h|?K5e>i?%SmzgjPvzbxEJ zEZ1KCTcN1Ke$v*zZW~klR!6zLAEkKf{_KXmABK$g{jkp>6s6|foj-TS<~4^t@0d|= zu2aKwhI_w`e%HO+%4ggTubPx6w@>ba^Gr1zZ=ZR&^~&DA6ti{H2Dc9i@}s2}6oxoj zC`d_7vq#VG^K}_0fz|fn${F1kI9aqK8Oj1bPLL@Aap8)++Q~A<?vs<G8MxXtvo%X| zeBqNtu1T~yLvJM22u8D!WCUD2Ci54mh|1?fE#s)Jz(k%&_7g0jxC=8><GNIVS7gks z1b)USpja@Fv$wH|)L<3~$w4wq;4iYDG4rf~JWZosUPOSfK1i8?Qo&w=ndn78rE-Q2 zt;|Hk6SfrdFpfA#puZ7Nf_gTKm?zB``w;D#xXc12%t1_^HML8btEs{G&Rs=YTr4O# zMA7JbCS3UAWN`_QE&t-0QmKII&14!<Hz-`8W#x|kr5XwJ$BQ&)3Kc21KtClZCjkmq zgz`zZ3>vx=>OoJmsZg$F);Up-#w0Ei7!%B#1i@tu;m}&PdLoCaQlNC9wg{y677H{K z8MqEtD&R}88?!u0@LrB{1r~`d;Phd(=MqT*H1~kO5BbFt%IM)+s1y`>5hOZvKp;Rz z7SpQCyw`%yGN=O=ox2$deM*OlT4SqVAjQOe7L1^{8b~(}<(~|=_D6RJdK1j~dch<u zrs{xz(#Nr5<m&lzX#{i7MLDCP{K7orujUG(43xpX$O*UBjDZrSp0My8jW)Z&gud(Z zG;xhi7kzdGhXMt>akOYe$9Y;Pc&N@m1#yiGQq||ZW40OduE_}Zw<|MHLMbImbot6m zrVUSD4efcrQ$|*qV4^XAwq(o?^NR@PqAp)wi+OyR_e6ootl-^}EwSMbMuC3d`j;4f zErsMaf!2g#@U(Fn6el0Q<+*Dh-88-zO7es}^v>e16dbkTSIKjdoPChwXtEc7iGVpW zjXzBl`@|OIO9N<q=DIq+UBJ9@<PT9|6`32M{0151x~yv;tgg!Zso`BA*(un0^0%n; z_<??ofRx;PnnzODF08&OQoh1dWfbdp*Z9n;oxCPltcM_tbzwj&Ml+9pUzdT|WJ<H1 z;8+5o!wgX13kk+1jsJvZ>W=bH(s*{>=z$k(0$u~JM2XonjF%Y6wCv=+lw}7?s53=& z20W6vc9w4;E8N+x4yArQbfbnR$DFL>e;{xsu<?>-R;cp|X`$T1vA6=IE8Ox?UG%A4 zfy7Ge5dnyjNU`+*pKY43zc#&dlE#OGsj#sfdJfLCy2$P69lj{~A(W3{pJFRYLd8}E zk>c08Y(a`eM6S;brExZ}Z32*y78caWi_B`h1h^`XWiX<EQU}upqMNrJDD1?433YP9 z(Y!D&8H22$d-A~P4vFjM!X$Hf)L}-`q6fU0%D82LGW*cIgo*vM5u8cvFI$nm{Tu;% z0<iT<EP(X-K%3-NzxP}z6*o1&l!|b&mbPfM(FGE!vX&^rl2#TS)oP&GQoAV0{3b2( zRB5Ep&reit*Wp<T<+y_1)Efa4BtxeX44g}FQr61Td%EhK&~h<Cnx$~aoZ!)jMuE)~ zBzp^WPfmfy?=2<nR-s2Gv=i=EA$3!_6-_jwm7%<=XGU8=y95QBLEq`ABdy5K<q0ds znV?mDp@g5@kJjd2z<>+71YIl?w2&szFk3(a1t`Xzwh$)7>Y;oapcDqs`g}r`RSp_J zTcNxC0C=Q=#q5PM!e!9r{&XMyYh702Q-9ikv*u|J*4mA*m<u{H7A#d@`v=WH&JK0h z(zg6Lmh9gX?Px9Zd;pLlYt{(#xy%p``?%eK1)#~*AHA~!;LIQl2z}?NAm{!tlMQ_V zOm@b0go>kOki|+^BftK%7Qc@x22m;ejTU3$5$>!ibiRc8E0oa5v7o*H+gvidi!ec@ z%OG5?;~-r7d;ru(FYW08{Eyt<jt+Dm^uii69tWcSj<7tq9VS$ClS8&P5P}OK9GYeW zRFnhAEgdHzr@cuYl{>(oYK}n79git*)M^7i_!D~MP(ui<hAOf8!bu$b%YoKG8?oT9 zI2I8e^z5>=5Zih*9ix`0(vg-yEiO>)W!H1~AmEH=icM>wpfNB!n++Fd;pyRS8tOF= ze5aDSeygFRfgsd45Omw-h=mLW(WWR9gV)o=VAVkIR4En4+cg?;A}j};O(zY}o6)qc zvbjW2R!XY4iz=*#Zpo@Ov?7{O1hyV@!QCx8BQ#71FmEMi2w+WABBUMpyNbGyX379X zjqJ|Y2iQn35=-lGtIEgl{!L<72|;Ji0IgdC@bc|qSOvYso+ua6=6u^dT`+F9Dda;0 z91B2tZV${h{Vb4PKENl+#nLJ$As?b}5KI555W`03X$TnkfTd+liD9;tr_OPgRs(ms z_Fs<e3hJOttk-l=f`h)G2B#N__0H8xaBMdb+Jn8&h8)P>L^H_Wt2a0S==&+4mSy0J za}MaK-;{u>Tqs4{&eK6jS-_Rwk>EPrp}$z41$s5vz(w5`b1tZJ86ejmiV^6hxWGz9 zW9hM2TIrdX(?#Xk5TD0*fTzzTVCX&eLdu?S?kE-Gj2#0R#CwGWjL@1fv?{th7F33H zAs^JB*T?Rj?PCB=c*hCK%M&teLlS)+1N%H4tL$v~m(xKq7*6~qhE<VkUr152e^NB( zC*}<i+j4XchN*lC0kum#ur{iT1UMJNc~Z#8oz`X>nCK25NDsqHq{VP=G&O`aM83fQ zODTw9fBuYK;vc=ypJ2R&x&olg9X%7&Spl}kc`_6>=Vmcri5l}E`CG<;(GbIKv0i9P zC?qm_a4f@=gV{fn3`)=@<k2(Sy&WG4{GI{-@cJk*np*;8)Q}6wb8jHa<0i2*8k+}; z5xWZ1GzLk)(D+p6Ob%KJT-cC*IkqXPjqc?EH)Xg4*LMvh-Oy}Gz-4n8Rvt6*UxBWr zo`#jx{UkUTl<l5cAs%+<*QkFvIpn(v0<k3rdX<)iFp*Qx55mg?cNGfvSLM*vLfVoq zHAVu)%~k}>RypqQNG_yxc%lFD&|ND8lWc6xL`ZPZSrs6~<q+4P72r?j|8WZFX-c<U z{pBFw9rdrW44S;0)<Z|I@}SB8ayoderO~tHurVyA{>#ZCs}-~b%EUGorTxpvp!3UV z6&wU}{?!bQ!`)u=DF<>%DN~GSp@3X^AfL(-Bl_&dzztzVzV%A6Sy|*31*!>>h;11m zhsrQ~?EheGBpn9<*|7+GYbh2R(LvBnRzo)i;vJ8l8PHbN=f7OX2|#CAAEBv$Sg+?0 z=oags4^wFk?qN1#GMF2%h2x-iYKIogfKmKo0p9+fSVaXD$AXFpw)ADE1dRL62>Q+% z$kiKmP)ZyKtk^FW0Po@;4Q@{ZxbdJER!1iBV6I_0zyptnVI9;j0S;QD831Q9Vwml2 z%c5J8Au3m^#IO!hxB|zNODv2VI0aNXYQ&rhf)=+7H<yN)FqhvqB{=AGtDy}s-H*H6 z86dFxu2?`7-2e}``23j!oNj}d(?J1~;hFa;4q!o}7*@e8aU)J;Iv(##6US|tNe@N` zaWqRlilx~BDBKoTM{+m@0x7g)6znHh9CZ{i9p1I_Cjp!%BZhU*3-APK;&G=dis3Fx zQ{zB|eL3+;1OY7vP5=fsK<g9XD4aPB;H=?dJrx8!bQOH^%cFxhc9XosoDS-Ufft@? zLntYfCqT^a`-(Yrln@R5|5kVtM~CoGHv%{gx80Fo7Pt{R9X3couvkS0ot+Mw+$jb| zsU6F~;FuaZy_Zl!8#BRDU;<dGiIPy+3<&y!^Wgm?i2<x;k?@egiDrc3<1|IAVvbNe zxLOb|M%0m?FSyzn57N8w;OR5O96Lo*bPaD6rz8p3BnBLFjtAI#ju_^?u0XaU!5lj{ zfzvr|J|}R<7M^Ysrol9_!eAO@IUL;6--k~GU~%9T@&8`ldKZZ$Wl>=YoTLenaEONe zA55akNO+alIRT8jmj0_kqUH(kPX8bhxY1?*&%wtA>i88}8F^r3$Bq9lkTU_Eq^i-t z{oNwwx;|`xuM(7zfg65);Y-Yli6GUo<6kKw<c3cp^8*qv{zw7AM+(aLnxl+9V!hv@ ze+9I;?-kzT7wIrY%(2BSd6YN{o;8+pfZHa?;cpL+@?laN<<EiLsX7k?{7y+IgMc!E zj~vv|FHcx=*9EXh18XD%*diGewPetcV1Yc^vH(2&WeK8(XM-*K>;Eqb-$&S>p_q%m zC&9ruwublviJl^!lvKz+FFhw7{wt3^nlMMs_@Wz|1ed6oxBtg;6RS&t?2AZ-0Zo5N z2(W%bG0+t4!%=DM{C^J0ftpBRE=>OtR-P@5hDCyO_k4AcEe_4MS-?g1lHj0VXpZ#e z0?hQ5fN`-9CC#Oc(N`SgMD73QJm<siyf`1?`_Np1Q(FL+B{o|og8)CGuLK7+wK*;g zyYYRL1c&R4=*cX2xMt4-n@h)Y9J`(k^WgpSNfM}RNfpCv9+;pzVX)(8FGIs7!5)b6 zfemCm9#(U1I2bw61H<KD6447sUmgt_58Rwwu?ouxP~Lbrp@v5Q=b9(Mt&E2g>a7>( zJ;OHB3MDwODL@8O;6GLYIEAqCN1<4N#syFvv~D~chLsz{aBuW`JUG4Muo$6GKoo3K z_J+LD3SJP79%bhV#ZVb!zYGq9KOBInttmCsCkIxtVJXC8&xLL|8W}Eww;eX}<(WWz zyWB%TJ+<pmHVtbHy3R#0$&fO_*MzPb#kmaNT<?k1O8!z>+=b9C6LyLE5}<t_a;i{o z)k3qDz%=$Wij66w>r0@B;edx<@LB@y>`hT9FB2X_tfh8rsp`FWAY7x>Lvnc#|I-U# z_Orimk%5Y=Ec(3wCi^@EG%6)wDB)_N#1wGA@P}C17+qXQYx5qKPJx{6uIE~zF#z)2 zbP#U}<OpkY^Fqi{Ic)T|<-f23nw--8UZjN2EeA&|V2VH5>xW;i%=xlII0VYPG>Taa zt23$xVGF;m{B0gxG9Lx%=vpdFVRt$h%l8zkvUOk$G&mh{M%G^fUX~7C%HXwd3l_s{ zRag}jr@{E!#&U3D8tuaWn;;=GaxpkMBNZ(EP3(eGX>Y#qYzZ0t!vYy(lLmono-ei| zjouxD)37iN*7@`*Schk$U>#)hv4A#`34(%>XV;H0K@f+a(coTsF-JosT8leFmj(eG zvXO(=-{!-WDhLL;xs^k-9zc25eKd@*W)RFHd=Crb`XYKXsQYrgmP6TcHQMV6$!XyR z6M8NQLls*U^(h7Jm@9B;6`XyjaNBa%tyv!Mr2Y2}I_v}|WH=Nq?GN3+t*jozhrhFi zqoG(WkA4gV`fU%=3;DYPVp}VgQA4{hvZxEG9tQhddKfsAGYlNM(gVApLqlM~n!_M6 zWmh?UkTyh#!ywFmvGmZJ67Xpoo*bI&0k-vCi$~K&>jD6o*^LCy4#>g>R7092WEAxH zid<AEFW{01bQYe>$lZ(fLXIPFKK0@dT#!e9kMfm}Sq?mn+I&G;SywErivs*$HNImb z)+Q1vP{Y^Y4umoOz*RU)aJZ<CD*Pc6W{rfQQr&yZmBd0lojo9Zyg|)3;Q!TjdV?47 ze!yvmh&g-o-WTG@M(>n2m|QxA<!~1ODfvL~N^k_u%@Av_bCN+{ec+8c1J9{5Rf40! z_?p<HGBWf8YY(y|IB?zoNn@2idH=#BiuR>V&>kGV4I3pm=p&eL&Iz%(j{i6gf~|v~ zm!Qshe|$LDRM{n=2Sbrac?8@OF5nopLRkwI2`1%;Jh8CxbqV3J;ouh=f#*1=+n`Xy z3G1K%-Z0Q>KR{<ZlF%6F4^M2(K!AN-O2Dw#I`|=`jB>nyQ++SNwLIWKwGKbvu6uw3 zQop+g(MNBG0ktI&C`X7oKQR;~XS2@gpu7M`5!SUYc(l=zSPJ)g<Po$tjJazCD-Z{_ z8h&QHgg{3fe6r2HMY94zY~k1p2?6Ma$aAU83Tzz;0+*8{1fV^lf(!>kELXZgDzF8K zqLeNH=u@EIE}(LsL*Rr_7k+A&MPo-n1oAOsvzV0y26sfX(0dnP{D%M|$n0S@y;H@7 z3pNY&VNJi}(K?`F$%!X9gZQ-N-Qw!F<zkLH2Lqb5vKv(ru6zg$MrSZ@j0e^(=wYB0 zBUJp#7Wump{RK8@A?y)xeXb@k&9*^sDlcE(O|y*}<md!P!>xZn4YbY)^1u$e0&n$r z(eNqT-Ni9M<43`yPCEg;c9$e=&J9MI!SQ_HFvs9-4%_NrpUs~+6yc5!lC_4Rhlp4? zjEv#xqd%A)P22}U`^-Sj?f5@vF#mL=7!Wo?d~(e}O7V;&Xpd%D0l21)1=%(dx?lye zzJ0*=2R*Poa>K87_pAWke@#LfdP?w)V*xpm6Ai&@yVY$TZtM60yV(*nhTiT*)rHkY za@^I{6*GbdJ<LHZ3Kd`-3Xh`R*v+s`>LN=Euur`0M&;2&3%VcQM*=uvLH9)(#jumU zePYGX=wjNAZ`;ECNP@l<iYkHmPM1WWlVyUh$nt1lDX1>}A(qxfO&IBD7b60+QHNII zb`JY0=z+l1styT(C_{M1&Mc+-5L2P|mHZpJ|7NB5a*A(wPfBQQLRy(dXMa%oxI@<U r+IYAa@E00g_{U;;1YxMp{<2mN@)J<_;$y%w;MLBNlDc6+!>|7V4+B$v delta 13530 zcmZWv2Rv2p|L>kVj&-?YXGO@Kl^F>kNyARsrHE)yiU!e?OP>azB@N-msV_<6o3=E+ z4W+%4mUjQoIY*a%|Gux+x6b?hS<mNr&U4T6`8?$Z^>!W969y0G@>F}t;D7lm%r^*K zxcFC9fRF=+s|BMerNe?k5vBWF&?W%kbAbqcZsF)a`g4H-=!;1Kcw7}|=jnDMa@45* z1ouhstRQ-TFd>hWr#4;{<nSolHo+SbnrpaSd_yit!tFBxO-S?Y(xKj75(s(X=S}L= z`!j+DK9J7^>aeB*RrJ@98kD<4aD@j3#w1TXi(>%0DMB4?{S@ekC%E&(^`SBny^%{u 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 V0{R0*pQFz?ncS<_1tSul{tweI$Cdy9 diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index c7a9a51c45..229e912b63 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -15,8 +15,11 @@ import net.sf.briar.api.protocol.Group; import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.Transport; +import net.sf.briar.api.protocol.SubscriptionAck; +import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportId; +import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.transport.ContactTransport; import net.sf.briar.api.transport.TemporarySecret; @@ -73,7 +76,7 @@ interface Database<T> { /** * Adds a new contact to the database and returns an ID for the contact. * <p> - * Locking: contact write, subscription write, transport write. + * Locking: contact write, subscription write. */ ContactId addContact(T txn) throws DbException; @@ -85,8 +88,8 @@ interface Database<T> { void addContactTransport(T txn, ContactTransport ct) throws DbException; /** - * Returns false if the given message is already in the database. Otherwise - * stores the message and returns true. + * Stores the given message, or returns false if the message is already in + * the database. * <p> * Locking: message write. */ @@ -108,8 +111,8 @@ interface Database<T> { throws DbException; /** - * Returns false if the given message is already in the database. Otherwise - * stores the message and returns true. + * Stores the given message, or returns false if the message is already in + * the database. * <p> * Locking: contact read, message write. */ @@ -132,18 +135,16 @@ interface Database<T> { void addSubscription(T txn, Group g) throws DbException; /** - * Records the given contact's subscription to the given group starting at - * the given time. + * Adds a new transport to the database. * <p> - * Locking: contact read, subscription write. + * Locking: transport write. */ - void addSubscription(T txn, ContactId c, Group g, long start) - throws DbException; + void addTransport(T txn, TransportId t) throws DbException; /** * Makes the given group visible to the given contact. * <p> - * Locking: contact read, subscription write. + * Locking: contact write, subscription write. */ void addVisibility(T txn, ContactId c, GroupId g) throws DbException; @@ -177,23 +178,13 @@ interface Database<T> { boolean containsSubscription(T txn, GroupId g) throws DbException; /** - * Returns true if the user has been subscribed to the given group since - * the given time. - * <p> - * Locking: subscription read. - */ - boolean containsSubscription(T txn, GroupId g, long time) - throws DbException; - - /** - * Returns true if the user is subscribed to the given group, the group is - * visible to the given contact, and the subscription has existed since the - * given time. + * Returns true if the user subscribes to the given group and the + * subscription is visible to the given contact. * <p> * Locking: contact read, subscription read. */ - boolean containsVisibleSubscription(T txn, GroupId g, ContactId c, - long time) throws DbException; + boolean containsVisibleSubscription(T txn, ContactId c, GroupId g) + throws DbException; /** * Returns the configuration for the given transport. @@ -249,13 +240,6 @@ interface Database<T> { TransportProperties getLocalProperties(T txn, TransportId t) throws DbException; - /** - * Returns all local transports. - * <p> - * Locking: transport read. - */ - Collection<Transport> getLocalTransports(T txn) throws DbException; - /** * Returns the message identified by the given ID, in serialised form. * <p> @@ -402,53 +386,55 @@ interface Database<T> { Collection<Group> getSubscriptions(T txn, ContactId c) throws DbException; /** - * Returns the time at which the local transports were last modified. + * Returns a subscription ack for the given contact, or null if no ack is + * due. * <p> - * Locking: transport read. + * Locking: contact read, subscription write. */ - long getTransportsModified(T txn) throws DbException; + SubscriptionAck getSubscriptionAck(T txn, ContactId c) throws DbException; /** - * Returns the time at which a transport update was last sent to the given - * contact. + * Returns a subscription update for the given contact, or null if no + * update is due. * <p> - * Locking: contact read, transport read. + * Locking: contact read, subscription write. */ - long getTransportsSent(T txn, ContactId c) throws DbException; + SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c) + throws DbException; /** - * Returns the number of unread messages in each subscribed group. + * Returns a collection of transport acks for the given contact, or null if + * no acks are due. * <p> - * Locking: message read, messageFlag read, subscription read. + * Locking: contact read, transport write. */ - Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException; + Collection<TransportAck> getTransportAcks(T txn, ContactId c) + throws DbException; /** - * Returns the contacts to which the given group is visible. + * Returns a collection of transport updates for the given contact, or + * null if no updates are due. * <p> - * Locking: contact read, subscription read. + * Locking: contact read, transport write. */ - Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException; + Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c) + throws DbException; /** - * Returns any holes covering unsubscriptions that are visible to the given - * contact, occurred strictly before the given timestamp, and have not yet - * been acknowledged. + * Returns the version number of the + /** + * Returns the number of unread messages in each subscribed group. * <p> - * Locking: contact read, subscription read. + * Locking: message read, messageFlag read, subscription read. */ - Map<GroupId, GroupId> getVisibleHoles(T txn, ContactId c, long timestamp) - throws DbException; + Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException; /** - * Returns any subscriptions that are visible to the given contact, - * occurred strictly before the given timestamp, and have not yet been - * acknowledged. + * Returns the contacts to which the given group is visible. * <p> * Locking: contact read, subscription read. */ - Map<Group, Long> getVisibleSubscriptions(T txn, ContactId c, long timestamp) - throws DbException; + Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException; /** * Returns true if any messages are sendable to the given contact. @@ -523,25 +509,22 @@ interface Database<T> { * Unsubscribes from the given group. Any messages belonging to the group * are deleted from the database. * <p> - * Locking: contact read, message write, messageFlag write, + * Locking: contact write, message write, messageFlag write, * messageStatus write, subscription write. */ void removeSubscription(T txn, GroupId g) throws DbException; /** - * Removes any subscriptions for the given contact with IDs between the - * given IDs. If both of the given IDs are null, all subscriptions are - * removed. If only the first is null, all subscriptions with IDs less than - * the second ID are removed. If onlt the second is null, all subscriptions - * with IDs greater than the first are removed. + * Removes a transport (and all associated state) from the database. + * <p> + * Locking: contact read, transport write. */ - void removeSubscriptions(T txn, ContactId c, GroupId start, GroupId end) - throws DbException; + void removeTransport(T txn, TransportId t) throws DbException; /** * Makes the given group invisible to the given contact. * <p> - * Locking: contact read, subscription write. + * Locking: contact write, subscription write. */ void removeVisibility(T txn, ContactId c, GroupId g) throws DbException; @@ -557,7 +540,7 @@ interface Database<T> { /** * Sets the given contact's database expiry time. * <p> - * Locking: contact read, subscription write. + * Locking: contact write. */ void setExpiryTime(T txn, ContactId c, long expiry) throws DbException; @@ -576,6 +559,17 @@ interface Database<T> { */ boolean setRead(T txn, MessageId m, boolean read) throws DbException; + /** + * Updates the remote transport properties for the given contact and the + * given transport, replacing any existing properties, unless an update + * with an equal or higher version number has already been received from + * the contact. + * <p> + * Locking: contact read, transport write. + */ + void setRemoteProperties(T txn, ContactId c, TransportUpdate t) + throws DbException; + /** * Sets the sendability score of the given message. * <p> @@ -612,45 +606,30 @@ interface Database<T> { throws DbException; /** - * Records the time of the latest subscription update acknowledged by the - * given contact. + * Updates the groups to which the given contact subscribes, unless an + * update with an equal or higher version number has already been received + * from the contact. * <p> * Locking: contact read, subscription write. */ - void setSubscriptionsAcked(T txn, ContactId c, long timestamp) + void setSubscriptions(T txn, ContactId c, SubscriptionUpdate s) throws DbException; /** - * Records the time of the latest subscription update received from the - * given contact. + * Records a subscription ack from the given contact for the given version + * unless the contact has already acked an equal or higher version. * <p> * Locking: contact read, subscription write. */ - void setSubscriptionsReceived(T txn, ContactId c, long timestamp) + void setSubscriptionUpdateAcked(T txn, ContactId c, long version) throws DbException; /** - * Sets the transports for the given contact, replacing any existing - * transports unless the existing transports have a newer timestamp. - * <p> - * Locking: contact read, transport write. - */ - void setTransports(T txn, ContactId c, Collection<Transport> transports, - long timestamp) throws DbException; - - /** - * Records the time at which the local transports were last modified. - * <p> - * Locking: contact read, transport write. - */ - void setTransportsModified(T txn, long timestamp) throws DbException; - - /** - * Records the time at which a transport update was last sent to the given - * contact. + * Records a transport ack from the give contact for the given version + * unless the contact has already acked an equal or higher version. * <p> * Locking: contact read, transport write. */ - void setTransportsSent(T txn, ContactId c, long timestamp) - throws DbException; + void setTransportUpdateAcked(T txn, ContactId c, TransportId t, + long version) throws DbException; } diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index d9333bcd2b..d1308b3b1a 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -5,7 +5,6 @@ import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP; import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE; import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS; import static net.sf.briar.db.DatabaseConstants.MAX_MS_BETWEEN_SPACE_CHECKS; -import static net.sf.briar.db.DatabaseConstants.MAX_UPDATE_INTERVAL; import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE; import java.io.IOException; @@ -17,7 +16,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Logger; @@ -36,12 +34,11 @@ import net.sf.briar.api.db.event.ContactAddedEvent; import net.sf.briar.api.db.event.ContactRemovedEvent; import net.sf.briar.api.db.event.DatabaseEvent; import net.sf.briar.api.db.event.DatabaseListener; -import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent; import net.sf.briar.api.db.event.MessageAddedEvent; import net.sf.briar.api.db.event.MessageReceivedEvent; import net.sf.briar.api.db.event.RatingChangedEvent; -import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent; import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent; +import net.sf.briar.api.db.event.TransportsUpdatedEvent; import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.AuthorId; @@ -50,10 +47,10 @@ import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.SubscriptionAck; import net.sf.briar.api.protocol.SubscriptionUpdate; -import net.sf.briar.api.protocol.Transport; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.transport.ContactTransport; @@ -98,7 +95,6 @@ DatabaseCleaner.Callback { private final Database<T> db; private final DatabaseCleaner cleaner; private final ShutdownManager shutdown; - private final PacketFactory packetFactory; private final Clock clock; private final Collection<DatabaseListener> listeners = @@ -114,12 +110,10 @@ DatabaseCleaner.Callback { @Inject DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory, - Clock clock) { + ShutdownManager shutdown, Clock clock) { this.db = db; this.cleaner = cleaner; this.shutdown = shutdown; - this.packetFactory = packetFactory; this.clock = clock; } @@ -173,23 +167,13 @@ DatabaseCleaner.Callback { try { subscriptionLock.writeLock().lock(); try { - transportLock.writeLock().lock(); + T txn = db.startTransaction(); try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - c = db.addContact(txn); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.writeLock().unlock(); + c = db.addContact(txn); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { subscriptionLock.writeLock().unlock(); @@ -243,12 +227,9 @@ DatabaseCleaner.Callback { T txn = db.startTransaction(); try { // Don't store the message if the user has - // unsubscribed from the group or the message - // predates the subscription - if(db.containsSubscription(txn, m.getGroup(), - m.getTimestamp())) { + // unsubscribed from the group + if(db.containsSubscription(txn, m.getGroup())) added = storeGroupMessage(txn, m, null); - } db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -418,11 +399,27 @@ DatabaseCleaner.Callback { } } + public void addTransport(TransportId t) throws DbException { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + db.addTransport(txn, t); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } + /** - * If the given message is already in the database, returns false. * Otherwise stores the message and marks it as new or seen with respect to * the given contact, depending on whether the message is outgoing or - * incoming, respectively. + * incoming, respectively; or returns false if the message is already in + * the database. * <p> * Locking: contact read, message write, messageStatus write. */ @@ -478,7 +475,7 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - return packetFactory.createAck(acked); + return new Ack(acked); } public Collection<byte[]> generateBatch(ContactId c, int maxLength) @@ -627,80 +624,94 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - return packetFactory.createOffer(offered); + return new Offer(offered); } - public SubscriptionUpdate generateSubscriptionUpdate(ContactId c) + public SubscriptionAck generateSubscriptionAck(ContactId c) throws DbException { - Map<GroupId, GroupId> holes; - Map<Group, Long> subs; - long expiry, timestamp; contactLock.readLock().lock(); try { - subscriptionLock.readLock().lock(); + subscriptionLock.writeLock().lock(); try { T txn = db.startTransaction(); try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - timestamp = clock.currentTimeMillis() - 1; - holes = db.getVisibleHoles(txn, c, timestamp); - subs = db.getVisibleSubscriptions(txn, c, timestamp); - expiry = db.getExpiryTime(txn); + SubscriptionAck a = db.getSubscriptionAck(txn, c); db.commitTransaction(txn); + return a; } catch(DbException e) { db.abortTransaction(txn); throw e; } } finally { - subscriptionLock.readLock().unlock(); + subscriptionLock.writeLock().unlock(); } } finally { contactLock.readLock().unlock(); } - return packetFactory.createSubscriptionUpdate(holes, subs, expiry, - timestamp); } - private boolean updateIsDue(long sent) { - long now = clock.currentTimeMillis(); - return now - sent >= MAX_UPDATE_INTERVAL; - } - - public TransportUpdate generateTransportUpdate(ContactId c) + public SubscriptionUpdate generateSubscriptionUpdate(ContactId c) throws DbException { - boolean due; - Collection<Transport> transports; - long timestamp; contactLock.readLock().lock(); try { - transportLock.readLock().lock(); + subscriptionLock.writeLock().lock(); try { T txn = db.startTransaction(); try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - // Work out whether an update is due - long modified = db.getTransportsModified(txn); - long sent = db.getTransportsSent(txn, c); - due = modified >= sent || updateIsDue(sent); + SubscriptionUpdate s = db.getSubscriptionUpdate(txn, c); db.commitTransaction(txn); + return s; } catch(DbException e) { db.abortTransaction(txn); throw e; } } finally { - transportLock.readLock().unlock(); + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<TransportAck> generateTransportAcks(ContactId c) + throws DbException { + contactLock.readLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Collection<TransportAck> acks = db.getTransportAcks(txn, c); + db.commitTransaction(txn); + return acks; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); } - if(!due) return null; + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<TransportUpdate> generateTransportUpdates(ContactId c) + throws DbException { + contactLock.readLock().lock(); + try { transportLock.writeLock().lock(); try { T txn = db.startTransaction(); try { - transports = db.getLocalTransports(txn); - timestamp = clock.currentTimeMillis(); - db.setTransportsSent(txn, c, timestamp); + Collection<TransportUpdate> updates = + db.getTransportUpdates(txn, c); db.commitTransaction(txn); + return updates; } catch(DbException e) { db.abortTransaction(txn); throw e; @@ -711,7 +722,6 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - return packetFactory.createTransportUpdate(transports, timestamp); } public TransportConfig getConfig(TransportId t) throws DbException { @@ -766,23 +776,6 @@ DatabaseCleaner.Callback { } } - public Collection<Transport> getLocalTransports() throws DbException { - transportLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<Transport> transports = db.getLocalTransports(txn); - db.commitTransaction(txn); - return transports; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.readLock().unlock(); - } - } - public Collection<MessageHeader> getMessageHeaders(GroupId g) throws DbException { messageLock.readLock().lock(); @@ -1022,7 +1015,6 @@ DatabaseCleaner.Callback { try { if(!p.equals(db.getLocalProperties(txn, t))) { db.mergeLocalProperties(txn, t, p); - db.setTransportsModified(txn, clock.currentTimeMillis()); changed = true; } db.commitTransaction(txn); @@ -1034,7 +1026,7 @@ DatabaseCleaner.Callback { transportLock.writeLock().unlock(); } // Call the listeners outside the lock - if(changed) callListeners(new LocalTransportsUpdatedEvent()); + if(changed) callListeners(new TransportsUpdatedEvent()); } public void receiveAck(ContactId c, Ack a) throws DbException { @@ -1114,8 +1106,7 @@ DatabaseCleaner.Callback { throws DbException { GroupId g = m.getGroup(); if(g == null) return storePrivateMessage(txn, m, c, true); - if(!db.containsVisibleSubscription(txn, g, c, m.getTimestamp())) - return false; + if(!db.containsVisibleSubscription(txn, c, g)) return false; return storeGroupMessage(txn, m, c); } @@ -1161,12 +1152,36 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - return packetFactory.createRequest(request, offered.size()); + return new Request(request, offered.size()); + } + + public void receiveSubscriptionAck(ContactId c, SubscriptionAck a) + throws DbException { + contactLock.readLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + long version = a.getVersionNumber(); + db.setSubscriptionUpdateAcked(txn, c, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } } public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate s) throws DbException { - // Update the contact's subscriptions contactLock.readLock().lock(); try { subscriptionLock.writeLock().lock(); @@ -1175,17 +1190,7 @@ DatabaseCleaner.Callback { try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - Map<GroupId, GroupId> holes = s.getHoles(); - for(Entry<GroupId, GroupId> e : holes.entrySet()) { - GroupId start = e.getKey(), end = e.getValue(); - db.removeSubscriptions(txn, c, start, end); - } - Map<Group, Long> subs = s.getSubscriptions(); - for(Entry<Group, Long> e : subs.entrySet()) { - db.addSubscription(txn, c, e.getKey(), e.getValue()); - } - db.setExpiryTime(txn, c, s.getExpiryTime()); - db.setSubscriptionsReceived(txn, c, s.getTimestamp()); + db.setSubscriptions(txn, c, s); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -1197,15 +1202,36 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - // Call the listeners outside the lock - callListeners(new SubscriptionsUpdatedEvent( - Collections.singletonList(c))); + } + + public void receiveTransportAck(ContactId c, TransportAck a) + throws DbException { + contactLock.readLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + TransportId t = a.getId(); + long version = a.getVersionNumber(); + db.setTransportUpdateAcked(txn, c, t, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } } public void receiveTransportUpdate(ContactId c, TransportUpdate t) throws DbException { - Collection<Transport> transports; - // Update the contact's transport properties contactLock.readLock().lock(); try { transportLock.writeLock().lock(); @@ -1214,8 +1240,7 @@ DatabaseCleaner.Callback { try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - transports = t.getTransports(); - db.setTransports(txn, c, transports, t.getTimestamp()); + db.setRemoteProperties(txn, c, t); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -1227,8 +1252,6 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - // Call the listeners outside the lock - callListeners(new RemoteTransportsUpdatedEvent(c, transports)); } public void removeContact(ContactId c) throws DbException { @@ -1398,8 +1421,7 @@ DatabaseCleaner.Callback { public void setVisibility(GroupId g, Collection<ContactId> visible) throws DbException { - List<ContactId> affected = new ArrayList<ContactId>(); - contactLock.readLock().lock(); + contactLock.writeLock().lock(); try { subscriptionLock.writeLock().lock(); try { @@ -1413,13 +1435,8 @@ DatabaseCleaner.Callback { for(ContactId c : db.getContacts(txn)) { boolean then = oldVisible.contains(c); boolean now = visible.contains(c); - if(!then && now) { - db.addVisibility(txn, c, g); - affected.add(c); - } else if(then && !now) { - db.removeVisibility(txn, c, g); - affected.add(c); - } + if(!then && now) db.addVisibility(txn, c, g); + else if(then && !now) db.removeVisibility(txn, c, g); } db.commitTransaction(txn); } catch(DbException e) { @@ -1430,12 +1447,7 @@ DatabaseCleaner.Callback { subscriptionLock.writeLock().unlock(); } } finally { - contactLock.readLock().unlock(); - } - // Call the listeners outside the lock - if(!affected.isEmpty()) { - affected = Collections.unmodifiableList(affected); - callListeners(new SubscriptionsUpdatedEvent(affected)); + contactLock.writeLock().unlock(); } } @@ -1459,7 +1471,7 @@ DatabaseCleaner.Callback { public void unsubscribe(GroupId g) throws DbException { Collection<ContactId> affected = null; - contactLock.readLock().lock(); + contactLock.writeLock().lock(); try { messageLock.writeLock().lock(); try { @@ -1493,7 +1505,7 @@ DatabaseCleaner.Callback { messageLock.writeLock().unlock(); } } finally { - contactLock.readLock().unlock(); + contactLock.writeLock().unlock(); } // Call the listeners outside the lock if(affected != null && !affected.isEmpty()) diff --git a/briar-core/src/net/sf/briar/db/DatabaseModule.java b/briar-core/src/net/sf/briar/db/DatabaseModule.java index 6ed65d9b8d..ce5cdcf6bc 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseModule.java +++ b/briar-core/src/net/sf/briar/db/DatabaseModule.java @@ -8,7 +8,6 @@ import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseConfig; import net.sf.briar.api.db.DatabaseExecutor; import net.sf.briar.api.lifecycle.ShutdownManager; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.util.BoundedExecutor; import com.google.inject.AbstractModule; @@ -41,15 +40,14 @@ public class DatabaseModule extends AbstractModule { } @Provides - Database<Connection> getDatabase(Clock clock, DatabaseConfig config) { - return new H2Database(clock, config); + Database<Connection> getDatabase(DatabaseConfig config) { + return new H2Database(config); } @Provides @Singleton DatabaseComponent getDatabaseComponent(Database<Connection> db, - DatabaseCleaner cleaner, ShutdownManager shutdown, - PacketFactory packetFactory, Clock clock) { + DatabaseCleaner cleaner, ShutdownManager shutdown, Clock clock) { return new DatabaseComponentImpl<Connection>(db, cleaner, shutdown, - packetFactory, clock); + clock); } } diff --git a/briar-core/src/net/sf/briar/db/H2Database.java b/briar-core/src/net/sf/briar/db/H2Database.java index 4c00ec8c22..471b679106 100644 --- a/briar-core/src/net/sf/briar/db/H2Database.java +++ b/briar-core/src/net/sf/briar/db/H2Database.java @@ -30,8 +30,8 @@ class H2Database extends JdbcDatabase { private final long maxSize; @Inject - H2Database(Clock clock, DatabaseConfig config) { - super(clock, HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE); + H2Database(DatabaseConfig config) { + super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE); home = new File(config.getDataDirectory(), "db"); url = "jdbc:h2:split:" + home.getPath() + ";CIPHER=AES;DB_CLOSE_ON_EXIT=false"; diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java index d5a4f5544d..708595a0b2 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -27,7 +27,6 @@ import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; import net.sf.briar.api.TransportConfig; import net.sf.briar.api.TransportProperties; -import net.sf.briar.api.clock.Clock; import net.sf.briar.api.db.DbClosedException; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.MessageHeader; @@ -36,8 +35,11 @@ import net.sf.briar.api.protocol.Group; import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.Transport; +import net.sf.briar.api.protocol.SubscriptionAck; +import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportId; +import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.transport.ContactTransport; import net.sf.briar.api.transport.TemporarySecret; import net.sf.briar.util.FileUtils; @@ -48,19 +50,14 @@ import net.sf.briar.util.FileUtils; */ abstract class JdbcDatabase implements Database<Connection> { - private static final String CREATE_SUBSCRIPTIONS = - "CREATE TABLE subscriptions" - + " (groupId HASH NOT NULL," - + " groupName VARCHAR NOT NULL," - + " groupKey BINARY," // Null for unrestricted groups - + " start BIGINT NOT NULL," - + " PRIMARY KEY (groupId))"; - + // Locking: contact private static final String CREATE_CONTACTS = "CREATE TABLE contacts" + " (contactId COUNTER," + + " expiry BIGINT NOT NULL DEFAULT 0," // FIXME: Move this + " PRIMARY KEY (contactId))"; + // Locking: message private static final String CREATE_MESSAGES = "CREATE TABLE messages" + " (messageId HASH NOT NULL," @@ -77,9 +74,10 @@ abstract class JdbcDatabase implements Database<Connection> { + " contactId INT," // Null for group messages + " PRIMARY KEY (messageId)," + " FOREIGN KEY (groupId)" - + " REFERENCES subscriptions (groupId)" + + " REFERENCES groups (groupId)" + " ON DELETE CASCADE," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; private static final String INDEX_MESSAGES_BY_PARENT = @@ -94,58 +92,28 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String INDEX_MESSAGES_BY_SENDABILITY = "CREATE INDEX messagesBySendability ON messages (sendability)"; - private static final String CREATE_VISIBILITIES = - "CREATE TABLE visibilities" - + " (contactId INT NOT NULL," - + " groupId HASH," // Null for the head of the linked list - + " nextId HASH," // Null for the tail of the linked list - + " deleted BIGINT NOT NULL," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" - + " ON DELETE CASCADE," - + " FOREIGN KEY (groupId)" - + " REFERENCES subscriptions (groupId)" - + " ON DELETE CASCADE)"; - - private static final String INDEX_VISIBILITIES_BY_GROUP = - "CREATE INDEX visibilitiesByGroup ON visibilities (groupId)"; - - private static final String INDEX_VISIBILITIES_BY_NEXT = - "CREATE INDEX visibilitiesByNext on visibilities (nextId)"; - + // Locking: contact read, messageStatus private static final String CREATE_MESSAGES_TO_ACK = "CREATE TABLE messagesToAck" + " (messageId HASH NOT NULL," + " contactId INT NOT NULL," + " PRIMARY KEY (messageId, contactId)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" - + " ON DELETE CASCADE)"; - - private static final String CREATE_CONTACT_SUBSCRIPTIONS = - "CREATE TABLE contactSubscriptions" - + " (contactId INT NOT NULL," - + " groupId HASH NOT NULL," - + " groupName VARCHAR NOT NULL," - + " groupKey BINARY," // Null for unrestricted groups - + " start BIGINT NOT NULL," - + " PRIMARY KEY (contactId, groupId)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - private static final String CREATE_RATINGS = - "CREATE TABLE ratings" - + " (authorId HASH NOT NULL," - + " rating SMALLINT NOT NULL," - + " PRIMARY KEY (authorId))"; - + // Locking: contact read, message read, messageStatus private static final String CREATE_STATUSES = "CREATE TABLE statuses" + " (messageId HASH NOT NULL," + " contactId INT NOT NULL," + " status SMALLINT NOT NULL," + " PRIMARY KEY (messageId, contactId)," - + " FOREIGN KEY (messageId) REFERENCES messages (messageId)" + + " FOREIGN KEY (messageId)" + + " REFERENCES messages (messageId)" + " ON DELETE CASCADE," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; private static final String INDEX_STATUSES_BY_MESSAGE = @@ -154,30 +122,137 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String INDEX_STATUSES_BY_CONTACT = "CREATE INDEX statusesByContact ON statuses (contactId)"; + // Locking: message read, messageFlag + private static final String CREATE_FLAGS = + "CREATE TABLE flags" + + " (messageId HASH NOT NULL," + + " read BOOLEAN NOT NULL," + + " starred BOOLEAN NOT NULL," + + " PRIMARY KEY (messageId)," + + " FOREIGN KEY (messageId)" + + " REFERENCES messages (messageId)" + + " ON DELETE CASCADE)"; + + // Locking: rating + private static final String CREATE_RATINGS = + "CREATE TABLE ratings" + + " (authorId HASH NOT NULL," + + " rating SMALLINT NOT NULL," + + " PRIMARY KEY (authorId))"; + + // Locking: subscription + private static final String CREATE_GROUPS = + "CREATE TABLE groups" + + " (groupId HASH NOT NULL," + + " name VARCHAR NOT NULL," + + " key BINARY," // Null for unrestricted groups + + " PRIMARY KEY (groupId))"; + + // Locking: contact read, subscription + private static final String CREATE_GROUP_VISIBILITIES = + "CREATE TABLE groupVisibilities" + + " (contactId INT NOT NULL," + + " groupId HASH NOT NULL," + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (groupId)" + + " REFERENCES groups (groupId)" + + " ON DELETE CASCADE)"; + + // Locking: contact read, subscription + private static final String CREATE_CONTACT_GROUPS = + "CREATE TABLE contactGroups" + + " (contactId INT NOT NULL," + + " groupId HASH NOT NULL," // Not a foreign key + + " name VARCHAR NOT NULL," + + " key BINARY," // Null for unrestricted groups + + " PRIMARY KEY (contactId, groupId)," + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE)"; + + // Locking: contact read, subscription + private static final String CREATE_GROUP_VERSIONS = + "CREATE TABLE groupVersions" + + " (contactId INT NOT NULL," + + " localVersion BIGINT NOT NULL," + + " localAcked BIGINT NOT NULL," + + " remoteVersion BIGINT NOT NULL," + + " remoteAcked BOOLEAN NOT NULL," + + " PRIMARY KEY (contactId)," + + " FOREIGN KEY (contactid)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE)"; + + // Locking: transport + private static final String CREATE_TRANSPORTS = + "CREATE TABLE transports" + + " (transportId HASH NOT NULL," + + " PRIMARY KEY (transportId))"; + + // Locking: transport private static final String CREATE_TRANSPORT_CONFIGS = "CREATE TABLE transportConfigs" + " (transportId HASH NOT NULL," + " key VARCHAR NOT NULL," + " value VARCHAR NOT NULL," - + " PRIMARY KEY (transportId, key))"; + + " PRIMARY KEY (transportId, key)," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + + " ON DELETE CASCADE)"; + // Locking: transport private static final String CREATE_TRANSPORT_PROPS = "CREATE TABLE transportProperties" + " (transportId HASH NOT NULL," + " key VARCHAR NOT NULL," + " value VARCHAR NOT NULL," - + " PRIMARY KEY (transportId, key))"; + + " PRIMARY KEY (transportId, key)," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + + " ON DELETE CASCADE)"; + + // Locking: contact read, transport + private static final String CREATE_TRANSPORT_VERSIONS = + "CREATE TABLE transportVersions" + + " (contactId HASH NOT NULL," + + " transportId HASH NOT NULL," + + " localVersion BIGINT NOT NULL," + + " localAcked BIGINT NOT NULL," + + " PRIMARY KEY (contactId, transportId)," + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + + " ON DELETE CASCADE)"; + // Locking: contact read, transport private static final String CREATE_CONTACT_TRANSPORT_PROPS = "CREATE TABLE contactTransportProperties" + " (contactId INT NOT NULL," - + " transportId HASH NOT NULL," + + " transportId HASH NOT NULL," // Not a foreign key + " key VARCHAR NOT NULL," + " value VARCHAR NOT NULL," + " PRIMARY KEY (contactId, transportId, key)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE)"; + + // Locking: contact read, transport + private static final String CREATE_CONTACT_TRANSPORT_VERSIONS = + "CREATE TABLE contactTransportVersions" + + " (contactId HASH NOT NULL," + + " transportId HASH NOT NULL," // Not a foreign key + + " remoteVersion BIGINT NOT NULL," + + " remoteAcked BOOLEAN NOT NULL," + + " PRIMARY KEY (contactId, transportId)," + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; + // Locking: contact read, window private static final String CREATE_CONTACT_TRANSPORTS = "CREATE TABLE contactTransports" + " (contactId INT NOT NULL," @@ -187,9 +262,14 @@ abstract class JdbcDatabase implements Database<Connection> { + " latency BIGINT NOT NULL," + " alice BOOLEAN NOT NULL," + " PRIMARY KEY (contactId, transportId)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; + // Locking: contact read, window private static final String CREATE_SECRETS = "CREATE TABLE secrets" + " (contactId INT NOT NULL," @@ -200,42 +280,16 @@ abstract class JdbcDatabase implements Database<Connection> { + " centre BIGINT NOT NULL," + " bitmap BINARY NOT NULL," + " PRIMARY KEY (contactId, transportId, period)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" - + " ON DELETE CASCADE)"; - - private static final String CREATE_SUBSCRIPTION_TIMES = - "CREATE TABLE subscriptionTimes" - + " (contactId INT NOT NULL," - + " received BIGINT NOT NULL," - + " acked BIGINT NOT NULL," - + " expiry BIGINT NOT NULL," - + " PRIMARY KEY (contactId)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" - + " ON DELETE CASCADE)"; - - private static final String CREATE_TRANSPORT_TIMESTAMPS = - "CREATE TABLE transportTimestamps" - + " (contactId INT NOT NULL," - + " sent BIGINT NOT NULL," - + " received BIGINT NOT NULL," - + " modified BIGINT NOT NULL," - + " PRIMARY KEY (contactId)," - + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" - + " ON DELETE CASCADE)"; - - private static final String CREATE_FLAGS = - "CREATE TABLE flags" - + " (messageId HASH NOT NULL," - + " read BOOLEAN NOT NULL," - + " starred BOOLEAN NOT NULL," - + " PRIMARY KEY (messageId)," - + " FOREIGN KEY (messageId) REFERENCES messages (messageId)" + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; private static final Logger LOG = Logger.getLogger(JdbcDatabase.class.getName()); - private final Clock clock; // Different database libraries use different names for certain types private final String hashType, binaryType, counterType, secretType; @@ -247,9 +301,8 @@ abstract class JdbcDatabase implements Database<Connection> { protected abstract Connection createConnection() throws SQLException; - JdbcDatabase(Clock clock, String hashType, String binaryType, - String counterType, String secretType) { - this.clock = clock; + JdbcDatabase(String hashType, String binaryType, String counterType, + String secretType) { this.hashType = hashType; this.binaryType = binaryType; this.counterType = counterType; @@ -286,30 +339,30 @@ abstract class JdbcDatabase implements Database<Connection> { Statement s = null; try { s = txn.createStatement(); - s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTIONS)); s.executeUpdate(insertTypeNames(CREATE_CONTACTS)); s.executeUpdate(insertTypeNames(CREATE_MESSAGES)); s.executeUpdate(INDEX_MESSAGES_BY_PARENT); s.executeUpdate(INDEX_MESSAGES_BY_AUTHOR); s.executeUpdate(INDEX_MESSAGES_BY_TIMESTAMP); s.executeUpdate(INDEX_MESSAGES_BY_SENDABILITY); - s.executeUpdate(insertTypeNames(CREATE_VISIBILITIES)); - s.executeUpdate(INDEX_VISIBILITIES_BY_GROUP); - s.executeUpdate(INDEX_VISIBILITIES_BY_NEXT); s.executeUpdate(insertTypeNames(CREATE_MESSAGES_TO_ACK)); - s.executeUpdate(insertTypeNames(CREATE_CONTACT_SUBSCRIPTIONS)); - s.executeUpdate(insertTypeNames(CREATE_RATINGS)); s.executeUpdate(insertTypeNames(CREATE_STATUSES)); s.executeUpdate(INDEX_STATUSES_BY_MESSAGE); s.executeUpdate(INDEX_STATUSES_BY_CONTACT); + s.executeUpdate(insertTypeNames(CREATE_FLAGS)); + s.executeUpdate(insertTypeNames(CREATE_RATINGS)); + s.executeUpdate(insertTypeNames(CREATE_GROUPS)); + s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES)); + s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS)); + s.executeUpdate(insertTypeNames(CREATE_GROUP_VERSIONS)); + s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_CONFIGS)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_PROPS)); + s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_VERSIONS)); s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_PROPS)); + s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_VERSIONS)); s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS)); s.executeUpdate(insertTypeNames(CREATE_SECRETS)); - s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_TIMES)); - s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_TIMESTAMPS)); - s.executeUpdate(insertTypeNames(CREATE_FLAGS)); s.close(); } catch(SQLException e) { tryToClose(s); @@ -427,7 +480,7 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - // Create a new contact row + // Create a contact row String sql = "INSERT INTO contacts DEFAULT VALUES"; ps = txn.prepareStatement(sql); int affected = ps.executeUpdate(); @@ -444,31 +497,41 @@ abstract class JdbcDatabase implements Database<Connection> { if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - // Create the head-of-list pointer for the visibility list - sql = "INSERT INTO visibilities (contactId, deleted)" - + " VALUES (?, ZERO())"; + // Create a group version row + sql = "INSERT INTO groupVersions (contactId, localVersion," + + " localAcked, remoteVersion, remoteAcked)" + + " VALUES (?, ?, ZERO(), ZERO(), TRUE)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); + ps.setInt(2, 1); affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); - // Initialise the subscription timestamps - sql = "INSERT INTO subscriptionTimes" - + " (contactId, received, acked, expiry)" - + " VALUES (?, ZERO(), ZERO(), ZERO())"; + // Create a transport version row for each local transport + sql = "SELECT transportId FROM transports"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - affected = ps.executeUpdate(); - if(affected != 1) throw new DbStateException(); + rs = ps.executeQuery(); + Collection<byte[]> transports = new ArrayList<byte[]>(); + while(rs.next()) transports.add(rs.getBytes(1)); + rs.close(); ps.close(); - // Initialise the transport timestamps - sql = "INSERT INTO transportTimestamps" - + " (contactId, sent, received, modified)" - + " VALUES (?, ZERO(), ZERO(), ZERO())"; + if(transports.isEmpty()) return c; + sql = "INSERT INTO transportVersions" + + " (contactId, transportId, localVersion, localAcked)" + + " VALUES (?, ?, ?, ZERO())"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - affected = ps.executeUpdate(); - if(affected != 1) throw new DbStateException(); + ps.setInt(3, 1); + for(byte[] t : transports) { + ps.setBytes(2, t); + ps.addBatch(); + } + int[] affectedBatch = ps.executeBatch(); + if(affectedBatch.length != transports.size()) + throw new DbStateException(); + for(int i = 0; i < affectedBatch.length; i++) { + if(affectedBatch[i] != 1) throw new DbStateException(); + } ps.close(); return c; } catch(SQLException e) { @@ -482,9 +545,8 @@ abstract class JdbcDatabase implements Database<Connection> { throws DbException { PreparedStatement ps = null; try { - String sql = "INSERT INTO contactTransports" - + " (contactId, transportId, epoch, clockDiff, latency," - + " alice)" + String sql = "INSERT INTO contactTransports (contactId," + + " transportId, epoch, clockDiff, latency, alice)" + " VALUES (?, ?, ?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, ct.getContactId().getInt()); @@ -630,15 +692,12 @@ abstract class JdbcDatabase implements Database<Connection> { public void addSubscription(Connection txn, Group g) throws DbException { PreparedStatement ps = null; try { - String sql = "INSERT INTO subscriptions" - + " (groupId, groupName, groupKey, start)" - + " VALUES (?, ?, ?, ?)"; + String sql = "INSERT INTO groups" + + " (groupId, name, key) VALUES (?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getId().getBytes()); ps.setString(2, g.getName()); ps.setBytes(3, g.getPublicKey()); - long now = clock.currentTimeMillis(); - ps.setLong(4, now); int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); @@ -653,9 +712,8 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; try { // Store the new secrets - String sql = "INSERT INTO secrets" - + " (contactId, transportId, period, secret, outgoing," - + " centre, bitmap)" + String sql = "INSERT INTO secrets (contactId, transportId, period," + + " secret, outgoing, centre, bitmap)" + " VALUES (?, ?, ?, ?, ?, ?, ?)"; ps = txn.prepareStatement(sql); for(TemporarySecret s : secrets) { @@ -698,39 +756,45 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void addSubscription(Connection txn, ContactId c, Group g, - long start) throws DbException { + public void addTransport(Connection txn, TransportId t) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - // Check whether the subscription already exists - String sql = "SELECT NULL FROM contactSubscriptions" - + " WHERE contactId = ? AND groupId = ?"; + // Create a transport row + String sql = "INSERT INTO transports (transportId) VALUES (?)"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, t.getBytes()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps.close(); + // Create a transport version row for each contact + sql = "SELECT contactId FROM contacts"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getId().getBytes()); rs = ps.executeQuery(); - boolean found = rs.next(); - if(rs.next()) throw new DbStateException(); + Collection<Integer> contacts = new ArrayList<Integer>(); + while(rs.next()) contacts.add(rs.getInt(1)); rs.close(); ps.close(); - if(found) return; - // Add the subscription - sql = "INSERT INTO contactSubscriptions" - + " (contactId, groupId, groupName, groupKey, start)" - + " VALUES (?, ?, ?, ?, ?)"; + if(contacts.isEmpty()) return; + sql = "INSERT INTO transportVersions" + + " (contactId, transportId, localVersion, localAcked)" + + " VALUES (?, ?, ?, ZERO())"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getId().getBytes()); - ps.setString(3, g.getName()); - ps.setBytes(4, g.getPublicKey()); - ps.setLong(5, start); - int affected = ps.executeUpdate(); - if(affected != 1) throw new DbStateException(); - ps.close(); + ps.setBytes(2, t.getBytes()); + ps.setInt(3, 1); + for(Integer c : contacts) { + ps.setInt(1, c); + ps.addBatch(); + } + int[] batchAffected = ps.executeBatch(); + if(batchAffected.length != contacts.size()) + throw new DbStateException(); + for(int i = 0; i < batchAffected.length; i++) { + if(batchAffected[i] != 1) throw new DbStateException(); + } } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); + tryToClose(rs); throw new DbException(e); } } @@ -738,71 +802,25 @@ abstract class JdbcDatabase implements Database<Connection> { public void addVisibility(Connection txn, ContactId c, GroupId g) throws DbException { PreparedStatement ps = null; - ResultSet rs = null; try { - // Find the new element's predecessor - byte[] groupId = null, nextId = null; - long deleted = 0L; - String sql = "SELECT groupId, nextId, deleted FROM visibilities" - + " WHERE contactId = ? AND nextId > ?" - + " ORDER BY nextId LIMIT ?"; + String sql = "INSERT INTO groupVisibilities (contactId, groupId)" + + " VALUES (?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getBytes()); - ps.setInt(3, 1); - rs = ps.executeQuery(); - if(!rs.next()) { - // The predecessor has a null nextId so it's at the tail - rs.close(); - ps.close(); - sql = "SELECT groupId, nextId, deleted FROM visibilities" - + " WHERE contactId = ? AND nextId IS NULL"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - } - groupId = rs.getBytes(1); - nextId = rs.getBytes(2); - deleted = rs.getLong(3); - if(rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - // Update the predecessor's nextId - if(groupId == null) { - // Inserting at the head of the list - sql = "UPDATE visibilities SET nextId = ?" - + " WHERE contactId = ? AND groupId IS NULL"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - ps.setInt(2, c.getInt()); - } else { - // Inserting in the middle or at the tail of the list - sql = "UPDATE visibilities SET nextId = ?" - + " WHERE contactId = ? AND groupId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - ps.setInt(2, c.getInt()); - ps.setBytes(3, groupId); - } + ps.setBytes(3, g.getBytes()); int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); - // Insert the new element - sql = "INSERT INTO visibilities" - + " (contactId, groupId, nextId, deleted)" - + " VALUES (?, ?, ?, ?)"; + // Bump the subscription version + sql = "UPDATE groupVersions SET localVersion = localVersion + ?" + + " WHERE contactId = ?"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getBytes()); - if(nextId == null) ps.setNull(3, BINARY); // At the tail - else ps.setBytes(3, nextId); // In the middle - ps.setLong(4, deleted); + ps.setInt(1, 1); + ps.setInt(2, c.getInt()); affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); throw new DbException(e); } @@ -878,7 +896,7 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT NULL FROM subscriptions WHERE groupId = ?"; + String sql = "SELECT NULL FROM groups WHERE groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); rs = ps.executeQuery(); @@ -894,16 +912,18 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public boolean containsSubscription(Connection txn, GroupId g, long time) - throws DbException { + public boolean containsVisibleSubscription(Connection txn, ContactId c, + GroupId g) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT NULL FROM subscriptions" - + " WHERE groupId = ? AND start <= ?"; + String sql = "SELECT NULL FROM groups AS g" + + " JOIN groupVisibilities AS gv" + + " ON g.groupId = gv.groupId" + + " WHERE g.groupId = ? AND contactId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); - ps.setLong(2, time); + ps.setInt(2, c.getInt()); rs = ps.executeQuery(); boolean found = rs.next(); if(rs.next()) throw new DbStateException(); @@ -917,35 +937,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public boolean containsVisibleSubscription(Connection txn, GroupId g, - ContactId c, long time) throws DbException { - boolean found = false; - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT start FROM subscriptions AS s" - + " JOIN visibilities AS v" - + " ON s.groupId = v.groupId" - + " WHERE s.groupId = ? AND contactId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - ps.setInt(2, c.getInt()); - rs = ps.executeQuery(); - if(rs.next()) { - long start = rs.getLong(1); - if(start <= time) found = true; - if(rs.next()) throw new DbStateException(); - } - rs.close(); - ps.close(); - return found; - } catch(SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - public TransportConfig getConfig(Connection txn, TransportId t) throws DbException { PreparedStatement ps = null; @@ -1100,38 +1091,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Collection<Transport> getLocalTransports(Connection txn) - throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT transportId, key, value" - + " FROM transportProperties" - + " ORDER BY transportId"; - ps = txn.prepareStatement(sql); - rs = ps.executeQuery(); - List<Transport> transports = new ArrayList<Transport>(); - TransportId lastId = null; - Transport t = null; - while(rs.next()) { - TransportId id = new TransportId(rs.getBytes(1)); - if(!id.equals(lastId)) { - t = new Transport(id); - transports.add(t); - } - t.getProperties().put(rs.getString(2), rs.getString(3)); - lastId = id; - } - rs.close(); - ps.close(); - return Collections.unmodifiableList(transports); - } catch(SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - public byte[] getMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; ResultSet rs = null; @@ -1246,19 +1205,18 @@ abstract class JdbcDatabase implements Database<Connection> { if(raw != null) return raw; // Do we have a sendable group message with the given ID? sql = "SELECT length, raw FROM messages AS m" - + " JOIN contactSubscriptions AS cs" - + " ON m.groupId = cs.groupId" - + " JOIN visibilities AS v" - + " ON m.groupId = v.groupId" - + " AND cs.contactId = v.contactId" + + " JOIN contactGroups AS cg" + + " ON m.groupId = cg.groupId" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + + " AND cg.contactId = gv.contactId" + + " JOIN contacts AS c" + + " ON cg.contactId = c.contactId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " AND cs.contactId = s.contactId" - + " JOIN subscriptionTimes AS st" - + " ON cs.contactId = st.contactId" + + " AND cg.contactId = s.contactId" + " WHERE m.messageId = ?" - + " AND cs.contactId = ?" - + " AND timestamp >= start" + + " AND cg.contactId = ?" + " AND timestamp >= expiry" + " AND status = ?" + " AND sendability > ZERO()"; @@ -1353,18 +1311,17 @@ abstract class JdbcDatabase implements Database<Connection> { return Collections.unmodifiableList(ids); // Do we have any sendable group messages? sql = "SELECT m.messageId FROM messages AS m" - + " JOIN contactSubscriptions AS cs" - + " ON m.groupId = cs.groupId" - + " JOIN visibilities AS v" - + " ON m.groupId = v.groupId" - + " AND cs.contactId = v.contactId" + + " JOIN contactGroups AS cg" + + " ON m.groupId = cg.groupId" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + + " AND cg.contactId = gv.contactId" + + " JOIN contacts AS c" + + " ON cg.contactId = c.contactId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " AND cs.contactId = s.contactId" - + " JOIN subscriptionTimes AS st" - + " ON cs.contactId = st.contactId" - + " WHERE cs.contactId = ?" - + " AND timestamp >= start" + + " AND cg.contactId = s.contactId" + + " WHERE cg.contactId = ?" + " AND timestamp >= expiry" + " AND status = ?" + " AND sendability > ZERO()" @@ -1401,7 +1358,7 @@ abstract class JdbcDatabase implements Database<Connection> { if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - sql = "SELECT COUNT(messageId) FROM messages" + sql = "SELECT COUNT (messageId) FROM messages" + " WHERE parentId = ? AND groupId = ?" + " AND sendability > ZERO()"; ps = txn.prepareStatement(sql); @@ -1509,11 +1466,12 @@ abstract class JdbcDatabase implements Database<Connection> { TransportProperties p = null; while(rs.next()) { ContactId id = new ContactId(rs.getInt(1)); + String key = rs.getString(2), value = rs.getString(3); if(!id.equals(lastId)) { p = new TransportProperties(); properties.put(id, p); } - p.put(rs.getString(2), rs.getString(3)); + p.put(key, value); } rs.close(); ps.close(); @@ -1614,18 +1572,17 @@ abstract class JdbcDatabase implements Database<Connection> { if(total == maxLength) return Collections.unmodifiableList(ids); // Do we have any sendable group messages? sql = "SELECT length, m.messageId FROM messages AS m" - + " JOIN contactSubscriptions AS cs" - + " ON m.groupId = cs.groupId" - + " JOIN visibilities AS v" - + " ON m.groupId = v.groupId" - + " AND cs.contactId = v.contactId" + + " JOIN contactGroups AS cg" + + " ON m.groupId = cg.groupId" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + + " AND cg.contactId = gv.contactId" + + " JOIN contacts AS c" + + " ON cg.contactId = c.contactId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " AND cs.contactId = s.contactId" - + " JOIN subscriptionTimes AS st" - + " ON cs.contactId = st.contactId" - + " WHERE cs.contactId = ?" - + " AND timestamp >= start" + + " AND cg.contactId = s.contactId" + + " WHERE cg.contactId = ?" + " AND timestamp >= expiry" + " AND status = ?" + " AND sendability > ZERO()" @@ -1676,8 +1633,7 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT groupId, groupName, groupKey" - + " FROM subscriptions"; + String sql = "SELECT groupId, name, key FROM groups"; ps = txn.prepareStatement(sql); rs = ps.executeQuery(); List<Group> subs = new ArrayList<Group>(); @@ -1702,8 +1658,7 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT groupId, groupName, groupKey" - + " FROM contactSubscriptions" + String sql = "SELECT groupId, name, key FROM contactGroups" + " WHERE contactId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -1725,125 +1680,178 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public long getTransportsModified(Connection txn) throws DbException { + public SubscriptionAck getSubscriptionAck(Connection txn, ContactId c) + throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT DISTINCT modified FROM transportTimestamps"; + String sql = "SELECT remoteVersion FROM groupVersions" + + " WHERE contactId = ? AND remoteAcked = FALSE"; ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbException(); - long modified = rs.getLong(1); - if(rs.next()) throw new DbException(); + if(!rs.next()) { + rs.close(); + ps.close(); + return null; + } + long version = rs.getLong(1); + if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - return modified; + sql = "UPDATE groupVersions SET remoteAcked = TRUE" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps.close(); + return new SubscriptionAck(version); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); + tryToClose(rs); throw new DbException(e); } } - public long getTransportsSent(Connection txn, ContactId c) + public SubscriptionUpdate getSubscriptionUpdate(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT sent FROM transportTimestamps" - + " WHERE contactId = ?"; + String sql = "SELECT g.groupId, name, key, localVersion" + + " FROM groups AS g" + + " JOIN groupVisibilities as gv" + + " ON g.groupId = gv.groupId" + + " JOIN groupVersions AS v" + + " ON gv.contactId = v.contactId" + + " WHERE gv.contactId = ?" + + " AND localVersion > localAcked"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbException(); - long sent = rs.getLong(1); - if(rs.next()) throw new DbException(); + List<Group> subs = new ArrayList<Group>(); + long version = 0L; + while(rs.next()) { + byte[] id = rs.getBytes(1); + String name = rs.getString(2); + byte[] key = rs.getBytes(3); + version = rs.getLong(4); + subs.add(new Group(new GroupId(id), name, key)); + } rs.close(); ps.close(); - return sent; + if(subs.isEmpty()) return null; + subs = Collections.unmodifiableList(subs); + return new SubscriptionUpdate(subs, version); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); + tryToClose(rs); throw new DbException(e); } } - public Map<GroupId, Integer> getUnreadMessageCounts(Connection txn) - throws DbException { + public Collection<TransportAck> getTransportAcks(Connection txn, + ContactId c) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT groupId, COUNT(*)" - + " FROM messages AS m" - + " LEFT OUTER JOIN flags AS f" - + " ON m.messageId = f.messageId" - + " WHERE (NOT read) OR (read IS NULL)" - + " GROUP BY groupId"; + String sql = "SELECT transportId, remoteVersion" + + " FROM contactTransportVersions" + + " WHERE contactId = ? AND remoteAcked = FALSE"; ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); rs = ps.executeQuery(); - Map<GroupId, Integer> counts = new HashMap<GroupId, Integer>(); + List<TransportAck> acks = new ArrayList<TransportAck>(); while(rs.next()) { - GroupId g = new GroupId(rs.getBytes(1)); - counts.put(g, rs.getInt(2)); + TransportId id = new TransportId(rs.getBytes(1)); + acks.add(new TransportAck(id, rs.getLong(2))); } rs.close(); ps.close(); - return Collections.unmodifiableMap(counts); + if(acks.isEmpty()) return null; + sql = "UPDATE contactTransportVersions SET remoteAcked = TRUE" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + for(TransportAck a : acks) { + ps.setBytes(2, a.getId().getBytes()); + ps.addBatch(); + } + int[] affectedBatch = ps.executeBatch(); + if(affectedBatch.length != acks.size()) + throw new DbStateException(); + for(int i = 0; i < affectedBatch.length; i++) { + if(affectedBatch[i] < 1) throw new DbStateException(); + } + ps.close(); + return Collections.unmodifiableList(acks); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); + tryToClose(rs); throw new DbException(e); } } - public Collection<ContactId> getVisibility(Connection txn, GroupId g) - throws DbException { + public Collection<TransportUpdate> getTransportUpdates(Connection txn, + ContactId c) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT contactId FROM visibilities WHERE groupId = ?"; + String sql = "SELECT transportId, key, value, localVersion" + + " FROM transportProperties AS tp" + + " JOIN transportVersions as tv" + + " ON tp.transportId = tv.transportId" + + " WHERE tv.contactId = ?" + + " AND localVersion > localAcked"; ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); + ps.setInt(1, c.getInt()); rs = ps.executeQuery(); - List<ContactId> visible = new ArrayList<ContactId>(); - while(rs.next()) visible.add(new ContactId(rs.getInt(1))); + List<TransportUpdate> updates = new ArrayList<TransportUpdate>(); + TransportId lastId = null; + TransportProperties p = null; + while(rs.next()) { + TransportId id = new TransportId(rs.getBytes(1)); + String key = rs.getString(2), value = rs.getString(3); + long version = rs.getLong(4); + if(!id.equals(lastId)) { + p = new TransportProperties(); + updates.add(new TransportUpdate(id, p, version)); + } + p.put(key, value); + } rs.close(); ps.close(); - return Collections.unmodifiableList(visible); + if(updates.isEmpty()) return null; + return Collections.unmodifiableList(updates); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); + tryToClose(rs); throw new DbException(e); } } - public Map<GroupId, GroupId> getVisibleHoles(Connection txn, ContactId c, - long timestamp) throws DbException { + public Map<GroupId, Integer> getUnreadMessageCounts(Connection txn) + throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT groupId, nextId FROM visibilities AS v" - + " JOIN subscriptionTimes AS st" - + " ON v.contactId = st.contactId" - + " WHERE v.contactId = ?" - + " AND deleted > acked AND deleted < ?"; + String sql = "SELECT groupId, COUNT(*)" + + " FROM messages AS m" + + " LEFT OUTER JOIN flags AS f" + + " ON m.messageId = f.messageId" + + " WHERE (NOT read) OR (read IS NULL)" + + " GROUP BY groupId"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setLong(2, timestamp); rs = ps.executeQuery(); - Map<GroupId, GroupId> holes = null; + Map<GroupId, Integer> counts = new HashMap<GroupId, Integer>(); while(rs.next()) { - byte[] b = rs.getBytes(1); - GroupId groupId = b == null ? null : new GroupId(b); - b = rs.getBytes(2); - GroupId nextId = b == null ? null : new GroupId(b); - if(holes == null) holes = new HashMap<GroupId, GroupId>(); - holes.put(groupId, nextId); + GroupId g = new GroupId(rs.getBytes(1)); + counts.put(g, rs.getInt(2)); } rs.close(); ps.close(); - if(holes == null) return Collections.emptyMap(); - return Collections.unmodifiableMap(holes); + return Collections.unmodifiableMap(counts); } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1851,36 +1859,21 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Map<Group, Long> getVisibleSubscriptions(Connection txn, ContactId c, - long timestamp) throws DbException { + public Collection<ContactId> getVisibility(Connection txn, GroupId g) + throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT s.groupId, groupName, groupKey, start" - + " FROM subscriptions AS s" - + " JOIN visibilities AS v" - + " ON s.groupId = v.groupId" - + " JOIN subscriptionTimes AS st" - + " ON v.contactId = st.contactId" - + " WHERE v.contactId = ?" - + " AND start > acked AND start < ?"; + String sql = "SELECT contactId FROM groupVisibilities" + + " WHERE groupId = ?"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setLong(2, timestamp); + ps.setBytes(1, g.getBytes()); rs = ps.executeQuery(); - Map<Group, Long> subs = null; - while(rs.next()) { - GroupId id = new GroupId(rs.getBytes(1)); - String name = rs.getString(2); - byte[] publicKey = rs.getBytes(3); - long start = rs.getLong(4); - if(subs == null) subs = new HashMap<Group, Long>(); - subs.put(new Group(id, name, publicKey), start); - } + List<ContactId> visible = new ArrayList<ContactId>(); + while(rs.next()) visible.add(new ContactId(rs.getInt(1))); rs.close(); ps.close(); - if(subs == null) return Collections.emptyMap(); - return Collections.unmodifiableMap(subs); + return Collections.unmodifiableList(visible); } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1911,18 +1904,17 @@ abstract class JdbcDatabase implements Database<Connection> { if(found) return true; // Do we have any sendable group messages? sql = "SELECT m.messageId FROM messages AS m" - + " JOIN contactSubscriptions AS cs" - + " ON m.groupId = cs.groupId" - + " JOIN visibilities AS v" - + " ON m.groupId = v.groupId" - + " AND cs.contactId = v.contactId" + + " JOIN contactGroups AS cg" + + " ON m.groupId = cg.groupId" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + + " AND cg.contactId = gv.contactId" + + " JOIN contacts AS c" + + " ON cg.contactId = c.contactId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " AND cs.contactId = s.contactId" - + " JOIN subscriptionTimes AS st" - + " ON cs.contactId = st.contactId" - + " WHERE cs.contactId = ?" - + " AND timestamp >= start" + + " AND cg.contactId = s.contactId" + + " WHERE cg.contactId = ?" + " AND timestamp >= expiry" + " AND status = ?" + " AND sendability > ZERO()" @@ -1967,12 +1959,13 @@ abstract class JdbcDatabase implements Database<Connection> { rs.close(); ps.close(); // Increment the connection counter - sql = "UPDATE secrets SET outgoing = outgoing + 1" + sql = "UPDATE secrets SET outgoing = outgoing + ?" + " WHERE contactId = ? AND transportId = ? AND period = ?"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, t.getBytes()); - ps.setLong(3, period); + ps.setInt(1, 1); + ps.setInt(2, c.getInt()); + ps.setBytes(3, t.getBytes()); + ps.setLong(4, period); int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); @@ -2070,83 +2063,59 @@ abstract class JdbcDatabase implements Database<Connection> { public void removeSubscription(Connection txn, GroupId g) throws DbException { - PreparedStatement ps = null, ps1 = null; + PreparedStatement ps = null; ResultSet rs = null; try { - // Remove the group ID from the visibility lists - long now = clock.currentTimeMillis(); - String sql = "SELECT contactId, nextId FROM visibilities" + // Find out which contacts are affected + String sql = "SELECT contactId FROM groupVisibilities" + " WHERE groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); rs = ps.executeQuery(); - while(rs.next()) { - int contactId = rs.getInt(1); - byte[] nextId = rs.getBytes(2); - sql = "UPDATE visibilities SET nextId = ?, deleted = ?" - + " WHERE contactId = ? AND nextId = ?"; - ps1 = txn.prepareStatement(sql); - if(nextId == null) ps1.setNull(1, BINARY); // At the tail - else ps1.setBytes(1, nextId); // At the head or in the middle - ps1.setLong(2, now); - ps1.setInt(3, contactId); - ps1.setBytes(4, g.getBytes()); - int affected = ps1.executeUpdate(); - if(affected != 1) throw new DbStateException(); - ps1.close(); - } + Collection<Integer> visible = new ArrayList<Integer>(); + while(rs.next()) visible.add(rs.getInt(1)); rs.close(); ps.close(); - // Remove the group from the subscriptions table - sql = "DELETE FROM subscriptions WHERE groupId = ?"; + // Delete the group + sql = "DELETE FROM groups WHERE groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); + if(visible.isEmpty()) return; + // Bump the subscription version for the affected contacts + sql = "UPDATE groupVersions SET localVersion = localVersion + ?" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, 1); + for(Integer c : visible) { + ps.setInt(2, c); + ps.addBatch(); + } + int[] affectedBatch = ps.executeBatch(); + if(affectedBatch.length != visible.size()) + throw new DbStateException(); + for(int i = 0; i < affectedBatch.length; i++) { + if(affectedBatch[i] != 1) throw new DbStateException(); + } + ps.close(); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); - tryToClose(ps1); + tryToClose(rs); throw new DbException(e); } } - public void removeSubscriptions(Connection txn, ContactId c, GroupId start, - GroupId end) throws DbException { + public void removeTransport(Connection txn, TransportId t) + throws DbException { PreparedStatement ps = null; try { - if(start == null && end == null) { - // Delete everything - String sql = "DELETE FROM contactSubscriptions" - + " WHERE contactId = ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - } else if(start == null) { - // Delete everything before end - String sql = "DELETE FROM contactSubscriptions" - + " WHERE contactId = ? AND groupId < ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, end.getBytes()); - } else if(end == null) { - // Delete everything after start - String sql = "DELETE FROM contactSubscriptions" - + " WHERE contactId = ? AND groupId > ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, start.getBytes()); - } else { - // Delete everything between start and end - String sql = "DELETE FROM contactSubscriptions" - + " WHERE contactId = ?" - + " AND groupId > ? AND groupId < ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, start.getBytes()); - ps.setBytes(3, end.getBytes()); - } - ps.executeUpdate(); + String sql = "DELETE FROM transports WHERE transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, t.getBytes()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); ps.close(); } catch(SQLException e) { tryToClose(ps); @@ -2157,21 +2126,8 @@ abstract class JdbcDatabase implements Database<Connection> { public void removeVisibility(Connection txn, ContactId c, GroupId g) throws DbException { PreparedStatement ps = null; - ResultSet rs = null; try { - // Remove the group ID from the linked list - String sql = "SELECT nextId FROM visibilities" - + " WHERE contactId = ? AND groupId = ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getBytes()); - rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - byte[] nextId = rs.getBytes(1); - if(rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - sql = "DELETE FROM visibilities" + String sql = "DELETE FROM groupVisibilities" + " WHERE contactId = ? AND groupId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -2179,19 +2135,16 @@ abstract class JdbcDatabase implements Database<Connection> { int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); - sql = "UPDATE visibilities SET nextId = ?, deleted = ?" - + " WHERE contactId = ? AND nextId = ?"; + // Bump the subscription version + sql = "UPDATE groupVersions SET localVersion = localVersion + ?" + + " WHERE contactId = ?"; ps = txn.prepareStatement(sql); - if(nextId == null) ps.setNull(1, BINARY); // At the tail - else ps.setBytes(1, nextId); // At the head or in the middle - ps.setLong(2, clock.currentTimeMillis()); - ps.setInt(3, c.getInt()); - ps.setBytes(4, g.getBytes()); + ps.setInt(1, 1); + ps.setInt(2, c.getInt()); affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); throw new DbException(e); } @@ -2199,12 +2152,29 @@ abstract class JdbcDatabase implements Database<Connection> { public void mergeConfig(Connection txn, TransportId t, TransportConfig c) throws DbException { + // Merge the new configuration with the existing one mergeStringMap(txn, t, c, "transportConfigs"); } public void mergeLocalProperties(Connection txn, TransportId t, TransportProperties p) throws DbException { + // Merge the new properties with the existing ones mergeStringMap(txn, t, p, "transportProperties"); + // Bump the transport version + PreparedStatement ps = null; + try { + String sql = "UPDATE transportVersions" + + " SET localVersion = localVersion + ?" + + " WHERE transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, 1); + ps.setBytes(2, t.getBytes()); + ps.executeUpdate(); + ps.close(); + } catch(SQLException e) { + tryToClose(ps); + throw new DbException(e); + } } private void mergeStringMap(Connection txn, TransportId t, @@ -2278,7 +2248,7 @@ abstract class JdbcDatabase implements Database<Connection> { throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE subscriptionTimes SET expiry = ?" + String sql = "UPDATE contacts SET expiry = ?" + " WHERE contactId = ?"; ps = txn.prepareStatement(sql); ps.setLong(1, expiry); @@ -2391,6 +2361,85 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void setRemoteProperties(Connection txn, ContactId c, + TransportUpdate t) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + // Find the existing version, if any + String sql = "SELECT remoteVersion FROM contactTransportVersions" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, t.getId().getBytes()); + rs = ps.executeQuery(); + long version = rs.next() ? rs.getLong(1) : -1L; + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + // Mark the update as needing to be acked + if(version == -1L) { + // The row doesn't exist - create it + sql = "INSERT INTO contactTransportVersions (contactId," + + " transportId, remoteVersion, remoteAcked)" + + " VALUES (?, ?, ?, FALSE)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, t.getId().getBytes()); + ps.setLong(3, t.getVersionNumber()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps.close(); + } else { + // The row exists - update it + sql = "UPDATE contactTransportVersions" + + " SET remoteVersion = ?, remoteAcked = FALSE" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setLong(1, Math.max(version, t.getVersionNumber())); + ps.setInt(1, c.getInt()); + ps.setBytes(2, t.getId().getBytes()); + int affected = ps.executeUpdate(); + if(affected > 1) throw new DbStateException(); + ps.close(); + // Return if the update is obsolete + if(t.getVersionNumber() <= version) return; + } + // Delete the existing properties, if any + sql = "DELETE FROM contactTransportProperties" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, t.getId().getBytes()); + ps.executeUpdate(); + ps.close(); + // Store the new properties, if any + TransportProperties p = t.getProperties(); + if(p.isEmpty()) return; + sql = "INSERT INTO contactTransportProperties" + + " (contactId, transportId, key, value)" + + " VALUES (?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, t.getId().getBytes()); + for(Entry<String, String> e : p.entrySet()) { + ps.setString(1, e.getKey()); + ps.setString(2, e.getValue()); + ps.addBatch(); + } + int[] batchAffected = ps.executeBatch(); + if(batchAffected.length != p.size()) throw new DbStateException(); + for(int i = 0; i < batchAffected.length; i++) { + if(batchAffected[i] != 1) throw new DbStateException(); + } + ps.close(); + } catch(SQLException e) { + tryToClose(ps); + tryToClose(rs); + throw new DbException(e); + } + } + public void setSendability(Connection txn, MessageId m, int sendability) throws DbException { PreparedStatement ps = null; @@ -2514,16 +2563,15 @@ abstract class JdbcDatabase implements Database<Connection> { ResultSet rs = null; try { String sql = "SELECT NULL FROM messages AS m" - + " JOIN contactSubscriptions AS cs" - + " ON m.groupId = cs.groupId" - + " JOIN visibilities AS v" - + " ON m.groupId = v.groupId" - + " AND cs.contactId = v.contactId" - + " JOIN subscriptionTimes AS st" - + " ON cs.contactId = st.contactId" + + " JOIN contactGroups AS cg" + + " ON m.groupId = cg.groupId" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + + " AND cg.contactId = gv.contactId" + + " JOIN contacts AS c" + + " ON cg.contactId = c.contactId" + " WHERE messageId = ?" - + " AND cs.contactId = ?" - + " AND timestamp >= start" + + " AND cg.contactId = ?" + " AND timestamp >= expiry"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); @@ -2551,112 +2599,78 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setSubscriptionsAcked(Connection txn, ContactId c, - long timestamp) throws DbException { - PreparedStatement ps = null; - try { - String sql = "UPDATE subscriptionTimes SET acked = ?" - + " WHERE contactId = ?"; - ps = txn.prepareStatement(sql); - ps.setLong(1, timestamp); - ps.setInt(2, c.getInt()); - int affected = ps.executeUpdate(); - if(affected > 1) throw new DbStateException(); - ps.close(); - } catch(SQLException e) { - tryToClose(ps); - throw new DbException(e); - } - } - - public void setSubscriptionsReceived(Connection txn, ContactId c, - long timestamp) throws DbException { - PreparedStatement ps = null; - try { - String sql = "UPDATE subscriptionTimes SET received = ?" - + " WHERE contactId = ?"; - ps = txn.prepareStatement(sql); - ps.setLong(1, timestamp); - ps.setInt(2, c.getInt()); - int affected = ps.executeUpdate(); - if(affected > 1) throw new DbStateException(); - ps.close(); - } catch(SQLException e) { - tryToClose(ps); - throw new DbException(e); - } - } - - public void setTransports(Connection txn, ContactId c, - Collection<Transport> transports, long timestamp) - throws DbException { + public void setSubscriptions(Connection txn, ContactId c, + SubscriptionUpdate s) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - // Return if the timestamp isn't fresh - String sql = "SELECT received FROM transportTimestamps" + // Find the existing version + String sql = "SELECT remoteVersion FROM groupVersions" + " WHERE contactId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); rs = ps.executeQuery(); if(!rs.next()) throw new DbStateException(); - long lastTimestamp = rs.getLong(1); + long version = rs.getLong(1); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - if(lastTimestamp >= timestamp) return; - // Delete any existing transport properties - sql = "DELETE FROM contactTransportProperties WHERE contactId = ?"; + // Mark the update as needing to be acked + sql = "UPDATE groupVersions" + + " SET remoteVersion = ?, remoteAcked = FALSE" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setLong(1, Math.max(version, s.getVersionNumber())); + ps.setInt(2, c.getInt()); + int affected = ps.executeUpdate(); + if(affected > 1) throw new DbStateException(); + ps.close(); + // Delete the existing subscriptions, if any + sql = "DELETE FROM contactGroups WHERE contactId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); ps.executeUpdate(); - ps.close(); - // Store the new transport properties - sql = "INSERT INTO contactTransportProperties" - + " (contactId, transportId, key, value)" + // Store the new subscriptions, if any + Collection<Group> subs = s.getGroups(); + if(subs.isEmpty()) return; + sql = "INSERT INTO contactGroups (contactId, groupId, name, key)" + " VALUES (?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - int batchSize = 0; - for(Transport t : transports) { - ps.setBytes(2, t.getId().getBytes()); - for(Entry<String, String> e1 : t.getProperties().entrySet()) { - ps.setString(3, e1.getKey()); - ps.setString(4, e1.getValue()); - ps.addBatch(); - batchSize++; - } + for(Group g : subs) { + ps.setBytes(2, g.getId().getBytes()); + ps.setString(3, g.getName()); + byte[] key = g.getPublicKey(); + if(key == null) ps.setNull(4, BINARY); + else ps.setBytes(4, key); + ps.addBatch(); } - int[] batchAffected = ps.executeBatch(); - if(batchAffected.length != batchSize) throw new DbStateException(); - for(int i = 0; i < batchAffected.length; i++) { - if(batchAffected[i] != 1) throw new DbStateException(); + int[] affectedBatch = ps.executeBatch(); + if(affectedBatch.length != subs.size()) + throw new DbStateException(); + for(int i = 0; i < affectedBatch.length; i++) { + if(affectedBatch[i] != 1) throw new DbStateException(); } ps.close(); - // Update the timestamp - sql = "UPDATE transportTimestamps SET received = ?" - + " WHERE contactId = ?"; - ps = txn.prepareStatement(sql); - ps.setLong(1, timestamp); - ps.setInt(2, c.getInt()); - int affected = ps.executeUpdate(); - if(affected != 1) throw new DbStateException(); - ps.close(); } catch(SQLException e) { - tryToClose(rs); tryToClose(ps); + tryToClose(rs); throw new DbException(e); } } - public void setTransportsModified(Connection txn, long timestamp) - throws DbException { + public void setSubscriptionUpdateAcked(Connection txn, ContactId c, + long version) throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE transportTimestamps set modified = ?"; + String sql = "UPDATE groupVersions SET localAcked = ?" + + " WHERE contactId = ? AND localAcked < ?"; ps = txn.prepareStatement(sql); - ps.setLong(1, timestamp); - ps.executeUpdate(); + ps.setLong(1, version); + ps.setInt(2, c.getInt()); + ps.setLong(3, version); + int affected = ps.executeUpdate(); + if(affected > 1) throw new DbStateException(); ps.close(); } catch(SQLException e) { tryToClose(ps); @@ -2664,16 +2678,18 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setTransportsSent(Connection txn, ContactId c, long timestamp) - throws DbException { + public void setTransportUpdateAcked(Connection txn, ContactId c, + TransportId t, long version) throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE transportTimestamps SET sent = ?" - + " WHERE contactId = ? AND sent < ?"; + String sql = "UPDATE transportVersions SET localAcked = ?" + + " WHERE contactId = ? AND transportId = ?" + + " AND localAcked < ?"; ps = txn.prepareStatement(sql); - ps.setLong(1, timestamp); + ps.setLong(1, version); ps.setInt(2, c.getInt()); - ps.setLong(3, timestamp); + ps.setBytes(3, t.getBytes()); + ps.setLong(4, version); int affected = ps.executeUpdate(); if(affected > 1) throw new DbStateException(); ps.close(); diff --git a/briar-core/src/net/sf/briar/invitation/Connector.java b/briar-core/src/net/sf/briar/invitation/Connector.java index 658b3cd401..5ec5f0520c 100644 --- a/briar-core/src/net/sf/briar/invitation/Connector.java +++ b/briar-core/src/net/sf/briar/invitation/Connector.java @@ -104,7 +104,7 @@ abstract class Connector extends Thread { protected byte[] receivePublicKeyHash(Reader r) throws IOException { byte[] b = r.readBytes(HASH_LENGTH); - if(b.length != HASH_LENGTH) throw new FormatException(); + if(b.length < HASH_LENGTH) throw new FormatException(); if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash"); return b; } diff --git a/briar-core/src/net/sf/briar/protocol/AckImpl.java b/briar-core/src/net/sf/briar/protocol/AckImpl.java deleted file mode 100644 index ea8e924284..0000000000 --- a/briar-core/src/net/sf/briar/protocol/AckImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.sf.briar.protocol; - -import java.util.Collection; - -import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.MessageId; - -class AckImpl implements Ack { - - private final Collection<MessageId> acked; - - AckImpl(Collection<MessageId> acked) { - this.acked = acked; - } - - public Collection<MessageId> getMessageIds() { - return acked; - } -} diff --git a/briar-core/src/net/sf/briar/protocol/AckReader.java b/briar-core/src/net/sf/briar/protocol/AckReader.java index b9e55fc5e8..beb073633d 100644 --- a/briar-core/src/net/sf/briar/protocol/AckReader.java +++ b/briar-core/src/net/sf/briar/protocol/AckReader.java @@ -12,7 +12,6 @@ import net.sf.briar.api.Bytes; import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.UniqueId; import net.sf.briar.api.serial.Consumer; import net.sf.briar.api.serial.CountingConsumer; @@ -21,18 +20,11 @@ import net.sf.briar.api.serial.StructReader; class AckReader implements StructReader<Ack> { - private final PacketFactory packetFactory; - - AckReader(PacketFactory packetFactory) { - this.packetFactory = packetFactory; - } - public Ack readStruct(Reader r) throws IOException { - // Initialise the consumer Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH); - // Read the data r.addConsumer(counting); r.readStructId(ACK); + // Read the message IDs as byte arrays r.setMaxBytesLength(UniqueId.LENGTH); List<Bytes> raw = r.readList(Bytes.class); r.resetMaxBytesLength(); @@ -46,6 +38,6 @@ class AckReader implements StructReader<Ack> { acked.add(new MessageId(b.getBytes())); } // Build and return the ack - return packetFactory.createAck(Collections.unmodifiableList(acked)); + return new Ack(Collections.unmodifiableList(acked)); } } diff --git a/briar-core/src/net/sf/briar/protocol/AuthorFactoryImpl.java b/briar-core/src/net/sf/briar/protocol/AuthorFactoryImpl.java index bced33cb32..41fa3d7826 100644 --- a/briar-core/src/net/sf/briar/protocol/AuthorFactoryImpl.java +++ b/briar-core/src/net/sf/briar/protocol/AuthorFactoryImpl.java @@ -27,7 +27,7 @@ class AuthorFactoryImpl implements AuthorFactory { } public Author createAuthor(String name, byte[] publicKey) - throws IOException { + throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); Writer w = writerFactory.createWriter(out); w.writeStructId(AUTHOR); @@ -36,10 +36,6 @@ class AuthorFactoryImpl implements AuthorFactory { MessageDigest messageDigest = crypto.getMessageDigest(); messageDigest.update(out.toByteArray()); AuthorId id = new AuthorId(messageDigest.digest()); - return new AuthorImpl(id, name, publicKey); - } - - public Author createAuthor(AuthorId id, String name, byte[] publicKey) { - return new AuthorImpl(id, name, publicKey); + return new Author(id, name, publicKey); } } diff --git a/briar-core/src/net/sf/briar/protocol/AuthorImpl.java b/briar-core/src/net/sf/briar/protocol/AuthorImpl.java deleted file mode 100644 index dfaf022c84..0000000000 --- a/briar-core/src/net/sf/briar/protocol/AuthorImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.sf.briar.protocol; - -import net.sf.briar.api.protocol.Author; -import net.sf.briar.api.protocol.AuthorId; - -class AuthorImpl implements Author { - - private final AuthorId id; - private final String name; - private final byte[] publicKey; - - AuthorImpl(AuthorId id, String name, byte[] publicKey) { - this.id = id; - this.name = name; - this.publicKey = publicKey; - } - - public AuthorId getId() { - return id; - } - - public String getName() { - return name; - } - - public byte[] getPublicKey() { - return publicKey; - } -} diff --git a/briar-core/src/net/sf/briar/protocol/AuthorReader.java b/briar-core/src/net/sf/briar/protocol/AuthorReader.java index 9bc177f728..d8d392503d 100644 --- a/briar-core/src/net/sf/briar/protocol/AuthorReader.java +++ b/briar-core/src/net/sf/briar/protocol/AuthorReader.java @@ -9,7 +9,6 @@ import java.io.IOException; import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.MessageDigest; import net.sf.briar.api.protocol.Author; -import net.sf.briar.api.protocol.AuthorFactory; import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.serial.DigestingConsumer; import net.sf.briar.api.serial.Reader; @@ -18,15 +17,12 @@ import net.sf.briar.api.serial.StructReader; class AuthorReader implements StructReader<Author> { private final MessageDigest messageDigest; - private final AuthorFactory authorFactory; - AuthorReader(CryptoComponent crypto, AuthorFactory authorFactory) { + AuthorReader(CryptoComponent crypto) { messageDigest = crypto.getMessageDigest(); - this.authorFactory = authorFactory; } public Author readStruct(Reader r) throws IOException { - // Initialise the consumer DigestingConsumer digesting = new DigestingConsumer(messageDigest); // Read and digest the data r.addConsumer(digesting); @@ -36,6 +32,6 @@ class AuthorReader implements StructReader<Author> { r.removeConsumer(digesting); // Build and return the author AuthorId id = new AuthorId(messageDigest.digest()); - return authorFactory.createAuthor(id, name, publicKey); + return new Author(id, name, publicKey); } } diff --git a/briar-core/src/net/sf/briar/protocol/GroupReader.java b/briar-core/src/net/sf/briar/protocol/GroupReader.java index af1fcfc3c5..3cfc9d908c 100644 --- a/briar-core/src/net/sf/briar/protocol/GroupReader.java +++ b/briar-core/src/net/sf/briar/protocol/GroupReader.java @@ -23,7 +23,6 @@ class GroupReader implements StructReader<Group> { } public Group readStruct(Reader r) throws IOException { - // Initialise the consumer DigestingConsumer digesting = new DigestingConsumer(messageDigest); // Read and digest the data r.addConsumer(digesting); diff --git a/briar-core/src/net/sf/briar/protocol/MessageReader.java b/briar-core/src/net/sf/briar/protocol/MessageReader.java index 9b2f801a1f..f86ae4dfa9 100644 --- a/briar-core/src/net/sf/briar/protocol/MessageReader.java +++ b/briar-core/src/net/sf/briar/protocol/MessageReader.java @@ -46,7 +46,7 @@ class MessageReader implements StructReader<UnverifiedMessage> { r.readNull(); } else { byte[] b = r.readBytes(UniqueId.LENGTH); - if(b.length != UniqueId.LENGTH) throw new FormatException(); + if(b.length < UniqueId.LENGTH) throw new FormatException(); parent = new MessageId(b); } // Read the group, if there is one @@ -74,7 +74,7 @@ class MessageReader implements StructReader<UnverifiedMessage> { if(timestamp < 0L) throw new FormatException(); // Read the salt byte[] salt = r.readBytes(SALT_LENGTH); - if(salt.length != SALT_LENGTH) throw new FormatException(); + if(salt.length < SALT_LENGTH) throw new FormatException(); // Read the message body byte[] body = r.readBytes(MAX_BODY_LENGTH); // Record the offset of the body within the message diff --git a/briar-core/src/net/sf/briar/protocol/OfferImpl.java b/briar-core/src/net/sf/briar/protocol/OfferImpl.java deleted file mode 100644 index de892202e6..0000000000 --- a/briar-core/src/net/sf/briar/protocol/OfferImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.sf.briar.protocol; - -import java.util.Collection; - -import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.Offer; - -class OfferImpl implements Offer { - - private final Collection<MessageId> offered; - - OfferImpl(Collection<MessageId> offered) { - this.offered = offered; - } - - public Collection<MessageId> getMessageIds() { - return offered; - } -} diff --git a/briar-core/src/net/sf/briar/protocol/OfferReader.java b/briar-core/src/net/sf/briar/protocol/OfferReader.java index 32c0bc5bf8..62a8d665a8 100644 --- a/briar-core/src/net/sf/briar/protocol/OfferReader.java +++ b/briar-core/src/net/sf/briar/protocol/OfferReader.java @@ -12,7 +12,6 @@ import net.sf.briar.api.Bytes; import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.UniqueId; import net.sf.briar.api.serial.Consumer; import net.sf.briar.api.serial.CountingConsumer; @@ -21,18 +20,11 @@ import net.sf.briar.api.serial.StructReader; class OfferReader implements StructReader<Offer> { - private final PacketFactory packetFactory; - - OfferReader(PacketFactory packetFactory) { - this.packetFactory = packetFactory; - } - public Offer readStruct(Reader r) throws IOException { - // Initialise the consumer Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH); - // Read the data r.addConsumer(counting); r.readStructId(OFFER); + // Read the message IDs as byte arrays r.setMaxBytesLength(UniqueId.LENGTH); List<Bytes> raw = r.readList(Bytes.class); r.resetMaxBytesLength(); @@ -46,7 +38,6 @@ class OfferReader implements StructReader<Offer> { messages.add(new MessageId(b.getBytes())); } // Build and return the offer - return packetFactory.createOffer(Collections.unmodifiableList( - messages)); + return new Offer(Collections.unmodifiableList(messages)); } } diff --git a/briar-core/src/net/sf/briar/protocol/PacketFactoryImpl.java b/briar-core/src/net/sf/briar/protocol/PacketFactoryImpl.java deleted file mode 100644 index a8e9e32c20..0000000000 --- a/briar-core/src/net/sf/briar/protocol/PacketFactoryImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.sf.briar.protocol; - -import java.util.BitSet; -import java.util.Collection; -import java.util.Map; - -import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.Group; -import net.sf.briar.api.protocol.GroupId; -import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; -import net.sf.briar.api.protocol.Request; -import net.sf.briar.api.protocol.SubscriptionUpdate; -import net.sf.briar.api.protocol.Transport; -import net.sf.briar.api.protocol.TransportUpdate; - -class PacketFactoryImpl implements PacketFactory { - - public Ack createAck(Collection<MessageId> acked) { - return new AckImpl(acked); - } - - public Offer createOffer(Collection<MessageId> offered) { - return new OfferImpl(offered); - } - - public Request createRequest(BitSet requested, int length) { - return new RequestImpl(requested, length); - } - - public SubscriptionUpdate createSubscriptionUpdate( - Map<GroupId, GroupId> holes, Map<Group, Long> subs, long expiry, - long timestamp) { - return new SubscriptionUpdateImpl(holes, subs, expiry, timestamp); - } - - public TransportUpdate createTransportUpdate( - Collection<Transport> transports, long timestamp) { - return new TransportUpdateImpl(transports, timestamp); - } -} diff --git a/briar-core/src/net/sf/briar/protocol/ProtocolModule.java b/briar-core/src/net/sf/briar/protocol/ProtocolModule.java index 075f4158b9..dfce9b987c 100644 --- a/briar-core/src/net/sf/briar/protocol/ProtocolModule.java +++ b/briar-core/src/net/sf/briar/protocol/ProtocolModule.java @@ -11,11 +11,12 @@ import net.sf.briar.api.protocol.GroupFactory; import net.sf.briar.api.protocol.MessageFactory; import net.sf.briar.api.protocol.MessageVerifier; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.ProtocolReaderFactory; import net.sf.briar.api.protocol.ProtocolWriterFactory; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.SubscriptionAck; import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.protocol.UnverifiedMessage; import net.sf.briar.api.protocol.VerificationExecutor; @@ -48,7 +49,6 @@ public class ProtocolModule extends AbstractModule { bind(GroupFactory.class).to(GroupFactoryImpl.class); bind(MessageFactory.class).to(MessageFactoryImpl.class); bind(MessageVerifier.class).to(MessageVerifierImpl.class); - bind(PacketFactory.class).to(PacketFactoryImpl.class); bind(ProtocolReaderFactory.class).to(ProtocolReaderFactoryImpl.class); bind(ProtocolWriterFactory.class).to(ProtocolWriterFactoryImpl.class); // The executor is bounded, so tasks must be independent and short-lived @@ -59,14 +59,13 @@ public class ProtocolModule extends AbstractModule { } @Provides - StructReader<Ack> getAckReader(PacketFactory ackFactory) { - return new AckReader(ackFactory); + StructReader<Ack> getAckReader() { + return new AckReader(); } @Provides - StructReader<Author> getAuthorReader(CryptoComponent crypto, - AuthorFactory authorFactory) { - return new AuthorReader(crypto, authorFactory); + StructReader<Author> getAuthorReader(CryptoComponent crypto) { + return new AuthorReader(crypto); } @Provides @@ -82,24 +81,33 @@ public class ProtocolModule extends AbstractModule { } @Provides - StructReader<Offer> getOfferReader(PacketFactory packetFactory) { - return new OfferReader(packetFactory); + StructReader<Offer> getOfferReader() { + return new OfferReader(); } @Provides - StructReader<Request> getRequestReader(PacketFactory packetFactory) { - return new RequestReader(packetFactory); + StructReader<Request> getRequestReader() { + return new RequestReader(); } @Provides - StructReader<SubscriptionUpdate> getSubscriptionReader( - StructReader<Group> groupReader, PacketFactory packetFactory) { - return new SubscriptionUpdateReader(groupReader, packetFactory); + StructReader<SubscriptionAck> getSubscriptionAckReader() { + return new SubscriptionAckReader(); } @Provides - StructReader<TransportUpdate> getTransportReader( - PacketFactory packetFactory) { - return new TransportUpdateReader(packetFactory); + StructReader<SubscriptionUpdate> getSubscriptionUpdateReader( + StructReader<Group> groupReader) { + return new SubscriptionUpdateReader(groupReader); + } + + @Provides + StructReader<TransportAck> getTransportAckReader() { + return new TransportAckReader(); + } + + @Provides + StructReader<TransportUpdate> getTransportUpdateReader() { + return new TransportUpdateReader(); } } diff --git a/briar-core/src/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java b/briar-core/src/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java index 3ac7649d52..fc42c841da 100644 --- a/briar-core/src/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java +++ b/briar-core/src/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java @@ -7,7 +7,9 @@ import net.sf.briar.api.protocol.Offer; import net.sf.briar.api.protocol.ProtocolReader; import net.sf.briar.api.protocol.ProtocolReaderFactory; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.SubscriptionAck; import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.protocol.UnverifiedMessage; import net.sf.briar.api.serial.ReaderFactory; @@ -16,6 +18,7 @@ import net.sf.briar.api.serial.StructReader; import com.google.inject.Inject; import com.google.inject.Provider; +// FIXME: Refactor this package to reduce boilerplate class ProtocolReaderFactoryImpl implements ProtocolReaderFactory { private final ReaderFactory readerFactory; @@ -23,8 +26,10 @@ class ProtocolReaderFactoryImpl implements ProtocolReaderFactory { private final Provider<StructReader<UnverifiedMessage>> messageProvider; private final Provider<StructReader<Offer>> offerProvider; private final Provider<StructReader<Request>> requestProvider; - private final Provider<StructReader<SubscriptionUpdate>> subscriptionProvider; - private final Provider<StructReader<TransportUpdate>> transportProvider; + private final Provider<StructReader<SubscriptionAck>> subscriptionAckProvider; + private final Provider<StructReader<SubscriptionUpdate>> subscriptionUpdateProvider; + private final Provider<StructReader<TransportAck>> transportAckProvider; + private final Provider<StructReader<TransportUpdate>> transportUpdateProvider; @Inject ProtocolReaderFactoryImpl(ReaderFactory readerFactory, @@ -32,21 +37,26 @@ class ProtocolReaderFactoryImpl implements ProtocolReaderFactory { Provider<StructReader<UnverifiedMessage>> messageProvider, Provider<StructReader<Offer>> offerProvider, Provider<StructReader<Request>> requestProvider, - Provider<StructReader<SubscriptionUpdate>> subscriptionProvider, - Provider<StructReader<TransportUpdate>> transportProvider) { + Provider<StructReader<SubscriptionAck>> subscriptionAckProvider, + Provider<StructReader<SubscriptionUpdate>> subscriptionUpdateProvider, + Provider<StructReader<TransportAck>> transportAckProvider, + Provider<StructReader<TransportUpdate>> transportUpdateProvider) { this.readerFactory = readerFactory; this.ackProvider = ackProvider; this.messageProvider = messageProvider; this.offerProvider = offerProvider; this.requestProvider = requestProvider; - this.subscriptionProvider = subscriptionProvider; - this.transportProvider = transportProvider; + this.subscriptionAckProvider = subscriptionAckProvider; + this.subscriptionUpdateProvider = subscriptionUpdateProvider; + this.transportAckProvider = transportAckProvider; + this.transportUpdateProvider = transportUpdateProvider; } public ProtocolReader createProtocolReader(InputStream in) { return new ProtocolReaderImpl(in, readerFactory, ackProvider.get(), messageProvider.get(), offerProvider.get(), - requestProvider.get(), subscriptionProvider.get(), - transportProvider.get()); + requestProvider.get(), subscriptionAckProvider.get(), + subscriptionUpdateProvider.get(), transportAckProvider.get(), + transportUpdateProvider.get()); } } diff --git a/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java b/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java index 9d5850b159..b13cfe8adf 100644 --- a/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java +++ b/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java @@ -4,7 +4,9 @@ import static net.sf.briar.api.protocol.Types.ACK; import static net.sf.briar.api.protocol.Types.MESSAGE; import static net.sf.briar.api.protocol.Types.OFFER; import static net.sf.briar.api.protocol.Types.REQUEST; +import static net.sf.briar.api.protocol.Types.SUBSCRIPTION_ACK; import static net.sf.briar.api.protocol.Types.SUBSCRIPTION_UPDATE; +import static net.sf.briar.api.protocol.Types.TRANSPORT_ACK; import static net.sf.briar.api.protocol.Types.TRANSPORT_UPDATE; import java.io.IOException; @@ -14,7 +16,9 @@ import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.Offer; import net.sf.briar.api.protocol.ProtocolReader; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.SubscriptionAck; import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.protocol.UnverifiedMessage; import net.sf.briar.api.serial.Reader; @@ -30,15 +34,19 @@ class ProtocolReaderImpl implements ProtocolReader { StructReader<UnverifiedMessage> messageReader, StructReader<Offer> offerReader, StructReader<Request> requestReader, - StructReader<SubscriptionUpdate> subscriptionReader, - StructReader<TransportUpdate> transportReader) { + StructReader<SubscriptionAck> subscriptionAckReader, + StructReader<SubscriptionUpdate> subscriptionUpdateReader, + StructReader<TransportAck> transportAckReader, + StructReader<TransportUpdate> transportUpdateReader) { reader = readerFactory.createReader(in); reader.addStructReader(ACK, ackReader); reader.addStructReader(MESSAGE, messageReader); reader.addStructReader(OFFER, offerReader); reader.addStructReader(REQUEST, requestReader); - reader.addStructReader(SUBSCRIPTION_UPDATE, subscriptionReader); - reader.addStructReader(TRANSPORT_UPDATE, transportReader); + reader.addStructReader(SUBSCRIPTION_ACK, subscriptionAckReader); + reader.addStructReader(SUBSCRIPTION_UPDATE, subscriptionUpdateReader); + reader.addStructReader(TRANSPORT_ACK, transportAckReader); + reader.addStructReader(TRANSPORT_UPDATE, transportUpdateReader); } public boolean eof() throws IOException { @@ -77,6 +85,14 @@ class ProtocolReaderImpl implements ProtocolReader { return reader.readStruct(REQUEST, Request.class); } + public boolean hasSubscriptionAck() throws IOException { + return reader.hasStruct(SUBSCRIPTION_ACK); + } + + public SubscriptionAck readSubscriptionAck() throws IOException { + return reader.readStruct(SUBSCRIPTION_ACK, SubscriptionAck.class); + } + public boolean hasSubscriptionUpdate() throws IOException { return reader.hasStruct(SUBSCRIPTION_UPDATE); } @@ -86,6 +102,14 @@ class ProtocolReaderImpl implements ProtocolReader { SubscriptionUpdate.class); } + public boolean hasTransportAck() throws IOException { + return reader.hasStruct(TRANSPORT_ACK); + } + + public TransportAck readTransportAck() throws IOException { + return reader.readStruct(TRANSPORT_ACK, TransportAck.class); + } + public boolean hasTransportUpdate() throws IOException { return reader.hasStruct(TRANSPORT_UPDATE); } diff --git a/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java b/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java index 87ddf36eb8..633ec17d88 100644 --- a/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java +++ b/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java @@ -5,24 +5,24 @@ import static net.sf.briar.api.protocol.Types.ACK; import static net.sf.briar.api.protocol.Types.GROUP; import static net.sf.briar.api.protocol.Types.OFFER; import static net.sf.briar.api.protocol.Types.REQUEST; +import static net.sf.briar.api.protocol.Types.SUBSCRIPTION_ACK; import static net.sf.briar.api.protocol.Types.SUBSCRIPTION_UPDATE; -import static net.sf.briar.api.protocol.Types.TRANSPORT; +import static net.sf.briar.api.protocol.Types.TRANSPORT_ACK; import static net.sf.briar.api.protocol.Types.TRANSPORT_UPDATE; import java.io.IOException; import java.io.OutputStream; import java.util.BitSet; -import java.util.Map.Entry; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.Group; -import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; import net.sf.briar.api.protocol.ProtocolWriter; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.SubscriptionAck; import net.sf.briar.api.protocol.SubscriptionUpdate; -import net.sf.briar.api.protocol.Transport; +import net.sf.briar.api.protocol.TransportAck; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.serial.SerialComponent; import net.sf.briar.api.serial.Writer; @@ -103,48 +103,40 @@ class ProtocolWriterImpl implements ProtocolWriter { if(flush) out.flush(); } + public void writeSubscriptionAck(SubscriptionAck a) throws IOException { + w.writeStructId(SUBSCRIPTION_ACK); + w.writeInt64(a.getVersionNumber()); + if(flush) out.flush(); + } + public void writeSubscriptionUpdate(SubscriptionUpdate s) throws IOException { w.writeStructId(SUBSCRIPTION_UPDATE); - // Holes - w.writeMapStart(); - for(Entry<GroupId, GroupId> e : s.getHoles().entrySet()) { - w.writeBytes(e.getKey().getBytes()); - w.writeBytes(e.getValue().getBytes()); - } - w.writeMapEnd(); - // Subscriptions - w.writeMapStart(); - for(Entry<Group, Long> e : s.getSubscriptions().entrySet()) { - writeGroup(w, e.getKey()); - w.writeInt64(e.getValue()); + w.writeListStart(); + for(Group g : s.getGroups()) { + w.writeStructId(GROUP); + w.writeString(g.getName()); + byte[] publicKey = g.getPublicKey(); + if(publicKey == null) w.writeNull(); + else w.writeBytes(publicKey); } - w.writeMapEnd(); - // Expiry time - w.writeInt64(s.getExpiryTime()); - // Timestamp - w.writeInt64(s.getTimestamp()); + w.writeListEnd(); + w.writeInt64(s.getVersionNumber()); if(flush) out.flush(); } - private void writeGroup(Writer w, Group g) throws IOException { - w.writeStructId(GROUP); - w.writeString(g.getName()); - byte[] publicKey = g.getPublicKey(); - if(publicKey == null) w.writeNull(); - else w.writeBytes(publicKey); + public void writeTransportAck(TransportAck a) throws IOException { + w.writeStructId(TRANSPORT_ACK); + w.writeBytes(a.getId().getBytes()); + w.writeInt64(a.getVersionNumber()); + if(flush) out.flush(); } public void writeTransportUpdate(TransportUpdate t) throws IOException { w.writeStructId(TRANSPORT_UPDATE); - w.writeListStart(); - for(Transport p : t.getTransports()) { - w.writeStructId(TRANSPORT); - w.writeBytes(p.getId().getBytes()); - w.writeMap(p.getProperties()); - } - w.writeListEnd(); - w.writeInt64(t.getTimestamp()); + w.writeBytes(t.getId().getBytes()); + w.writeMap(t.getProperties()); + w.writeInt64(t.getVersionNumber()); if(flush) out.flush(); } diff --git a/briar-core/src/net/sf/briar/protocol/RequestImpl.java b/briar-core/src/net/sf/briar/protocol/RequestImpl.java deleted file mode 100644 index aeae2cd285..0000000000 --- a/briar-core/src/net/sf/briar/protocol/RequestImpl.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.sf.briar.protocol; - -import java.util.BitSet; - -import net.sf.briar.api.protocol.Request; - -class RequestImpl implements Request { - - private final BitSet requested; - private final int length; - - RequestImpl(BitSet requested, int length) { - this.requested = requested; - this.length = length; - } - - public BitSet getBitmap() { - return requested; - } - - public int getLength() { - return length; - } -} diff --git a/briar-core/src/net/sf/briar/protocol/RequestReader.java b/briar-core/src/net/sf/briar/protocol/RequestReader.java index 0514ddc485..8bac06ec77 100644 --- a/briar-core/src/net/sf/briar/protocol/RequestReader.java +++ b/briar-core/src/net/sf/briar/protocol/RequestReader.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.util.BitSet; import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.Request; import net.sf.briar.api.serial.Consumer; import net.sf.briar.api.serial.CountingConsumer; @@ -16,20 +15,14 @@ import net.sf.briar.api.serial.StructReader; class RequestReader implements StructReader<Request> { - private final PacketFactory packetFactory; - - RequestReader(PacketFactory packetFactory) { - this.packetFactory = packetFactory; - } - public Request readStruct(Reader r) throws IOException { - // Initialise the consumer Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH); - // Read the data r.addConsumer(counting); r.readStructId(REQUEST); + // There may be up to 7 bits of padding at the end of the bitmap int padding = r.readUint7(); if(padding > 7) throw new FormatException(); + // Read the bitmap byte[] bitmap = r.readBytes(MAX_PACKET_LENGTH); r.removeConsumer(counting); // Convert the bitmap into a BitSet @@ -41,6 +34,6 @@ class RequestReader implements StructReader<Request> { if((bitmap[i] & bit) != 0) b.set(i * 8 + j); } } - return packetFactory.createRequest(b, length); + return new Request(b, length); } } diff --git a/briar-core/src/net/sf/briar/protocol/SubscriptionAckReader.java b/briar-core/src/net/sf/briar/protocol/SubscriptionAckReader.java new file mode 100644 index 0000000000..648fbc7ec1 --- /dev/null +++ b/briar-core/src/net/sf/briar/protocol/SubscriptionAckReader.java @@ -0,0 +1,20 @@ +package net.sf.briar.protocol; + +import static net.sf.briar.api.protocol.Types.SUBSCRIPTION_ACK; + +import java.io.IOException; + +import net.sf.briar.api.FormatException; +import net.sf.briar.api.protocol.SubscriptionAck; +import net.sf.briar.api.serial.Reader; +import net.sf.briar.api.serial.StructReader; + +class SubscriptionAckReader implements StructReader<SubscriptionAck> { + + public SubscriptionAck readStruct(Reader r) throws IOException { + r.readStructId(SUBSCRIPTION_ACK); + long version = r.readInt64(); + if(version < 0L) throw new FormatException(); + return new SubscriptionAck(version); + } +} diff --git a/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateImpl.java b/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateImpl.java deleted file mode 100644 index a98b7fee1f..0000000000 --- a/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.sf.briar.protocol; - -import java.util.Map; - -import net.sf.briar.api.protocol.Group; -import net.sf.briar.api.protocol.GroupId; -import net.sf.briar.api.protocol.SubscriptionUpdate; - -class SubscriptionUpdateImpl implements SubscriptionUpdate { - - private final Map<GroupId, GroupId> holes; - private final Map<Group, Long> subs; - private final long expiry, timestamp; - - SubscriptionUpdateImpl(Map<GroupId, GroupId> holes, Map<Group, Long> subs, - long expiry, long timestamp) { - this.holes = holes; - this.subs = subs; - this.expiry = expiry; - this.timestamp = timestamp; - } - - public Map<GroupId, GroupId> getHoles() { - return holes; - } - - public Map<Group, Long> getSubscriptions() { - return subs; - } - - public long getExpiryTime() { - return expiry; - } - - public long getTimestamp() { - return timestamp; - } -} diff --git a/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateReader.java b/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateReader.java index e87c20c7fe..35d0e5f42e 100644 --- a/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateReader.java +++ b/briar-core/src/net/sf/briar/protocol/SubscriptionUpdateReader.java @@ -5,15 +5,12 @@ import static net.sf.briar.api.protocol.Types.GROUP; import static net.sf.briar.api.protocol.Types.SUBSCRIPTION_UPDATE; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; +import java.util.Collections; +import java.util.List; import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.Group; -import net.sf.briar.api.protocol.GroupId; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.SubscriptionUpdate; -import net.sf.briar.api.protocol.UniqueId; import net.sf.briar.api.serial.Consumer; import net.sf.briar.api.serial.CountingConsumer; import net.sf.briar.api.serial.Reader; @@ -22,46 +19,25 @@ import net.sf.briar.api.serial.StructReader; class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> { private final StructReader<Group> groupReader; - private final PacketFactory packetFactory; - SubscriptionUpdateReader(StructReader<Group> groupReader, - PacketFactory packetFactory) { + SubscriptionUpdateReader(StructReader<Group> groupReader) { this.groupReader = groupReader; - this.packetFactory = packetFactory; } public SubscriptionUpdate readStruct(Reader r) throws IOException { - // Initialise the consumer Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH); - // Read the data r.addConsumer(counting); r.readStructId(SUBSCRIPTION_UPDATE); - // Holes - Map<GroupId, GroupId> holes = new HashMap<GroupId, GroupId>(); - r.setMaxBytesLength(UniqueId.LENGTH); - r.readMapStart(); - while(!r.hasMapEnd()) { - byte[] start = r.readBytes(); - if(start.length != UniqueId.LENGTH) throw new FormatException(); - byte[] end = r.readBytes(); - if(end.length != UniqueId.LENGTH)throw new FormatException(); - holes.put(new GroupId(start), new GroupId(end)); - } - r.readMapEnd(); - r.resetMaxBytesLength(); - // Subscriptions + // Read the subscriptions r.addStructReader(GROUP, groupReader); - Map<Group, Long> subs = r.readMap(Group.class, Long.class); + List<Group> subs = r.readList(Group.class); r.removeStructReader(GROUP); - // Expiry time - long expiry = r.readInt64(); - if(expiry < 0L) throw new FormatException(); - // Timestamp - long timestamp = r.readInt64(); - if(timestamp < 0L) throw new FormatException(); + // Read the version number + long version = r.readInt64(); + if(version < 0L) throw new FormatException(); r.removeConsumer(counting); // Build and return the subscription update - return packetFactory.createSubscriptionUpdate(holes, subs, expiry, - timestamp); + subs = Collections.unmodifiableList(subs); + return new SubscriptionUpdate(subs, version); } } diff --git a/briar-core/src/net/sf/briar/protocol/TransportAckReader.java b/briar-core/src/net/sf/briar/protocol/TransportAckReader.java new file mode 100644 index 0000000000..6a7c730d45 --- /dev/null +++ b/briar-core/src/net/sf/briar/protocol/TransportAckReader.java @@ -0,0 +1,24 @@ +package net.sf.briar.protocol; + +import static net.sf.briar.api.protocol.Types.TRANSPORT_ACK; + +import java.io.IOException; + +import net.sf.briar.api.FormatException; +import net.sf.briar.api.protocol.TransportAck; +import net.sf.briar.api.protocol.TransportId; +import net.sf.briar.api.protocol.UniqueId; +import net.sf.briar.api.serial.Reader; +import net.sf.briar.api.serial.StructReader; + +class TransportAckReader implements StructReader<TransportAck> { + + public TransportAck readStruct(Reader r) throws IOException { + r.readStructId(TRANSPORT_ACK); + byte[] b = r.readBytes(UniqueId.LENGTH); + if(b.length < UniqueId.LENGTH) throw new FormatException(); + long version = r.readInt64(); + if(version < 0L) throw new FormatException(); + return new TransportAck(new TransportId(b), version); + } +} diff --git a/briar-core/src/net/sf/briar/protocol/TransportUpdateImpl.java b/briar-core/src/net/sf/briar/protocol/TransportUpdateImpl.java deleted file mode 100644 index b69a2bce77..0000000000 --- a/briar-core/src/net/sf/briar/protocol/TransportUpdateImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.sf.briar.protocol; - -import java.util.Collection; - -import net.sf.briar.api.protocol.Transport; -import net.sf.briar.api.protocol.TransportUpdate; - -class TransportUpdateImpl implements TransportUpdate { - - private final Collection<Transport> transports; - private final long timestamp; - - TransportUpdateImpl(Collection<Transport> transports, - long timestamp) { - this.transports = transports; - this.timestamp = timestamp; - } - - public Collection<Transport> getTransports() { - return transports; - } - - public long getTimestamp() { - return timestamp; - } -} diff --git a/briar-core/src/net/sf/briar/protocol/TransportUpdateReader.java b/briar-core/src/net/sf/briar/protocol/TransportUpdateReader.java index d95c7d8c1c..ee7c4e5cc6 100644 --- a/briar-core/src/net/sf/briar/protocol/TransportUpdateReader.java +++ b/briar-core/src/net/sf/briar/protocol/TransportUpdateReader.java @@ -3,19 +3,13 @@ package net.sf.briar.protocol; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_TRANSPORTS; -import static net.sf.briar.api.protocol.Types.TRANSPORT; import static net.sf.briar.api.protocol.Types.TRANSPORT_UPDATE; import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.PacketFactory; -import net.sf.briar.api.protocol.Transport; +import net.sf.briar.api.TransportProperties; import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.protocol.UniqueId; @@ -26,50 +20,25 @@ import net.sf.briar.api.serial.StructReader; class TransportUpdateReader implements StructReader<TransportUpdate> { - private final PacketFactory packetFactory; - private final StructReader<Transport> transportReader; - - TransportUpdateReader(PacketFactory packetFactory) { - this.packetFactory = packetFactory; - transportReader = new TransportReader(); - } - public TransportUpdate readStruct(Reader r) throws IOException { - // Initialise the consumer Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH); - // Read the data r.addConsumer(counting); r.readStructId(TRANSPORT_UPDATE); - r.addStructReader(TRANSPORT, transportReader); - Collection<Transport> transports = r.readList(Transport.class); - r.removeStructReader(TRANSPORT); - if(transports.size() > MAX_TRANSPORTS) throw new FormatException(); - long timestamp = r.readInt64(); + // Read the transport ID + byte[] b = r.readBytes(UniqueId.LENGTH); + if(b.length < UniqueId.LENGTH) throw new FormatException(); + TransportId id = new TransportId(b); + // Read the transport properties + r.setMaxStringLength(MAX_PROPERTY_LENGTH); + Map<String, String> m = r.readMap(String.class, String.class); + r.resetMaxStringLength(); + if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) + throw new FormatException(); + // Read the version number + long version = r.readInt64(); + if(version < 0L) throw new FormatException(); r.removeConsumer(counting); - // Check for duplicate IDs - Set<TransportId> ids = new HashSet<TransportId>(); - for(Transport t : transports) { - if(!ids.add(t.getId())) throw new FormatException(); - } // Build and return the transport update - return packetFactory.createTransportUpdate(transports, timestamp); - } - - private static class TransportReader implements StructReader<Transport> { - - public Transport readStruct(Reader r) throws IOException { - r.readStructId(TRANSPORT); - // Read the ID - byte[] b = r.readBytes(UniqueId.LENGTH); - if(b.length != UniqueId.LENGTH) throw new FormatException(); - TransportId id = new TransportId(b); - // Read the properties - r.setMaxStringLength(MAX_PROPERTY_LENGTH); - Map<String, String> m = r.readMap(String.class, String.class); - r.resetMaxStringLength(); - if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) - throw new FormatException(); - return new Transport(id, m); - } + return new TransportUpdate(id, new TransportProperties(m), version); } } diff --git a/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java index 24d084bc85..221dd00275 100644 --- a/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java +++ b/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java @@ -27,10 +27,10 @@ import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.event.ContactRemovedEvent; import net.sf.briar.api.db.event.DatabaseEvent; import net.sf.briar.api.db.event.DatabaseListener; -import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent; import net.sf.briar.api.db.event.MessageAddedEvent; import net.sf.briar.api.db.event.MessageReceivedEvent; import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent; +import net.sf.briar.api.db.event.TransportsUpdatedEvent; import net.sf.briar.api.plugins.duplex.DuplexTransportConnection; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.Message; @@ -55,6 +55,7 @@ import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.util.ByteUtils; +// FIXME: Read and write subscription and transport acks abstract class DuplexConnection implements DatabaseListener { private static final Logger LOG = @@ -132,7 +133,7 @@ abstract class DuplexConnection implements DatabaseListener { if(affected.contains(contactId)) { dbExecutor.execute(new GenerateSubscriptionUpdate()); } - } else if(e instanceof LocalTransportsUpdatedEvent) { + } else if(e instanceof TransportsUpdatedEvent) { dbExecutor.execute(new GenerateTransportUpdate()); } } @@ -556,8 +557,9 @@ abstract class DuplexConnection implements DatabaseListener { public void run() { try { - TransportUpdate t = db.generateTransportUpdate(contactId); - if(t != null) writerTasks.add(new WriteTransportUpdate(t)); + Collection<TransportUpdate> t = + db.generateTransportUpdates(contactId); + if(t != null) writerTasks.add(new WriteTransportUpdates(t)); } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } @@ -565,18 +567,18 @@ abstract class DuplexConnection implements DatabaseListener { } // This task runs on the writer thread - private class WriteTransportUpdate implements Runnable { + private class WriteTransportUpdates implements Runnable { - private final TransportUpdate update; + private final Collection<TransportUpdate> updates; - private WriteTransportUpdate(TransportUpdate update) { - this.update = update; + private WriteTransportUpdates(Collection<TransportUpdate> updates) { + this.updates = updates; } public void run() { assert writer != null; try { - writer.writeTransportUpdate(update); + for(TransportUpdate t : updates) writer.writeTransportUpdate(t); } catch(IOException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); dispose(true, true); diff --git a/briar-core/src/net/sf/briar/protocol/simplex/IncomingSimplexConnection.java b/briar-core/src/net/sf/briar/protocol/simplex/IncomingSimplexConnection.java index ffc6b8479d..e9abf0c16d 100644 --- a/briar-core/src/net/sf/briar/protocol/simplex/IncomingSimplexConnection.java +++ b/briar-core/src/net/sf/briar/protocol/simplex/IncomingSimplexConnection.java @@ -30,6 +30,7 @@ import net.sf.briar.api.transport.ConnectionReaderFactory; import net.sf.briar.api.transport.ConnectionRegistry; import net.sf.briar.util.ByteUtils; +// FIXME: Read subscription and transport acks class IncomingSimplexConnection { private static final Logger LOG = diff --git a/briar-core/src/net/sf/briar/protocol/simplex/OutgoingSimplexConnection.java b/briar-core/src/net/sf/briar/protocol/simplex/OutgoingSimplexConnection.java index e53facc463..ef9c7c9324 100644 --- a/briar-core/src/net/sf/briar/protocol/simplex/OutgoingSimplexConnection.java +++ b/briar-core/src/net/sf/briar/protocol/simplex/OutgoingSimplexConnection.java @@ -25,6 +25,7 @@ import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.util.ByteUtils; +// FIXME: Write subscription and transport acks class OutgoingSimplexConnection { private static final Logger LOG = @@ -66,15 +67,15 @@ class OutgoingSimplexConnection { // There should be enough space for a packet long capacity = conn.getRemainingCapacity(); if(capacity < MAX_PACKET_LENGTH) throw new EOFException(); - // Write a transport update - TransportUpdate t = db.generateTransportUpdate(contactId); - if(t != null) writer.writeTransportUpdate(t); - // If there's space, write a subscription update - capacity = conn.getRemainingCapacity(); - if(capacity >= MAX_PACKET_LENGTH) { - SubscriptionUpdate s = db.generateSubscriptionUpdate(contactId); - if(s != null) writer.writeSubscriptionUpdate(s); + // Write transport updates. FIXME: Check for space + Collection<TransportUpdate> updates = + db.generateTransportUpdates(contactId); + if(updates != null) { + for(TransportUpdate t : updates) writer.writeTransportUpdate(t); } + // Write a subscription update. FIXME: Check for space + SubscriptionUpdate s = db.generateSubscriptionUpdate(contactId); + if(s != null) writer.writeSubscriptionUpdate(s); // Write acks until you can't write acks no more capacity = conn.getRemainingCapacity(); int maxMessages = writer.getMaxMessagesForAck(capacity); diff --git a/briar-core/src/net/sf/briar/serial/ReaderImpl.java b/briar-core/src/net/sf/briar/serial/ReaderImpl.java index 9fa9994e46..56fab7d60b 100644 --- a/briar-core/src/net/sf/briar/serial/ReaderImpl.java +++ b/briar-core/src/net/sf/briar/serial/ReaderImpl.java @@ -98,7 +98,7 @@ class ReaderImpl implements Reader { if(!consumers.remove(c)) throw new IllegalArgumentException(); } - public void addStructReader(int id, StructReader<?> o) { + public void addStructReader(int id, StructReader<?> r) { if(id < 0 || id > 255) throw new IllegalArgumentException(); if(structReaders.length < id + 1) { int len = Math.min(256, Math.max(id + 1, structReaders.length * 2)); @@ -107,7 +107,7 @@ class ReaderImpl implements Reader { structReaders.length); structReaders = newStructReaders; } - structReaders[id] = o; + structReaders[id] = r; } public void removeStructReader(int id) { diff --git a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java index c0be507489..18802f9be4 100644 --- a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java +++ b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java @@ -12,7 +12,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Random; @@ -23,18 +22,18 @@ import net.sf.briar.api.protocol.Author; import net.sf.briar.api.protocol.AuthorFactory; import net.sf.briar.api.protocol.Group; import net.sf.briar.api.protocol.GroupFactory; -import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageFactory; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageVerifier; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.ProtocolReader; import net.sf.briar.api.protocol.ProtocolReaderFactory; import net.sf.briar.api.protocol.ProtocolWriter; import net.sf.briar.api.protocol.ProtocolWriterFactory; import net.sf.briar.api.protocol.Request; +import net.sf.briar.api.protocol.Subscription; +import net.sf.briar.api.protocol.SubscriptionHole; import net.sf.briar.api.protocol.SubscriptionUpdate; import net.sf.briar.api.protocol.Transport; import net.sf.briar.api.protocol.TransportId; @@ -66,7 +65,6 @@ public class ProtocolIntegrationTest extends BriarTestCase { private final ConnectionWriterFactory connectionWriterFactory; private final ProtocolReaderFactory protocolReaderFactory; private final ProtocolWriterFactory protocolWriterFactory; - private final PacketFactory packetFactory; private final MessageVerifier messageVerifier; private final ContactId contactId; @@ -80,7 +78,6 @@ public class ProtocolIntegrationTest extends BriarTestCase { private final String messageBody = "Hello world"; private final Collection<MessageId> messageIds; private final Collection<Transport> transports; - private final long timestamp = System.currentTimeMillis(); public ProtocolIntegrationTest() throws Exception { super(); @@ -93,7 +90,6 @@ public class ProtocolIntegrationTest extends BriarTestCase { connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class); protocolReaderFactory = i.getInstance(ProtocolReaderFactory.class); protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class); - packetFactory = i.getInstance(PacketFactory.class); messageVerifier = i.getInstance(MessageVerifier.class); contactId = new ContactId(234); transportId = new TransportId(TestUtils.getRandomId()); @@ -149,33 +145,29 @@ public class ProtocolIntegrationTest extends BriarTestCase { ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out1, false); - Ack a = packetFactory.createAck(messageIds); - writer.writeAck(a); + writer.writeAck(new Ack(messageIds)); writer.writeMessage(message.getSerialised()); writer.writeMessage(message1.getSerialised()); writer.writeMessage(message2.getSerialised()); writer.writeMessage(message3.getSerialised()); - Offer o = packetFactory.createOffer(messageIds); - writer.writeOffer(o); + writer.writeOffer(new Offer(messageIds)); BitSet requested = new BitSet(4); requested.set(1); requested.set(3); - Request r = packetFactory.createRequest(requested, 4); - writer.writeRequest(r); - - // Use a LinkedHashMap for predictable iteration order - Map<Group, Long> subs = new LinkedHashMap<Group, Long>(); - subs.put(group, 0L); - subs.put(group1, 0L); - SubscriptionUpdate s = packetFactory.createSubscriptionUpdate( - Collections.<GroupId, GroupId>emptyMap(), subs, 0L, timestamp); + writer.writeRequest(new Request(requested, 4)); + + Collection<SubscriptionHole> holes = Arrays.asList( + new SubscriptionHole(group.getId(), group1.getId())); + Collection<Subscription> subs = Arrays.asList( + new Subscription(group, 0L), new Subscription(group1, 0L)); + SubscriptionUpdate s = new SubscriptionUpdate(holes, subs, 0L, + subscriptionVersion); writer.writeSubscriptionUpdate(s); - TransportUpdate t = packetFactory.createTransportUpdate(transports, - timestamp); + TransportUpdate t = new TransportUpdate(transports, transportVersion); writer.writeTransportUpdate(t); writer.flush(); @@ -232,17 +224,12 @@ public class ProtocolIntegrationTest extends BriarTestCase { // Read the subscription update assertTrue(reader.hasSubscriptionUpdate()); SubscriptionUpdate s = reader.readSubscriptionUpdate(); - Map<Group, Long> subs = s.getSubscriptions(); - assertEquals(2, subs.size()); - assertEquals(Long.valueOf(0L), subs.get(group)); - assertEquals(Long.valueOf(0L), subs.get(group1)); - assertTrue(s.getTimestamp() == timestamp); + // FIXME: Test for equality // Read the transport update assertTrue(reader.hasTransportUpdate()); TransportUpdate t = reader.readTransportUpdate(); - assertEquals(transports, t.getTransports()); - assertTrue(t.getTimestamp() == timestamp); + // FIXME: Test for equality in.close(); } diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentImplTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentImplTest.java index 389bb46a63..3d4d367acd 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentImplTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentImplTest.java @@ -9,7 +9,6 @@ import net.sf.briar.api.clock.SystemClock; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; import net.sf.briar.api.lifecycle.ShutdownManager; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.db.DatabaseCleaner.Callback; import org.jmock.Expectations; @@ -29,13 +28,11 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner, shutdown, - packetFactory); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -49,7 +46,6 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -62,8 +58,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner, shutdown, - packetFactory); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -72,13 +67,12 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @Test public void testExpiringUnsendableMessageDoesNotTriggerBackwardInclusion() - throws DbException { + throws DbException { Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -93,8 +87,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner, shutdown, - packetFactory); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -103,13 +96,12 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @Test public void testExpiringSendableMessageTriggersBackwardInclusion() - throws DbException { + throws DbException { Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -126,8 +118,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner, shutdown, - packetFactory); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -137,15 +128,14 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @Override protected <T> DatabaseComponent createDatabaseComponent( Database<T> database, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory) { - return createDatabaseComponentImpl(database, cleaner, shutdown, - packetFactory); + ShutdownManager shutdown) { + return createDatabaseComponentImpl(database, cleaner, shutdown); } private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( Database<T> database, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory) { + ShutdownManager shutdown) { return new DatabaseComponentImpl<T>(database, cleaner, shutdown, - packetFactory, new SystemClock()); + new SystemClock()); } } diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index 4e03a03411..298503a0c7 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -1,6 +1,5 @@ package net.sf.briar.db; -import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; @@ -29,7 +28,6 @@ import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.Request; import net.sf.briar.api.protocol.SubscriptionUpdate; import net.sf.briar.api.protocol.Transport; @@ -89,7 +87,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { protected abstract <T> DatabaseComponent createDatabaseComponent( Database<T> database, DatabaseCleaner cleaner, - ShutdownManager shutdown, PacketFactory packetFactory); + ShutdownManager shutdown); @Test @SuppressWarnings("unchecked") @@ -99,7 +97,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -167,7 +164,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).close(); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.open(false); db.addListener(listener); @@ -198,7 +195,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // setRating(authorId, Rating.GOOD) allowing(database).startTransaction(); @@ -217,7 +213,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.setRating(authorId, Rating.GOOD); @@ -231,7 +227,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // setRating(authorId, Rating.GOOD) oneOf(database).startTransaction(); @@ -254,7 +249,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.setRating(authorId, Rating.GOOD); @@ -269,7 +264,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // setRating(authorId, Rating.GOOD) oneOf(database).startTransaction(); @@ -295,7 +289,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.setRating(authorId, Rating.GOOD); @@ -310,7 +304,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -320,7 +313,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addLocalGroupMessage(message); @@ -334,7 +327,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -346,7 +338,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addLocalGroupMessage(message); @@ -360,7 +352,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -381,7 +372,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addLocalGroupMessage(message); @@ -396,7 +387,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -420,7 +410,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addLocalGroupMessage(message); @@ -434,7 +424,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -446,7 +435,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(false)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addLocalPrivateMessage(privateMessage, contactId); @@ -460,7 +449,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -473,7 +461,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addLocalPrivateMessage(privateMessage, contactId); @@ -488,7 +476,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final Ack ack = context.mock(Ack.class); final Offer offer = context.mock(Offer.class); final SubscriptionUpdate subscriptionUpdate = @@ -504,7 +491,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { exactly(16).of(database).abortTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); try { db.addContactTransport(contactTransport); @@ -598,7 +585,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // Check whether the contact transport is in the DB (which it's not) exactly(2).of(database).startTransaction(); @@ -609,7 +595,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { exactly(2).of(database).abortTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); try { db.incrementConnectionCounter(contactId, transportId, 0L); @@ -633,8 +619,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); - final Ack ack = context.mock(Ack.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -644,16 +628,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // Get the messages to ack oneOf(database).getMessagesToAck(txn, contactId, 123); will(returnValue(messagesToAck)); - // Create the ack packet - oneOf(packetFactory).createAck(messagesToAck); - will(returnValue(ack)); // Record the messages that were acked oneOf(database).removeMessagesToAck(txn, contactId, messagesToAck); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); - assertEquals(ack, db.generateAck(contactId, 123)); + Ack a = db.generateAck(contactId, 123); + assertEquals(messagesToAck, a.getMessageIds()); context.assertIsSatisfied(); } @@ -669,7 +651,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -687,7 +668,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addOutstandingMessages(txn, contactId, sendable); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); assertEquals(messages, db.generateBatch(contactId, size * 2)); @@ -698,17 +679,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase { public void testGenerateBatchFromRequest() throws Exception { final MessageId messageId2 = new MessageId(TestUtils.getRandomId()); final byte[] raw1 = new byte[size]; - final Collection<MessageId> requested = new ArrayList<MessageId>(); - requested.add(messageId); - requested.add(messageId1); - requested.add(messageId2); + final Collection<MessageId> requested = Arrays.asList(messageId, + messageId1, messageId2); final Collection<byte[]> messages = Arrays.asList(raw1); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -727,7 +705,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { Collections.singletonList(messageId1)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); assertEquals(messages, db.generateBatch(contactId, size * 3, requested)); @@ -738,16 +716,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase { @Test public void testGenerateOffer() throws Exception { final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - final Collection<MessageId> offerable = new ArrayList<MessageId>(); - offerable.add(messageId); - offerable.add(messageId1); + final Collection<MessageId> messagesToOffer = Arrays.asList(messageId, + messageId1); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); - final Offer offer = context.mock(Offer.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -756,15 +731,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(true)); // Get the sendable message IDs oneOf(database).getMessagesToOffer(txn, contactId, 123); - will(returnValue(offerable)); - // Create the packet - oneOf(packetFactory).createOffer(offerable); - will(returnValue(offer)); + will(returnValue(messagesToOffer)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); - assertEquals(offer, db.generateOffer(contactId, 123)); + Offer o = db.generateOffer(contactId, 123); + assertEquals(messagesToOffer, o.getMessageIds()); context.assertIsSatisfied(); } @@ -776,9 +749,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); - final SubscriptionUpdate subscriptionUpdate = - context.mock(SubscriptionUpdate.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -786,55 +756,39 @@ public abstract class DatabaseComponentTest extends BriarTestCase { allowing(database).containsContact(txn, contactId); will(returnValue(true)); // Get the visible holes and subscriptions - oneOf(database).getVisibleHoles(with(txn), with(contactId), - with(any(long.class))); - will(returnValue(Collections.emptyMap())); - oneOf(database).getVisibleSubscriptions(with(txn), with(contactId), - with(any(long.class))); - will(returnValue(Collections.singletonMap(group, 0L))); + oneOf(database).getVisibleHoles(txn, contactId); + will(returnValue(Collections.emptyMap())); // FIXME + oneOf(database).getVisibleSubscriptions(txn, contactId); + will(returnValue(Collections.singletonMap(group, 0L))); // FIXME // Get the expiry time oneOf(database).getExpiryTime(txn); will(returnValue(0L)); - // Create the packet - oneOf(packetFactory).createSubscriptionUpdate( - with(Collections.<GroupId, GroupId>emptyMap()), - with(Collections.singletonMap(group, 0L)), - with(any(long.class)), - with(any(long.class))); - will(returnValue(subscriptionUpdate)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); - assertEquals(subscriptionUpdate, - db.generateSubscriptionUpdate(contactId)); + SubscriptionUpdate s = db.generateSubscriptionUpdate(contactId); + // FIXME: Check that the update contains the expected data context.assertIsSatisfied(); } @Test public void testTransportUpdateNotSentUnlessDue() throws Exception { - final long now = System.currentTimeMillis(); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); allowing(database).commitTransaction(txn); allowing(database).containsContact(txn, contactId); will(returnValue(true)); - // Check whether an update is due - oneOf(database).getTransportsModified(txn); - will(returnValue(now - 1L)); - oneOf(database).getTransportsSent(txn, contactId); - will(returnValue(now)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); assertNull(db.generateTransportUpdate(contactId)); @@ -848,34 +802,21 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); - final TransportUpdate transportUpdate = - context.mock(TransportUpdate.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); allowing(database).commitTransaction(txn); allowing(database).containsContact(txn, contactId); will(returnValue(true)); - // Check whether an update is due - oneOf(database).getTransportsModified(txn); - will(returnValue(0L)); - oneOf(database).getTransportsSent(txn, contactId); - will(returnValue(0L)); // Get the local transport properties - oneOf(database).getLocalTransports(txn); - will(returnValue(transports)); - oneOf(database).setTransportsSent(with(txn), with(contactId), - with(any(long.class))); - // Create the packet - oneOf(packetFactory).createTransportUpdate(with(transports), - with(any(long.class))); - will(returnValue(transportUpdate)); + oneOf(database).getTransports(txn); + will(returnValue(transports)); // FIXME }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); - assertEquals(transportUpdate, db.generateTransportUpdate(contactId)); + TransportUpdate t = db.generateTransportUpdate(contactId); + // FIXME: Check that the update contains the expected data context.assertIsSatisfied(); } @@ -887,8 +828,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); - final Ack ack = context.mock(Ack.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -896,15 +835,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase { allowing(database).containsContact(txn, contactId); will(returnValue(true)); // Get the acked messages - oneOf(ack).getMessageIds(); - will(returnValue(Collections.singletonList(messageId))); oneOf(database).removeOutstandingMessages(txn, contactId, Collections.singletonList(messageId)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); - db.receiveAck(contactId, ack); + db.receiveAck(contactId, new Ack(Collections.singletonList(messageId))); context.assertIsSatisfied(); } @@ -916,7 +853,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -931,7 +867,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addMessageToAck(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveMessage(contactId, privateMessage); @@ -945,7 +881,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -959,7 +894,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addMessageToAck(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveMessage(contactId, privateMessage); @@ -974,7 +909,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -989,7 +923,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addMessageToAck(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveMessage(contactId, message); @@ -1004,7 +938,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1023,7 +956,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addMessageToAck(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveMessage(contactId, message); @@ -1037,7 +970,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1065,7 +997,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addMessageToAck(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveMessage(contactId, message); @@ -1080,7 +1012,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1110,7 +1041,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).addMessageToAck(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveMessage(contactId, message); @@ -1121,10 +1052,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { public void testReceiveOffer() throws Exception { final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); final MessageId messageId2 = new MessageId(TestUtils.getRandomId()); - final Collection<MessageId> offered = new ArrayList<MessageId>(); - offered.add(messageId); - offered.add(messageId1); - offered.add(messageId2); + final Collection<MessageId> offered = Arrays.asList(messageId, + messageId1, messageId2); final BitSet expectedRequest = new BitSet(3); expectedRequest.set(0); expectedRequest.set(2); @@ -1133,9 +1062,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final Offer offer = context.mock(Offer.class); - final Request request = context.mock(Request.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1151,14 +1078,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(true)); // Visible - do not request message # 1 oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2); will(returnValue(false)); // Not visible - request message # 2 - // Create the packet - oneOf(packetFactory).createRequest(expectedRequest, 3); - will(returnValue(request)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); - assertEquals(request, db.receiveOffer(contactId, offer)); + Request r = db.receiveOffer(contactId, offer); + assertEquals(expectedRequest, r.getBitmap()); + assertEquals(3, r.getLength()); context.assertIsSatisfied(); } @@ -1167,15 +1093,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase { public void testReceiveSubscriptionUpdate() throws Exception { final GroupId start = new GroupId(TestUtils.getRandomId()); final GroupId end = new GroupId(TestUtils.getRandomId()); - final long expiry = 1234L, timestamp = 5678L; + final long expiry = 1234L; Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final SubscriptionUpdate subscriptionUpdate = - context.mock(SubscriptionUpdate.class); + context.mock(SubscriptionUpdate.class); // FIXME: Don't mock context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1184,21 +1109,18 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(true)); // Get the contents of the update oneOf(subscriptionUpdate).getHoles(); - will(returnValue(Collections.singletonMap(start, end))); - oneOf(subscriptionUpdate).getSubscriptions(); - will(returnValue(Collections.singletonMap(group, 0L))); + will(returnValue(Collections.singletonMap(start, end))); // FIXME + oneOf(subscriptionUpdate).getGroupIds(); + will(returnValue(Collections.singletonMap(group, 0L))); // FIXME oneOf(subscriptionUpdate).getExpiryTime(); will(returnValue(expiry)); - oneOf(subscriptionUpdate).getTimestamp(); - will(returnValue(timestamp)); // Store the contents of the update oneOf(database).removeSubscriptions(txn, contactId, start, end); oneOf(database).addSubscription(txn, contactId, group, 0L); oneOf(database).setExpiryTime(txn, contactId, expiry); - oneOf(database).setSubscriptionsReceived(txn, contactId, timestamp); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveSubscriptionUpdate(contactId, subscriptionUpdate); @@ -1207,15 +1129,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase { @Test public void testReceiveTransportUpdate() throws Exception { - final long timestamp = 1234L; Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final TransportUpdate transportUpdate = - context.mock(TransportUpdate.class); + context.mock(TransportUpdate.class); // FIXME: Don't mock context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1225,13 +1145,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // Get the contents of the update oneOf(transportUpdate).getTransports(); will(returnValue(transports)); - oneOf(transportUpdate).getTimestamp(); - will(returnValue(timestamp)); - oneOf(database).setTransports(txn, contactId, transports, - timestamp); + oneOf(database).setTransports(txn, contactId, transports); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.receiveTransportUpdate(contactId, transportUpdate); @@ -1245,7 +1162,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) @@ -1268,7 +1184,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(listener).eventOccurred(with(any(MessageAddedEvent.class))); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.addLocalGroupMessage(message); @@ -1283,7 +1199,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1299,7 +1214,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(listener).eventOccurred(with(any(MessageAddedEvent.class))); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.addLocalPrivateMessage(privateMessage, contactId); @@ -1315,7 +1230,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) @@ -1329,7 +1243,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // The message was not added, so the listener should not be called }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.addLocalGroupMessage(message); @@ -1345,7 +1259,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1359,7 +1272,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // The message was not added, so the listener should not be called }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.addLocalPrivateMessage(privateMessage, contactId); @@ -1377,19 +1290,16 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ oneOf(database).startTransaction(); will(returnValue(txn)); oneOf(database).getLocalProperties(txn, transportId); will(returnValue(new TransportProperties())); oneOf(database).mergeLocalProperties(txn, transportId, properties); - oneOf(database).setTransportsModified(with(txn), - with(any(long.class))); oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.mergeLocalProperties(transportId, properties); @@ -1406,7 +1316,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ oneOf(database).startTransaction(); @@ -1416,7 +1325,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.mergeLocalProperties(transportId, properties); @@ -1431,7 +1340,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1442,7 +1350,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.setSeen(contactId, Collections.singletonList(messageId)); @@ -1458,7 +1366,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ oneOf(database).startTransaction(); @@ -1473,7 +1380,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { SubscriptionsUpdatedEvent.class))); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.setVisibility(groupId, Collections.singletonList(contactId)); @@ -1490,7 +1397,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ oneOf(database).startTransaction(); @@ -1502,7 +1408,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addListener(listener); db.setVisibility(groupId, both); @@ -1517,7 +1423,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); - final PacketFactory packetFactory = context.mock(PacketFactory.class); context.checking(new Expectations() {{ // addSecrets() oneOf(database).startTransaction(); @@ -1536,7 +1441,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - shutdown, packetFactory); + shutdown); db.addSecrets(Collections.singletonList(temporarySecret)); assertEquals(Collections.singletonList(temporarySecret), diff --git a/briar-tests/src/net/sf/briar/protocol/AckReaderTest.java b/briar-tests/src/net/sf/briar/protocol/AckReaderTest.java index d99bc010fc..82f792ab2e 100644 --- a/briar-tests/src/net/sf/briar/protocol/AckReaderTest.java +++ b/briar-tests/src/net/sf/briar/protocol/AckReaderTest.java @@ -5,13 +5,11 @@ import static net.sf.briar.api.protocol.Types.ACK; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.util.Collection; import net.sf.briar.BriarTestCase; import net.sf.briar.TestUtils; import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.ReaderFactory; import net.sf.briar.api.serial.SerialComponent; @@ -19,8 +17,6 @@ import net.sf.briar.api.serial.Writer; import net.sf.briar.api.serial.WriterFactory; import net.sf.briar.serial.SerialModule; -import org.jmock.Expectations; -import org.jmock.Mockery; import org.junit.Test; import com.google.inject.Guice; @@ -33,7 +29,6 @@ public class AckReaderTest extends BriarTestCase { private final SerialComponent serial; private final ReaderFactory readerFactory; private final WriterFactory writerFactory; - private final Mockery context; public AckReaderTest() throws Exception { super(); @@ -41,61 +36,39 @@ public class AckReaderTest extends BriarTestCase { serial = i.getInstance(SerialComponent.class); readerFactory = i.getInstance(ReaderFactory.class); writerFactory = i.getInstance(WriterFactory.class); - context = new Mockery(); } @Test public void testFormatExceptionIfAckIsTooLarge() throws Exception { - PacketFactory packetFactory = context.mock(PacketFactory.class); - AckReader ackReader = new AckReader(packetFactory); - byte[] b = createAck(true); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(ACK, ackReader); - + reader.addStructReader(ACK, new AckReader()); try { reader.readStruct(ACK, Ack.class); fail(); } catch(FormatException expected) {} - context.assertIsSatisfied(); } @Test - @SuppressWarnings("unchecked") public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - AckReader ackReader = new AckReader(packetFactory); - final Ack ack = context.mock(Ack.class); - context.checking(new Expectations() {{ - oneOf(packetFactory).createAck(with(any(Collection.class))); - will(returnValue(ack)); - }}); - byte[] b = createAck(false); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(ACK, ackReader); - - assertEquals(ack, reader.readStruct(ACK, Ack.class)); - context.assertIsSatisfied(); + reader.addStructReader(ACK, new AckReader()); + reader.readStruct(ACK, Ack.class); } @Test public void testEmptyAck() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - AckReader ackReader = new AckReader(packetFactory); - byte[] b = createEmptyAck(); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(ACK, ackReader); - + reader.addStructReader(ACK, new AckReader()); try { reader.readStruct(ACK, Ack.class); fail(); } catch(FormatException expected) {} - context.assertIsSatisfied(); } private byte[] createAck(boolean tooBig) throws Exception { diff --git a/briar-tests/src/net/sf/briar/protocol/ConstantsTest.java b/briar-tests/src/net/sf/briar/protocol/ConstantsTest.java index 94543fb5f7..3b06c4b49a 100644 --- a/briar-tests/src/net/sf/briar/protocol/ConstantsTest.java +++ b/briar-tests/src/net/sf/briar/protocol/ConstantsTest.java @@ -4,17 +4,13 @@ import static net.sf.briar.api.protocol.ProtocolConstants.MAX_AUTHOR_NAME_LENGTH import static net.sf.briar.api.protocol.ProtocolConstants.MAX_BODY_LENGTH; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_GROUP_NAME_LENGTH; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PUBLIC_KEY_LENGTH; import static net.sf.briar.api.protocol.ProtocolConstants.MAX_SUBJECT_LENGTH; -import static net.sf.briar.api.protocol.ProtocolConstants.MAX_TRANSPORTS; import java.io.ByteArrayOutputStream; import java.security.PrivateKey; import java.util.ArrayList; import java.util.Collection; -import java.util.Map; import net.sf.briar.BriarTestCase; import net.sf.briar.TestUtils; @@ -28,12 +24,8 @@ import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageFactory; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.ProtocolWriter; import net.sf.briar.api.protocol.ProtocolWriterFactory; -import net.sf.briar.api.protocol.Transport; -import net.sf.briar.api.protocol.TransportId; -import net.sf.briar.api.protocol.TransportUpdate; import net.sf.briar.api.protocol.UniqueId; import net.sf.briar.clock.ClockModule; import net.sf.briar.crypto.CryptoModule; @@ -50,7 +42,6 @@ public class ConstantsTest extends BriarTestCase { private final GroupFactory groupFactory; private final AuthorFactory authorFactory; private final MessageFactory messageFactory; - private final PacketFactory packetFactory; private final ProtocolWriterFactory protocolWriterFactory; public ConstantsTest() throws Exception { @@ -61,7 +52,6 @@ public class ConstantsTest extends BriarTestCase { groupFactory = i.getInstance(GroupFactory.class); authorFactory = i.getInstance(AuthorFactory.class); messageFactory = i.getInstance(MessageFactory.class); - packetFactory = i.getInstance(PacketFactory.class); protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class); } @@ -85,8 +75,7 @@ public class ConstantsTest extends BriarTestCase { for(int i = 0; i < maxMessages; i++) { acked.add(new MessageId(TestUtils.getRandomId())); } - Ack a = packetFactory.createAck(acked); - writer.writeAck(a); + writer.writeAck(new Ack(acked)); // Check the size of the serialised ack assertTrue(out.size() <= length); } @@ -136,43 +125,11 @@ public class ConstantsTest extends BriarTestCase { for(int i = 0; i < maxMessages; i++) { offered.add(new MessageId(TestUtils.getRandomId())); } - Offer o = packetFactory.createOffer(offered); - writer.writeOffer(o); + writer.writeOffer(new Offer(offered)); // Check the size of the serialised offer assertTrue(out.size() <= length); } - @Test - public void testTransportsFitIntoUpdate() throws Exception { - // Create the maximum number of plugins, each with the maximum number - // of maximum-length properties - Collection<Transport> transports = new ArrayList<Transport>(); - for(int i = 0; i < MAX_TRANSPORTS; i++) { - TransportId id = new TransportId(TestUtils.getRandomId()); - Transport t = new Transport(id); - Map<String, String> m = t.getProperties(); - for(int j = 0; j < MAX_PROPERTIES_PER_TRANSPORT; j++) { - String key = createRandomString(MAX_PROPERTY_LENGTH); - String value = createRandomString(MAX_PROPERTY_LENGTH); - m.put(key, value); - } - transports.add(t); - } - // Add the transports to an update - ByteArrayOutputStream out = - new ByteArrayOutputStream(MAX_PACKET_LENGTH); - ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out, - true); - TransportUpdate t = packetFactory.createTransportUpdate(transports, - Long.MAX_VALUE); - writer.writeTransportUpdate(t); - // Check the size of the serialised update - assertTrue(out.size() > MAX_TRANSPORTS * (UniqueId.LENGTH + 4 - + (MAX_PROPERTIES_PER_TRANSPORT * MAX_PROPERTY_LENGTH * 2)) - + 8); - assertTrue(out.size() <= MAX_PACKET_LENGTH); - } - private static String createRandomString(int length) throws Exception { StringBuilder s = new StringBuilder(length); for(int i = 0; i < length; i++) { diff --git a/briar-tests/src/net/sf/briar/protocol/OfferReaderTest.java b/briar-tests/src/net/sf/briar/protocol/OfferReaderTest.java index 7b89cc8d06..e10da58b0d 100644 --- a/briar-tests/src/net/sf/briar/protocol/OfferReaderTest.java +++ b/briar-tests/src/net/sf/briar/protocol/OfferReaderTest.java @@ -5,13 +5,11 @@ import static net.sf.briar.api.protocol.Types.OFFER; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.util.Collection; import net.sf.briar.BriarTestCase; import net.sf.briar.TestUtils; import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.ReaderFactory; import net.sf.briar.api.serial.SerialComponent; @@ -19,8 +17,6 @@ import net.sf.briar.api.serial.Writer; import net.sf.briar.api.serial.WriterFactory; import net.sf.briar.serial.SerialModule; -import org.jmock.Expectations; -import org.jmock.Mockery; import org.junit.Test; import com.google.inject.Guice; @@ -33,7 +29,6 @@ public class OfferReaderTest extends BriarTestCase { private final SerialComponent serial; private final ReaderFactory readerFactory; private final WriterFactory writerFactory; - private final Mockery context; public OfferReaderTest() throws Exception { super(); @@ -41,61 +36,39 @@ public class OfferReaderTest extends BriarTestCase { serial = i.getInstance(SerialComponent.class); readerFactory = i.getInstance(ReaderFactory.class); writerFactory = i.getInstance(WriterFactory.class); - context = new Mockery(); } @Test public void testFormatExceptionIfOfferIsTooLarge() throws Exception { - PacketFactory packetFactory = context.mock(PacketFactory.class); - OfferReader offerReader = new OfferReader(packetFactory); - byte[] b = createOffer(true); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(OFFER, offerReader); - + reader.addStructReader(OFFER, new OfferReader()); try { reader.readStruct(OFFER, Offer.class); fail(); } catch(FormatException expected) {} - context.assertIsSatisfied(); } @Test - @SuppressWarnings("unchecked") public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - OfferReader offerReader = new OfferReader(packetFactory); - final Offer offer = context.mock(Offer.class); - context.checking(new Expectations() {{ - oneOf(packetFactory).createOffer(with(any(Collection.class))); - will(returnValue(offer)); - }}); - byte[] b = createOffer(false); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(OFFER, offerReader); - - assertEquals(offer, reader.readStruct(OFFER, Offer.class)); - context.assertIsSatisfied(); + reader.addStructReader(OFFER, new OfferReader()); + reader.readStruct(OFFER, Offer.class); } @Test public void testEmptyOffer() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - OfferReader offerReader = new OfferReader(packetFactory); - byte[] b = createEmptyOffer(); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(OFFER, offerReader); - + reader.addStructReader(OFFER, new OfferReader()); try { reader.readStruct(OFFER, Offer.class); fail(); } catch(FormatException expected) {} - context.assertIsSatisfied(); } private byte[] createOffer(boolean tooBig) throws Exception { diff --git a/briar-tests/src/net/sf/briar/protocol/ProtocolWriterImplTest.java b/briar-tests/src/net/sf/briar/protocol/ProtocolWriterImplTest.java index 6c916c3e56..b89b9209d1 100644 --- a/briar-tests/src/net/sf/briar/protocol/ProtocolWriterImplTest.java +++ b/briar-tests/src/net/sf/briar/protocol/ProtocolWriterImplTest.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.util.BitSet; import net.sf.briar.BriarTestCase; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.ProtocolWriter; import net.sf.briar.api.protocol.Request; import net.sf.briar.api.serial.SerialComponent; @@ -24,7 +23,6 @@ public class ProtocolWriterImplTest extends BriarTestCase { // FIXME: This is an integration test, not a unit test - private final PacketFactory packetFactory; private final SerialComponent serial; private final WriterFactory writerFactory; @@ -32,7 +30,6 @@ public class ProtocolWriterImplTest extends BriarTestCase { super(); Injector i = Guice.createInjector(new ClockModule(), new CryptoModule(), new ProtocolModule(), new SerialModule()); - packetFactory = i.getInstance(PacketFactory.class); serial = i.getInstance(SerialComponent.class); writerFactory = i.getInstance(WriterFactory.class); } @@ -54,8 +51,7 @@ public class ProtocolWriterImplTest extends BriarTestCase { b.set(11); b.set(12); b.set(15); - Request r = packetFactory.createRequest(b, 16); - w.writeRequest(r); + w.writeRequest(new Request(b, 16)); // Short user tag 6, 0 as uint7, short bytes with length 2, 0xD959 byte[] output = out.toByteArray(); assertEquals("C6" + "00" + "92" + "D959", @@ -78,8 +74,7 @@ public class ProtocolWriterImplTest extends BriarTestCase { b.set(9); b.set(11); b.set(12); - Request r = packetFactory.createRequest(b, 13); - w.writeRequest(r); + w.writeRequest(new Request(b, 13)); // Short user tag 6, 3 as uint7, short bytes with length 2, 0x59D8 byte[] output = out.toByteArray(); assertEquals("C6" + "03" + "92" + "59D8", diff --git a/briar-tests/src/net/sf/briar/protocol/RequestReaderTest.java b/briar-tests/src/net/sf/briar/protocol/RequestReaderTest.java index d92b0dc420..ec9d2746d4 100644 --- a/briar-tests/src/net/sf/briar/protocol/RequestReaderTest.java +++ b/briar-tests/src/net/sf/briar/protocol/RequestReaderTest.java @@ -9,7 +9,6 @@ import java.util.BitSet; import net.sf.briar.BriarTestCase; import net.sf.briar.api.FormatException; -import net.sf.briar.api.protocol.PacketFactory; import net.sf.briar.api.protocol.Request; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.ReaderFactory; @@ -19,8 +18,6 @@ import net.sf.briar.clock.ClockModule; import net.sf.briar.crypto.CryptoModule; import net.sf.briar.serial.SerialModule; -import org.jmock.Expectations; -import org.jmock.Mockery; import org.junit.Test; import com.google.inject.Guice; @@ -32,8 +29,6 @@ public class RequestReaderTest extends BriarTestCase { private final ReaderFactory readerFactory; private final WriterFactory writerFactory; - private final PacketFactory packetFactory; - private final Mockery context; public RequestReaderTest() throws Exception { super(); @@ -41,45 +36,27 @@ public class RequestReaderTest extends BriarTestCase { new ProtocolModule(), new SerialModule()); readerFactory = i.getInstance(ReaderFactory.class); writerFactory = i.getInstance(WriterFactory.class); - packetFactory = i.getInstance(PacketFactory.class); - context = new Mockery(); } @Test public void testFormatExceptionIfRequestIsTooLarge() throws Exception { - PacketFactory packetFactory = context.mock(PacketFactory.class); - RequestReader requestReader = new RequestReader(packetFactory); - byte[] b = createRequest(true); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(REQUEST, requestReader); - + reader.addStructReader(REQUEST, new RequestReader()); try { reader.readStruct(REQUEST, Request.class); fail(); } catch(FormatException expected) {} - context.assertIsSatisfied(); } @Test public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception { - final PacketFactory packetFactory = context.mock(PacketFactory.class); - RequestReader requestReader = new RequestReader(packetFactory); - final Request request = context.mock(Request.class); - context.checking(new Expectations() {{ - oneOf(packetFactory).createRequest(with(any(BitSet.class)), - with(any(int.class))); - will(returnValue(request)); - }}); - byte[] b = createRequest(false); ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - reader.addStructReader(REQUEST, requestReader); - - assertEquals(request, reader.readStruct(REQUEST, Request.class)); - context.assertIsSatisfied(); + reader.addStructReader(REQUEST, new RequestReader()); + reader.readStruct(REQUEST, Request.class); } @Test @@ -104,7 +81,7 @@ public class RequestReaderTest extends BriarTestCase { // Deserialise the request ByteArrayInputStream in = new ByteArrayInputStream(b); Reader reader = readerFactory.createReader(in); - RequestReader requestReader = new RequestReader(packetFactory); + RequestReader requestReader = new RequestReader(); reader.addStructReader(REQUEST, requestReader); Request r = reader.readStruct(REQUEST, Request.class); BitSet decoded = r.getBitmap(); diff --git a/briar-tests/src/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java index cffd54b1e9..eea453a768 100644 --- a/briar-tests/src/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java +++ b/briar-tests/src/net/sf/briar/protocol/simplex/SimplexProtocolIntegrationTest.java @@ -5,8 +5,6 @@ import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.util.Collection; -import java.util.Collections; import java.util.Random; import net.sf.briar.BriarTestCase; @@ -161,17 +159,9 @@ public class SimplexProtocolIntegrationTest extends BriarTestCase { MessageListener listener = new MessageListener(); db.addListener(listener); // Fake a transport update from Alice - TransportUpdate transportUpdate = new TransportUpdate() { - - public Collection<Transport> getTransports() { - Transport t = new Transport(transportId); - return Collections.singletonList(t); - } - - public long getTimestamp() { - return System.currentTimeMillis(); - } - }; + Transport t = new Transport(transportId); + TransportUpdate transportUpdate = new TransportUpdate(t, + transportVersion); db.receiveTransportUpdate(contactId, transportUpdate); // Create a connection recogniser and recognise the connection ByteArrayInputStream in = new ByteArrayInputStream(b); -- GitLab