mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-21 23:29:52 +01:00
The response to a BMP Offer is now an Ack and/or a Request.
The Request packet now contains a list of message IDs, rather than a bitmap referring to the list of messages IDs in the Offer. This allows the Request to be understood out of context, e.g. if the Offer and Request are sent over separate connections or a connection is replayed.
This commit is contained in:
27
briar-api/src/net/sf/briar/api/db/AckAndRequest.java
Normal file
27
briar-api/src/net/sf/briar/api/db/AckAndRequest.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package net.sf.briar.api.db;
|
||||||
|
|
||||||
|
import net.sf.briar.api.messaging.Ack;
|
||||||
|
import net.sf.briar.api.messaging.Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tuple of an {@link net.sf.briar.api.messaging.Ack} and a
|
||||||
|
* {@link net.sf.briar.api.messaging.Request}.
|
||||||
|
*/
|
||||||
|
public class AckAndRequest {
|
||||||
|
|
||||||
|
private final Ack ack;
|
||||||
|
private final Request request;
|
||||||
|
|
||||||
|
public AckAndRequest(Ack ack, Request request) {
|
||||||
|
this.ack = ack;
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ack getAck() {
|
||||||
|
return ack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,6 @@ import net.sf.briar.api.messaging.GroupStatus;
|
|||||||
import net.sf.briar.api.messaging.Message;
|
import net.sf.briar.api.messaging.Message;
|
||||||
import net.sf.briar.api.messaging.MessageId;
|
import net.sf.briar.api.messaging.MessageId;
|
||||||
import net.sf.briar.api.messaging.Offer;
|
import net.sf.briar.api.messaging.Offer;
|
||||||
import net.sf.briar.api.messaging.Request;
|
|
||||||
import net.sf.briar.api.messaging.RetentionAck;
|
import net.sf.briar.api.messaging.RetentionAck;
|
||||||
import net.sf.briar.api.messaging.RetentionUpdate;
|
import net.sf.briar.api.messaging.RetentionUpdate;
|
||||||
import net.sf.briar.api.messaging.SubscriptionAck;
|
import net.sf.briar.api.messaging.SubscriptionAck;
|
||||||
@@ -98,9 +97,9 @@ public interface DatabaseComponent {
|
|||||||
* collection of requested messages, with a total length less than or equal
|
* collection of requested messages, with a total length less than or equal
|
||||||
* to the given length, for transmission over a transport with the given
|
* to the given length, for transmission over a transport with the given
|
||||||
* maximum latency. Any messages that were either added to the batch, or
|
* maximum latency. Any messages that were either added to the batch, or
|
||||||
* were considered but are no longer sendable to the contact, are removed
|
* were considered but are not sendable to the contact, are removed from
|
||||||
* from the collection of requested messages before returning. Returns null
|
* the collection of requested messages before returning. Returns null if
|
||||||
* if there are no sendable messages that fit in the given length.
|
* there are no sendable messages that fit in the given length.
|
||||||
*/
|
*/
|
||||||
Collection<byte[]> generateBatch(ContactId c, int maxLength,
|
Collection<byte[]> generateBatch(ContactId c, int maxLength,
|
||||||
long maxLatency, Collection<MessageId> requested)
|
long maxLatency, Collection<MessageId> requested)
|
||||||
@@ -259,14 +258,17 @@ public interface DatabaseComponent {
|
|||||||
void receiveMessage(ContactId c, Message m) throws DbException;
|
void receiveMessage(ContactId c, Message m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes an offer from the given contact and generates a request for
|
* Processes an offer from the given contact and generates an ack for any
|
||||||
* any messages in the offer that the contact should send. To prevent
|
* messages in the offer that are present in the database, and a request
|
||||||
* contacts from using offers to test for subscriptions that are not
|
* for any messages that are not. The ack or the request may be null if no
|
||||||
* visible to them, any messages belonging to groups that are not visible
|
* messages meet the respective criteria.
|
||||||
* to the contact are requested just as though they were not present in the
|
* <p>
|
||||||
* database.
|
* To prevent contacts from using offers to test for subscriptions that are
|
||||||
|
* not visible to them, any messages belonging to groups that are not
|
||||||
|
* visible to the contact are requested just as though they were not
|
||||||
|
* present in the database.
|
||||||
*/
|
*/
|
||||||
Request receiveOffer(ContactId c, Offer o) throws DbException;
|
AckAndRequest receiveOffer(ContactId c, Offer o) throws DbException;
|
||||||
|
|
||||||
/** Processes a retention ack from the given contact. */
|
/** Processes a retention ack from the given contact. */
|
||||||
void receiveRetentionAck(ContactId c, RetentionAck a) throws DbException;
|
void receiveRetentionAck(ContactId c, RetentionAck a) throws DbException;
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
package net.sf.briar.api.messaging;
|
package net.sf.briar.api.messaging;
|
||||||
|
|
||||||
import java.util.BitSet;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/** A packet requesting one or more {@link Message}s from the recipient. */
|
||||||
* A packet requesting some or all of the {@link Message}s from an
|
|
||||||
* {@link Offer}.
|
|
||||||
*/
|
|
||||||
public class Request {
|
public class Request {
|
||||||
|
|
||||||
private final BitSet requested;
|
private final Collection<MessageId> requested;
|
||||||
private final int length;
|
|
||||||
|
|
||||||
public Request(BitSet requested, int length) {
|
public Request(Collection<MessageId> requested) {
|
||||||
this.requested = requested;
|
this.requested = requested;
|
||||||
this.length = length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the identifiers of the requested messages. */
|
||||||
* Returns a sequence of bits corresponding to the sequence of messages in
|
public Collection<MessageId> getMessageIds() {
|
||||||
* the offer, where the i^th bit is set if the i^th message should be sent.
|
|
||||||
*/
|
|
||||||
public BitSet getBitmap() {
|
|
||||||
return requested;
|
return requested;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the length of the bitmap in bits. */
|
|
||||||
public int getLength() {
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -32,6 +31,7 @@ import net.sf.briar.api.TransportConfig;
|
|||||||
import net.sf.briar.api.TransportId;
|
import net.sf.briar.api.TransportId;
|
||||||
import net.sf.briar.api.TransportProperties;
|
import net.sf.briar.api.TransportProperties;
|
||||||
import net.sf.briar.api.clock.Clock;
|
import net.sf.briar.api.clock.Clock;
|
||||||
|
import net.sf.briar.api.db.AckAndRequest;
|
||||||
import net.sf.briar.api.db.ContactExistsException;
|
import net.sf.briar.api.db.ContactExistsException;
|
||||||
import net.sf.briar.api.db.DatabaseComponent;
|
import net.sf.briar.api.db.DatabaseComponent;
|
||||||
import net.sf.briar.api.db.DbException;
|
import net.sf.briar.api.db.DbException;
|
||||||
@@ -1402,9 +1402,9 @@ DatabaseCleaner.Callback {
|
|||||||
return storeGroupMessage(txn, m, c);
|
return storeGroupMessage(txn, m, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Request receiveOffer(ContactId c, Offer o) throws DbException {
|
public AckAndRequest receiveOffer(ContactId c, Offer o) throws DbException {
|
||||||
Collection<MessageId> offered;
|
List<MessageId> ack = new ArrayList<MessageId>();
|
||||||
BitSet request;
|
List<MessageId> request = new ArrayList<MessageId>();
|
||||||
contactLock.readLock().lock();
|
contactLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
messageLock.writeLock().lock();
|
messageLock.writeLock().lock();
|
||||||
@@ -1415,15 +1415,10 @@ DatabaseCleaner.Callback {
|
|||||||
try {
|
try {
|
||||||
if(!db.containsContact(txn, c))
|
if(!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
offered = o.getMessageIds();
|
for(MessageId m : o.getMessageIds()) {
|
||||||
request = new BitSet(offered.size());
|
// If the message is present and visible, ack it
|
||||||
Iterator<MessageId> it = offered.iterator();
|
if(db.setStatusSeenIfVisible(txn, c, m)) ack.add(m);
|
||||||
for(int i = 0; it.hasNext(); i++) {
|
else request.add(m);
|
||||||
// If the message is not in the database, or not
|
|
||||||
// visible to the contact, request it
|
|
||||||
MessageId m = it.next();
|
|
||||||
if(!db.setStatusSeenIfVisible(txn, c, m))
|
|
||||||
request.set(i);
|
|
||||||
}
|
}
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
@@ -1439,7 +1434,9 @@ DatabaseCleaner.Callback {
|
|||||||
} finally {
|
} finally {
|
||||||
contactLock.readLock().unlock();
|
contactLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
return new Request(request, offered.size());
|
Ack a = ack.isEmpty() ? null : new Ack(ack);
|
||||||
|
Request r = request.isEmpty() ? null : new Request(request);
|
||||||
|
return new AckAndRequest(a, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveRetentionAck(ContactId c, RetentionAck a)
|
public void receiveRetentionAck(ContactId c, RetentionAck a)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import static net.sf.briar.api.messaging.Types.TRANSPORT_UPDATE;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -113,22 +112,22 @@ class PacketReaderImpl implements PacketReader {
|
|||||||
// Read the start of the struct
|
// Read the start of the struct
|
||||||
r.readStructStart(OFFER);
|
r.readStructStart(OFFER);
|
||||||
// Read the message IDs
|
// Read the message IDs
|
||||||
List<MessageId> messages = new ArrayList<MessageId>();
|
List<MessageId> offered = new ArrayList<MessageId>();
|
||||||
r.readListStart();
|
r.readListStart();
|
||||||
while(!r.hasListEnd()) {
|
while(!r.hasListEnd()) {
|
||||||
byte[] b = r.readBytes(UniqueId.LENGTH);
|
byte[] b = r.readBytes(UniqueId.LENGTH);
|
||||||
if(b.length != UniqueId.LENGTH)
|
if(b.length != UniqueId.LENGTH)
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
messages.add(new MessageId(b));
|
offered.add(new MessageId(b));
|
||||||
}
|
}
|
||||||
if(messages.isEmpty()) throw new FormatException();
|
if(offered.isEmpty()) throw new FormatException();
|
||||||
r.readListEnd();
|
r.readListEnd();
|
||||||
// Read the end of the struct
|
// Read the end of the struct
|
||||||
r.readStructEnd();
|
r.readStructEnd();
|
||||||
// Reset the reader
|
// Reset the reader
|
||||||
r.removeConsumer(counting);
|
r.removeConsumer(counting);
|
||||||
// Build and return the offer
|
// Build and return the offer
|
||||||
return new Offer(Collections.unmodifiableList(messages));
|
return new Offer(Collections.unmodifiableList(offered));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasRequest() throws IOException {
|
public boolean hasRequest() throws IOException {
|
||||||
@@ -141,25 +140,23 @@ class PacketReaderImpl implements PacketReader {
|
|||||||
r.addConsumer(counting);
|
r.addConsumer(counting);
|
||||||
// Read the start of the struct
|
// Read the start of the struct
|
||||||
r.readStructStart(REQUEST);
|
r.readStructStart(REQUEST);
|
||||||
// There may be up to 7 bits of padding at the end of the bitmap
|
// Read the message IDs
|
||||||
int padding = r.readUint7();
|
List<MessageId> requested = new ArrayList<MessageId>();
|
||||||
if(padding > 7) throw new FormatException();
|
r.readListStart();
|
||||||
// Read the bitmap
|
while(!r.hasListEnd()) {
|
||||||
byte[] bitmap = r.readBytes(MAX_PACKET_LENGTH);
|
byte[] b = r.readBytes(UniqueId.LENGTH);
|
||||||
|
if(b.length != UniqueId.LENGTH)
|
||||||
|
throw new FormatException();
|
||||||
|
requested.add(new MessageId(b));
|
||||||
|
}
|
||||||
|
if(requested.isEmpty()) throw new FormatException();
|
||||||
|
r.readListEnd();
|
||||||
// Read the end of the struct
|
// Read the end of the struct
|
||||||
r.readStructEnd();
|
r.readStructEnd();
|
||||||
// Reset the reader
|
// Reset the reader
|
||||||
r.removeConsumer(counting);
|
r.removeConsumer(counting);
|
||||||
// Convert the bitmap into a BitSet
|
// Build and return the request
|
||||||
int length = bitmap.length * 8 - padding;
|
return new Request(Collections.unmodifiableList(requested));
|
||||||
BitSet b = new BitSet(length);
|
|
||||||
for(int i = 0; i < bitmap.length; i++) {
|
|
||||||
for(int j = 0; j < 8 && i * 8 + j < length; j++) {
|
|
||||||
byte bit = (byte) (128 >> j);
|
|
||||||
if((bitmap[i] & bit) != 0) b.set(i * 8 + j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Request(b, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasRetentionAck() throws IOException {
|
public boolean hasRetentionAck() throws IOException {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import static net.sf.briar.api.messaging.Types.TRANSPORT_UPDATE;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.BitSet;
|
|
||||||
|
|
||||||
import net.sf.briar.api.messaging.Ack;
|
import net.sf.briar.api.messaging.Ack;
|
||||||
import net.sf.briar.api.messaging.Group;
|
import net.sf.briar.api.messaging.Group;
|
||||||
@@ -92,22 +91,10 @@ class PacketWriterImpl implements PacketWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeRequest(Request r) throws IOException {
|
public void writeRequest(Request r) throws IOException {
|
||||||
BitSet b = r.getBitmap();
|
|
||||||
int length = r.getLength();
|
|
||||||
// If the number of bits isn't a multiple of 8, round up to a byte
|
|
||||||
int bytes = length % 8 == 0 ? length / 8 : length / 8 + 1;
|
|
||||||
byte[] bitmap = new byte[bytes];
|
|
||||||
// I'm kind of surprised BitSet doesn't have a method for this
|
|
||||||
for(int i = 0; i < length; i++) {
|
|
||||||
if(b.get(i)) {
|
|
||||||
int offset = i / 8;
|
|
||||||
byte bit = (byte) (128 >> i % 8);
|
|
||||||
bitmap[offset] |= bit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.writeStructStart(REQUEST);
|
w.writeStructStart(REQUEST);
|
||||||
w.writeUint7((byte) (bytes * 8 - length));
|
w.writeListStart();
|
||||||
w.writeBytes(bitmap);
|
for(MessageId m : r.getMessageIds()) w.writeBytes(m.getBytes());
|
||||||
|
w.writeListEnd();
|
||||||
w.writeStructEnd();
|
w.writeStructEnd();
|
||||||
if(flush) out.flush();
|
if(flush) out.flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -23,6 +18,7 @@ import java.util.logging.Logger;
|
|||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
import net.sf.briar.api.FormatException;
|
import net.sf.briar.api.FormatException;
|
||||||
import net.sf.briar.api.TransportId;
|
import net.sf.briar.api.TransportId;
|
||||||
|
import net.sf.briar.api.db.AckAndRequest;
|
||||||
import net.sf.briar.api.db.DatabaseComponent;
|
import net.sf.briar.api.db.DatabaseComponent;
|
||||||
import net.sf.briar.api.db.DbException;
|
import net.sf.briar.api.db.DbException;
|
||||||
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
import net.sf.briar.api.db.event.ContactRemovedEvent;
|
||||||
@@ -93,8 +89,6 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
private final AtomicBoolean canSendOffer, disposed;
|
private final AtomicBoolean canSendOffer, disposed;
|
||||||
private final BlockingQueue<Runnable> writerTasks;
|
private final BlockingQueue<Runnable> writerTasks;
|
||||||
|
|
||||||
private Collection<MessageId> offered = null; // Locking: this
|
|
||||||
|
|
||||||
private volatile PacketWriter writer = null;
|
private volatile PacketWriter writer = null;
|
||||||
|
|
||||||
DuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
|
DuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
|
||||||
@@ -142,8 +136,11 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
|
} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
|
||||||
LocalSubscriptionsUpdatedEvent l =
|
LocalSubscriptionsUpdatedEvent l =
|
||||||
(LocalSubscriptionsUpdatedEvent) e;
|
(LocalSubscriptionsUpdatedEvent) e;
|
||||||
if(l.getAffectedContacts().contains(contactId))
|
if(l.getAffectedContacts().contains(contactId)) {
|
||||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||||
|
if(canSendOffer.getAndSet(false))
|
||||||
|
dbExecutor.execute(new GenerateOffer());
|
||||||
|
}
|
||||||
} else if(e instanceof LocalTransportsUpdatedEvent) {
|
} else if(e instanceof LocalTransportsUpdatedEvent) {
|
||||||
dbExecutor.execute(new GenerateTransportUpdates());
|
dbExecutor.execute(new GenerateTransportUpdates());
|
||||||
} else if(e instanceof MessageReceivedEvent) {
|
} else if(e instanceof MessageReceivedEvent) {
|
||||||
@@ -159,6 +156,8 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
dbExecutor.execute(new GenerateRetentionAck());
|
dbExecutor.execute(new GenerateRetentionAck());
|
||||||
} else if(e instanceof RemoteSubscriptionsUpdatedEvent) {
|
} else if(e instanceof RemoteSubscriptionsUpdatedEvent) {
|
||||||
dbExecutor.execute(new GenerateSubscriptionAck());
|
dbExecutor.execute(new GenerateSubscriptionAck());
|
||||||
|
if(canSendOffer.getAndSet(false))
|
||||||
|
dbExecutor.execute(new GenerateOffer());
|
||||||
} else if(e instanceof RemoteTransportsUpdatedEvent) {
|
} else if(e instanceof RemoteTransportsUpdatedEvent) {
|
||||||
dbExecutor.execute(new GenerateTransportAcks());
|
dbExecutor.execute(new GenerateTransportAcks());
|
||||||
}
|
}
|
||||||
@@ -185,24 +184,7 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
} else if(reader.hasRequest()) {
|
} else if(reader.hasRequest()) {
|
||||||
Request r = reader.readRequest();
|
Request r = reader.readRequest();
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Received request");
|
if(LOG.isLoggable(INFO)) LOG.info("Received request");
|
||||||
// Retrieve the offered message IDs
|
dbExecutor.execute(new GenerateBatches(r.getMessageIds()));
|
||||||
Collection<MessageId> offered = getOfferedMessageIds();
|
|
||||||
if(offered == null) throw new FormatException();
|
|
||||||
// Work out which messages were requested
|
|
||||||
BitSet b = r.getBitmap();
|
|
||||||
List<MessageId> requested = new LinkedList<MessageId>();
|
|
||||||
List<MessageId> seen = new ArrayList<MessageId>();
|
|
||||||
int i = 0;
|
|
||||||
for(MessageId m : offered) {
|
|
||||||
if(b.get(i++)) requested.add(m);
|
|
||||||
else seen.add(m);
|
|
||||||
}
|
|
||||||
requested = Collections.synchronizedList(requested);
|
|
||||||
seen = Collections.unmodifiableList(seen);
|
|
||||||
// Mark the unrequested messages as seen
|
|
||||||
dbExecutor.execute(new SetSeen(seen));
|
|
||||||
// Start sending the requested messages
|
|
||||||
dbExecutor.execute(new GenerateBatches(requested));
|
|
||||||
} else if(reader.hasRetentionAck()) {
|
} else if(reader.hasRetentionAck()) {
|
||||||
RetentionAck a = reader.readRetentionAck();
|
RetentionAck a = reader.readRetentionAck();
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Received retention ack");
|
if(LOG.isLoggable(INFO)) LOG.info("Received retention ack");
|
||||||
@@ -244,17 +226,6 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized Collection<MessageId> getOfferedMessageIds() {
|
|
||||||
Collection<MessageId> ids = offered;
|
|
||||||
offered = null;
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void setOfferedMessageIds(Collection<MessageId> ids) {
|
|
||||||
assert offered == null;
|
|
||||||
offered = ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
void write() {
|
void write() {
|
||||||
connRegistry.registerConnection(contactId, transportId);
|
connRegistry.registerConnection(contactId, transportId);
|
||||||
db.addListener(this);
|
db.addListener(this);
|
||||||
@@ -383,9 +354,15 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Request r = db.receiveOffer(contactId, offer);
|
AckAndRequest ar = db.receiveOffer(contactId, offer);
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("DB received offer");
|
Ack a = ar.getAck();
|
||||||
writerTasks.add(new WriteRequest(r));
|
Request r = ar.getRequest();
|
||||||
|
if(LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("DB received offer: " + (a != null)
|
||||||
|
+ " " + (r != null));
|
||||||
|
}
|
||||||
|
if(a != null) writerTasks.add(new WriteAck(a));
|
||||||
|
if(r != null) writerTasks.add(new WriteRequest(r));
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
@@ -413,25 +390,6 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This task runs on a database thread
|
|
||||||
private class SetSeen implements Runnable {
|
|
||||||
|
|
||||||
private final Collection<MessageId> seen;
|
|
||||||
|
|
||||||
private SetSeen(Collection<MessageId> seen) {
|
|
||||||
this.seen = seen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
db.setSeen(contactId, seen);
|
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("DB set seen");
|
|
||||||
} catch(DbException e) {
|
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This task runs on a database thread
|
// This task runs on a database thread
|
||||||
private class ReceiveRetentionAck implements Runnable {
|
private class ReceiveRetentionAck implements Runnable {
|
||||||
|
|
||||||
@@ -649,15 +607,8 @@ abstract class DuplexConnection implements DatabaseListener {
|
|||||||
Offer o = db.generateOffer(contactId, maxMessages);
|
Offer o = db.generateOffer(contactId, maxMessages);
|
||||||
if(LOG.isLoggable(INFO))
|
if(LOG.isLoggable(INFO))
|
||||||
LOG.info("Generated offer: " + (o != null));
|
LOG.info("Generated offer: " + (o != null));
|
||||||
if(o == null) {
|
if(o == null) canSendOffer.set(true);
|
||||||
// No messages to offer - wait for some to be added
|
else writerTasks.add(new WriteOffer(o));
|
||||||
canSendOffer.set(true);
|
|
||||||
} else {
|
|
||||||
// Store the offered message IDs
|
|
||||||
setOfferedMessageIds(o.getMessageIds());
|
|
||||||
// Write the offer on the writer thread
|
|
||||||
writerTasks.add(new WriteOffer(o));
|
|
||||||
}
|
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,6 @@
|
|||||||
<test name='net.sf.briar.messaging.ConstantsTest'/>
|
<test name='net.sf.briar.messaging.ConstantsTest'/>
|
||||||
<test name='net.sf.briar.messaging.ConsumersTest'/>
|
<test name='net.sf.briar.messaging.ConsumersTest'/>
|
||||||
<test name='net.sf.briar.messaging.PacketReaderImplTest'/>
|
<test name='net.sf.briar.messaging.PacketReaderImplTest'/>
|
||||||
<test name='net.sf.briar.messaging.PacketWriterImplTest'/>
|
|
||||||
<test name='net.sf.briar.messaging.simplex.OutgoingSimplexConnectionTest'/>
|
<test name='net.sf.briar.messaging.simplex.OutgoingSimplexConnectionTest'/>
|
||||||
<test name='net.sf.briar.messaging.simplex.SimplexMessagingIntegrationTest'/>
|
<test name='net.sf.briar.messaging.simplex.SimplexMessagingIntegrationTest'/>
|
||||||
<test name='net.sf.briar.plugins.PluginManagerImplTest'/>
|
<test name='net.sf.briar.plugins.PluginManagerImplTest'/>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
@@ -138,9 +137,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
|
|
||||||
writer.writeOffer(new Offer(messageIds));
|
writer.writeOffer(new Offer(messageIds));
|
||||||
|
|
||||||
BitSet requested = new BitSet(2);
|
writer.writeRequest(new Request(messageIds));
|
||||||
requested.set(1);
|
|
||||||
writer.writeRequest(new Request(requested, 2));
|
|
||||||
|
|
||||||
SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
|
SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
|
||||||
writer.writeSubscriptionUpdate(su);
|
writer.writeSubscriptionUpdate(su);
|
||||||
@@ -187,11 +184,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
// Read the request
|
// Read the request
|
||||||
assertTrue(reader.hasRequest());
|
assertTrue(reader.hasRequest());
|
||||||
Request req = reader.readRequest();
|
Request req = reader.readRequest();
|
||||||
BitSet requested = req.getBitmap();
|
assertEquals(messageIds, req.getMessageIds());
|
||||||
assertFalse(requested.get(0));
|
|
||||||
assertTrue(requested.get(1));
|
|
||||||
// If there are any padding bits, they should all be zero
|
|
||||||
assertEquals(1, requested.cardinality());
|
|
||||||
|
|
||||||
// Read the subscription update
|
// Read the subscription update
|
||||||
assertTrue(reader.hasSubscriptionUpdate());
|
assertTrue(reader.hasSubscriptionUpdate());
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import net.sf.briar.api.LocalAuthor;
|
|||||||
import net.sf.briar.api.TransportConfig;
|
import net.sf.briar.api.TransportConfig;
|
||||||
import net.sf.briar.api.TransportId;
|
import net.sf.briar.api.TransportId;
|
||||||
import net.sf.briar.api.TransportProperties;
|
import net.sf.briar.api.TransportProperties;
|
||||||
|
import net.sf.briar.api.db.AckAndRequest;
|
||||||
import net.sf.briar.api.db.DatabaseComponent;
|
import net.sf.briar.api.db.DatabaseComponent;
|
||||||
import net.sf.briar.api.db.NoSuchContactException;
|
import net.sf.briar.api.db.NoSuchContactException;
|
||||||
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
import net.sf.briar.api.db.NoSuchSubscriptionException;
|
||||||
@@ -1077,7 +1078,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId);
|
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId);
|
||||||
will(returnValue(false)); // Not visible - request message # 0
|
will(returnValue(false)); // Not visible - request message # 0
|
||||||
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId1);
|
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId1);
|
||||||
will(returnValue(true)); // Visible - do not request message # 1
|
will(returnValue(true)); // Visible - ack message # 1
|
||||||
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2);
|
oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2);
|
||||||
will(returnValue(false)); // Not visible - request message # 2
|
will(returnValue(false)); // Not visible - request message # 2
|
||||||
}});
|
}});
|
||||||
@@ -1085,9 +1086,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
shutdown);
|
shutdown);
|
||||||
|
|
||||||
Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2));
|
Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2));
|
||||||
Request r = db.receiveOffer(contactId, o);
|
AckAndRequest ar = db.receiveOffer(contactId, o);
|
||||||
assertEquals(expectedRequest, r.getBitmap());
|
Ack a = ar.getAck();
|
||||||
assertEquals(3, r.getLength());
|
assertNotNull(a);
|
||||||
|
assertEquals(Arrays.asList(messageId1), a.getMessageIds());
|
||||||
|
Request r = ar.getRequest();
|
||||||
|
assertNotNull(r);
|
||||||
|
assertEquals(Arrays.asList(messageId, messageId2), r.getMessageIds());
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ import static net.sf.briar.api.messaging.Types.REQUEST;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.BitSet;
|
|
||||||
|
|
||||||
import net.sf.briar.BriarTestCase;
|
import net.sf.briar.BriarTestCase;
|
||||||
import net.sf.briar.TestUtils;
|
import net.sf.briar.TestUtils;
|
||||||
import net.sf.briar.api.FormatException;
|
import net.sf.briar.api.FormatException;
|
||||||
import net.sf.briar.api.messaging.Request;
|
|
||||||
import net.sf.briar.api.serial.ReaderFactory;
|
import net.sf.briar.api.serial.ReaderFactory;
|
||||||
import net.sf.briar.api.serial.SerialComponent;
|
import net.sf.briar.api.serial.SerialComponent;
|
||||||
import net.sf.briar.api.serial.Writer;
|
import net.sf.briar.api.serial.Writer;
|
||||||
@@ -127,39 +125,15 @@ public class PacketReaderImplTest extends BriarTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBitmapDecoding() throws Exception {
|
public void testEmptyRequest() throws Exception {
|
||||||
// Test sizes from 0 to 1000 bits
|
byte[] b = createEmptyRequest();
|
||||||
for(int i = 0; i < 1000; i++) {
|
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||||
// Create a BitSet of size i with one in ten bits set (on average)
|
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||||
BitSet requested = new BitSet(i);
|
null, in);
|
||||||
for(int j = 0; j < i; j++) if(Math.random() < 0.1) requested.set(j);
|
try {
|
||||||
// Encode the BitSet as a bitmap
|
reader.readRequest();
|
||||||
int bytes = i % 8 == 0 ? i / 8 : i / 8 + 1;
|
fail();
|
||||||
byte[] bitmap = new byte[bytes];
|
} catch(FormatException expected) {}
|
||||||
for(int j = 0; j < i; j++) {
|
|
||||||
if(requested.get(j)) {
|
|
||||||
int offset = j / 8;
|
|
||||||
byte bit = (byte) (128 >> j % 8);
|
|
||||||
bitmap[offset] |= bit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create a serialised request containing the bitmap
|
|
||||||
byte[] b = createRequest(bitmap);
|
|
||||||
// Deserialise the request
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
|
||||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory,
|
|
||||||
null, null, in);
|
|
||||||
Request request = reader.readRequest();
|
|
||||||
BitSet decoded = request.getBitmap();
|
|
||||||
// Check that the decoded BitSet matches the original - we can't
|
|
||||||
// use equals() because of padding, but the first i bits should
|
|
||||||
// match and the cardinalities should be equal, indicating that no
|
|
||||||
// padding bits are set
|
|
||||||
for(int j = 0; j < i; j++) {
|
|
||||||
assertEquals(requested.get(j), decoded.get(j));
|
|
||||||
}
|
|
||||||
assertEquals(requested.cardinality(), decoded.cardinality());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createAck(boolean tooBig) throws Exception {
|
private byte[] createAck(boolean tooBig) throws Exception {
|
||||||
@@ -222,26 +196,26 @@ public class PacketReaderImplTest extends BriarTestCase {
|
|||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Writer w = writerFactory.createWriter(out);
|
Writer w = writerFactory.createWriter(out);
|
||||||
w.writeStructStart(REQUEST);
|
w.writeStructStart(REQUEST);
|
||||||
// Allow one byte for the STRUCT tag, one byte for the struct ID,
|
w.writeListStart();
|
||||||
// one byte for the padding length as a uint7, one byte for the BYTES
|
while(out.size() + serial.getSerialisedUniqueIdLength()
|
||||||
// tag, five bytes for the length of the byte array as an int32, and
|
+ serial.getSerialisedListEndLength()
|
||||||
// one byte for the END tag
|
+ serial.getSerialisedStructEndLength()
|
||||||
int size = MAX_PACKET_LENGTH - 10;
|
< MAX_PACKET_LENGTH) {
|
||||||
if(tooBig) size++;
|
w.writeBytes(TestUtils.getRandomId());
|
||||||
assertTrue(size > Short.MAX_VALUE);
|
}
|
||||||
w.writeUint7((byte) 0);
|
if(tooBig) w.writeBytes(TestUtils.getRandomId());
|
||||||
w.writeBytes(new byte[size]);
|
w.writeListEnd();
|
||||||
w.writeStructEnd();
|
w.writeStructEnd();
|
||||||
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
|
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createRequest(byte[] bitmap) throws Exception {
|
private byte[] createEmptyRequest() throws Exception {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Writer w = writerFactory.createWriter(out);
|
Writer w = writerFactory.createWriter(out);
|
||||||
w.writeStructStart(REQUEST);
|
w.writeStructStart(REQUEST);
|
||||||
w.writeUint7((byte) 0);
|
w.writeListStart();
|
||||||
w.writeBytes(bitmap);
|
w.writeListEnd();
|
||||||
w.writeStructEnd();
|
w.writeStructEnd();
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
package net.sf.briar.messaging;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.BitSet;
|
|
||||||
|
|
||||||
import net.sf.briar.BriarTestCase;
|
|
||||||
import net.sf.briar.TestDatabaseModule;
|
|
||||||
import net.sf.briar.TestLifecycleModule;
|
|
||||||
import net.sf.briar.api.messaging.PacketWriter;
|
|
||||||
import net.sf.briar.api.messaging.Request;
|
|
||||||
import net.sf.briar.api.serial.SerialComponent;
|
|
||||||
import net.sf.briar.api.serial.WriterFactory;
|
|
||||||
import net.sf.briar.clock.ClockModule;
|
|
||||||
import net.sf.briar.crypto.CryptoModule;
|
|
||||||
import net.sf.briar.db.DatabaseModule;
|
|
||||||
import net.sf.briar.messaging.duplex.DuplexMessagingModule;
|
|
||||||
import net.sf.briar.messaging.simplex.SimplexMessagingModule;
|
|
||||||
import net.sf.briar.serial.SerialModule;
|
|
||||||
import net.sf.briar.transport.TransportModule;
|
|
||||||
import net.sf.briar.util.StringUtils;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.google.inject.Guice;
|
|
||||||
import com.google.inject.Injector;
|
|
||||||
|
|
||||||
public class PacketWriterImplTest extends BriarTestCase {
|
|
||||||
|
|
||||||
// FIXME: This is an integration test, not a unit test
|
|
||||||
|
|
||||||
private final SerialComponent serial;
|
|
||||||
private final WriterFactory writerFactory;
|
|
||||||
|
|
||||||
public PacketWriterImplTest() {
|
|
||||||
Injector i = Guice.createInjector(new TestDatabaseModule(),
|
|
||||||
new TestLifecycleModule(), new ClockModule(),
|
|
||||||
new CryptoModule(), new DatabaseModule(), new MessagingModule(),
|
|
||||||
new DuplexMessagingModule(), new SimplexMessagingModule(),
|
|
||||||
new SerialModule(), new TransportModule());
|
|
||||||
serial = i.getInstance(SerialComponent.class);
|
|
||||||
writerFactory = i.getInstance(WriterFactory.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteBitmapNoPadding() throws IOException {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
PacketWriter w = new PacketWriterImpl(serial, writerFactory, out,
|
|
||||||
true);
|
|
||||||
BitSet b = new BitSet();
|
|
||||||
// 11011001 = 0xD9
|
|
||||||
b.set(0);
|
|
||||||
b.set(1);
|
|
||||||
b.set(3);
|
|
||||||
b.set(4);
|
|
||||||
b.set(7);
|
|
||||||
// 01011001 = 0x59
|
|
||||||
b.set(9);
|
|
||||||
b.set(11);
|
|
||||||
b.set(12);
|
|
||||||
b.set(15);
|
|
||||||
w.writeRequest(new Request(b, 16));
|
|
||||||
// STRUCT tag, struct ID 5, 0 as uint7, BYTES tag, length 2 as uint7,
|
|
||||||
// bitmap 0xD959, END tag
|
|
||||||
byte[] output = out.toByteArray();
|
|
||||||
assertEquals("F3" + "05" + "00" + "F6" + "02" + "D959" + "F2",
|
|
||||||
StringUtils.toHexString(output));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteBitmapWithPadding() throws IOException {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
PacketWriter w = new PacketWriterImpl(serial, writerFactory, out,
|
|
||||||
true);
|
|
||||||
BitSet b = new BitSet();
|
|
||||||
// 01011001 = 0x59
|
|
||||||
b.set(1);
|
|
||||||
b.set(3);
|
|
||||||
b.set(4);
|
|
||||||
b.set(7);
|
|
||||||
// 11011xxx = 0xD8, after padding
|
|
||||||
b.set(8);
|
|
||||||
b.set(9);
|
|
||||||
b.set(11);
|
|
||||||
b.set(12);
|
|
||||||
w.writeRequest(new Request(b, 13));
|
|
||||||
// STRUCT tag, struct ID 5, 3 as uint7, BYTES tag, length 2 as uint7,
|
|
||||||
// bitmap 0x59D8, END tag
|
|
||||||
byte[] output = out.toByteArray();
|
|
||||||
assertEquals("F3" + "05" + "03" + "F6" + "02" + "59D8" + "F2",
|
|
||||||
StringUtils.toHexString(output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user