From c2b0f0ab5a21a690c4c1364ddcfe9772c60b6b0f Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Sat, 13 Aug 2011 17:46:19 +0200
Subject: [PATCH] Each request packet should contain the unique ID of the offer
 to which it responds.

---
 api/net/sf/briar/api/protocol/Offer.java      |  3 ++
 api/net/sf/briar/api/protocol/OfferId.java    | 26 ++++++++++++++++
 api/net/sf/briar/api/protocol/Request.java    |  6 ++++
 api/net/sf/briar/api/protocol/Tags.java       |  7 +++--
 .../api/protocol/writers/OfferWriter.java     |  5 ++--
 .../api/protocol/writers/RequestWriter.java   |  4 ++-
 .../db/ReadWriteLockDatabaseComponent.java    |  2 +-
 .../db/SynchronizedDatabaseComponent.java     |  2 +-
 .../net/sf/briar/protocol/AckReader.java      |  3 --
 .../net/sf/briar/protocol/AuthorReader.java   |  3 --
 .../net/sf/briar/protocol/BatchReader.java    |  3 --
 .../net/sf/briar/protocol/GroupReader.java    |  3 --
 .../net/sf/briar/protocol/MessageReader.java  |  3 --
 .../net/sf/briar/protocol/OfferFactory.java   |  3 +-
 .../sf/briar/protocol/OfferFactoryImpl.java   |  5 ++--
 .../net/sf/briar/protocol/OfferIdReader.java  | 20 +++++++++++++
 .../net/sf/briar/protocol/OfferImpl.java      |  9 +++++-
 .../net/sf/briar/protocol/OfferReader.java    | 19 ++++++++----
 .../net/sf/briar/protocol/ProtocolModule.java | 16 +++++++---
 .../net/sf/briar/protocol/RequestFactory.java |  3 +-
 .../sf/briar/protocol/RequestFactoryImpl.java |  5 ++--
 .../net/sf/briar/protocol/RequestImpl.java    |  9 +++++-
 .../net/sf/briar/protocol/RequestReader.java  | 13 ++++----
 .../sf/briar/protocol/SubscriptionReader.java |  3 --
 .../sf/briar/protocol/TransportReader.java    |  3 --
 .../protocol/writers/OfferWriterImpl.java     | 21 ++++++++-----
 .../writers/ProtocolWriterFactoryImpl.java    |  2 +-
 .../protocol/writers/RequestWriterImpl.java   |  6 +++-
 test/net/sf/briar/FileReadWriteTest.java      |  6 +++-
 .../sf/briar/db/DatabaseCleanerImplTest.java  |  8 ++---
 .../sf/briar/db/DatabaseComponentTest.java    |  9 ++++--
 .../briar/protocol/ProtocolReadWriteTest.java |  5 +++-
 .../sf/briar/protocol/RequestReaderTest.java  | 30 +++++++++++++------
 .../writers/RequestWriterImplTest.java        | 24 +++++++++++----
 34 files changed, 203 insertions(+), 86 deletions(-)
 create mode 100644 api/net/sf/briar/api/protocol/OfferId.java
 create mode 100644 components/net/sf/briar/protocol/OfferIdReader.java

diff --git a/api/net/sf/briar/api/protocol/Offer.java b/api/net/sf/briar/api/protocol/Offer.java
index e62f05d56a..fbeda85080 100644
--- a/api/net/sf/briar/api/protocol/Offer.java
+++ b/api/net/sf/briar/api/protocol/Offer.java
@@ -11,6 +11,9 @@ public interface Offer {
 	 */
 	static final int MAX_SIZE = (1024 * 1024) - 100;
 
+	/** Returns the offer's unique identifier. */
+	OfferId getId();
+
 	/** Returns the message IDs contained in the offer. */
 	Collection<MessageId> getMessageIds();
 }
diff --git a/api/net/sf/briar/api/protocol/OfferId.java b/api/net/sf/briar/api/protocol/OfferId.java
new file mode 100644
index 0000000000..1d235cfe71
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/OfferId.java
@@ -0,0 +1,26 @@
+package net.sf.briar.api.protocol;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import net.sf.briar.api.serial.Writer;
+
+/** Type-safe wrapper for a byte array that uniquely identifies an offer. */
+public class OfferId extends UniqueId {
+
+	public OfferId(byte[] id) {
+		super(id);
+	}
+
+	public void writeTo(Writer w) throws IOException {
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(id);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if(o instanceof OfferId)
+			return Arrays.equals(id, ((OfferId) o).id);
+		return false;
+	}
+}
diff --git a/api/net/sf/briar/api/protocol/Request.java b/api/net/sf/briar/api/protocol/Request.java
index ea3f461f3f..bc21df77cc 100644
--- a/api/net/sf/briar/api/protocol/Request.java
+++ b/api/net/sf/briar/api/protocol/Request.java
@@ -11,6 +11,12 @@ public interface Request {
 	 */
 	static final int MAX_SIZE = (1024 * 1024) - 100;
 
+	/**
+	 * Returns the unique identifier of the offer to which this request
+	 * responds.
+	 */
+	OfferId getOfferId();
+
 	/**
 	 * 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.
diff --git a/api/net/sf/briar/api/protocol/Tags.java b/api/net/sf/briar/api/protocol/Tags.java
index 3b0071d4ca..101b1e6577 100644
--- a/api/net/sf/briar/api/protocol/Tags.java
+++ b/api/net/sf/briar/api/protocol/Tags.java
@@ -17,7 +17,8 @@ public interface Tags {
 	static final int MESSAGE = 7;
 	static final int MESSAGE_ID = 8;
 	static final int OFFER = 9;
-	static final int REQUEST = 10;
-	static final int SUBSCRIPTIONS = 11;
-	static final int TRANSPORTS = 12;
+	static final int OFFER_ID = 10;
+	static final int REQUEST = 11;
+	static final int SUBSCRIPTIONS = 12;
+	static final int TRANSPORTS = 13;
 }
diff --git a/api/net/sf/briar/api/protocol/writers/OfferWriter.java b/api/net/sf/briar/api/protocol/writers/OfferWriter.java
index 5ad5ddd2e5..6f89b3b87f 100644
--- a/api/net/sf/briar/api/protocol/writers/OfferWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/OfferWriter.java
@@ -3,6 +3,7 @@ package net.sf.briar.api.protocol.writers;
 import java.io.IOException;
 
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.OfferId;
 
 /** An interface for creating a have notification. */
 public interface OfferWriter {
@@ -13,6 +14,6 @@ public interface OfferWriter {
 	 */
 	boolean writeMessageId(MessageId m) throws IOException;
 
-	/** Finishes writing the offer. */
-	void finish() throws IOException;
+	/** Finishes writing the offer and returns its unique identifier. */
+	OfferId finish() throws IOException;
 }
diff --git a/api/net/sf/briar/api/protocol/writers/RequestWriter.java b/api/net/sf/briar/api/protocol/writers/RequestWriter.java
index 335753f6c2..d284f5c0c1 100644
--- a/api/net/sf/briar/api/protocol/writers/RequestWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/RequestWriter.java
@@ -3,9 +3,11 @@ package net.sf.briar.api.protocol.writers;
 import java.io.IOException;
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
+
 /** An interface for creating a request packet. */
 public interface RequestWriter {
 
 	/** Writes the contents of the request. */
-	void writeBitmap(BitSet b, int length) throws IOException;
+	void writeRequest(OfferId offerId, BitSet b, int length) throws IOException;
 }
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index 2eb0dde388..eb8e93375e 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -815,7 +815,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							db.abortTransaction(txn);
 							throw e;
 						}
-						r.writeBitmap(request, offered.size());
+						r.writeRequest(o.getId(), request, offered.size());
 					} finally {
 						subscriptionLock.readLock().unlock();
 					}
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index d99f65f4ed..466efead75 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -627,7 +627,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							db.abortTransaction(txn);
 							throw e;
 						}
-						r.writeBitmap(request, offered.size());
+						r.writeRequest(o.getId(), request, offered.size());
 					}
 				}
 			}
diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java
index 77ba91d23d..96c74d70db 100644
--- a/components/net/sf/briar/protocol/AckReader.java
+++ b/components/net/sf/briar/protocol/AckReader.java
@@ -10,14 +10,11 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class AckReader implements ObjectReader<Ack> {
 
 	private final ObjectReader<BatchId> batchIdReader;
 	private final AckFactory ackFactory;
 
-	@Inject
 	AckReader(ObjectReader<BatchId> batchIdReader, AckFactory ackFactory) {
 		this.batchIdReader = batchIdReader;
 		this.ackFactory = ackFactory;
diff --git a/components/net/sf/briar/protocol/AuthorReader.java b/components/net/sf/briar/protocol/AuthorReader.java
index e0d73ed3ba..4ce4cc8e40 100644
--- a/components/net/sf/briar/protocol/AuthorReader.java
+++ b/components/net/sf/briar/protocol/AuthorReader.java
@@ -11,14 +11,11 @@ import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class AuthorReader implements ObjectReader<Author> {
 
 	private final MessageDigest messageDigest;
 	private final AuthorFactory authorFactory;
 
-	@Inject
 	AuthorReader(CryptoComponent crypto, AuthorFactory authorFactory) {
 		messageDigest = crypto.getMessageDigest();
 		this.authorFactory = authorFactory;
diff --git a/components/net/sf/briar/protocol/BatchReader.java b/components/net/sf/briar/protocol/BatchReader.java
index 15ccb655cd..bc41e694e8 100644
--- a/components/net/sf/briar/protocol/BatchReader.java
+++ b/components/net/sf/briar/protocol/BatchReader.java
@@ -13,15 +13,12 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class BatchReader implements ObjectReader<Batch> {
 
 	private final MessageDigest messageDigest;
 	private final ObjectReader<Message> messageReader;
 	private final BatchFactory batchFactory;
 
-	@Inject
 	BatchReader(CryptoComponent crypto, ObjectReader<Message> messageReader,
 			BatchFactory batchFactory) {
 		messageDigest = crypto.getMessageDigest();
diff --git a/components/net/sf/briar/protocol/GroupReader.java b/components/net/sf/briar/protocol/GroupReader.java
index dd61419a03..d855850dd4 100644
--- a/components/net/sf/briar/protocol/GroupReader.java
+++ b/components/net/sf/briar/protocol/GroupReader.java
@@ -11,14 +11,11 @@ import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class GroupReader implements ObjectReader<Group> {
 
 	private final MessageDigest messageDigest;
 	private final GroupFactory groupFactory;
 
-	@Inject
 	GroupReader(CryptoComponent crypto, GroupFactory groupFactory) {
 		messageDigest = crypto.getMessageDigest();
 		this.groupFactory = groupFactory;
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
index 96b6824c5d..5319c11710 100644
--- a/components/net/sf/briar/protocol/MessageReader.java
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -18,8 +18,6 @@ import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class MessageReader implements ObjectReader<Message> {
 
 	private final ObjectReader<MessageId> messageIdReader;
@@ -29,7 +27,6 @@ class MessageReader implements ObjectReader<Message> {
 	private final Signature signature;
 	private final MessageDigest messageDigest;
 
-	@Inject
 	MessageReader(CryptoComponent crypto,
 			ObjectReader<MessageId> messageIdReader,
 			ObjectReader<Group> groupReader,
diff --git a/components/net/sf/briar/protocol/OfferFactory.java b/components/net/sf/briar/protocol/OfferFactory.java
index 6f19d4e291..c9a1c33cc7 100644
--- a/components/net/sf/briar/protocol/OfferFactory.java
+++ b/components/net/sf/briar/protocol/OfferFactory.java
@@ -4,8 +4,9 @@ import java.util.Collection;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 
 interface OfferFactory {
 
-	Offer createOffer(Collection<MessageId> offered);
+	Offer createOffer(OfferId id, Collection<MessageId> offered);
 }
diff --git a/components/net/sf/briar/protocol/OfferFactoryImpl.java b/components/net/sf/briar/protocol/OfferFactoryImpl.java
index 075527d147..f76802c205 100644
--- a/components/net/sf/briar/protocol/OfferFactoryImpl.java
+++ b/components/net/sf/briar/protocol/OfferFactoryImpl.java
@@ -4,10 +4,11 @@ import java.util.Collection;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 
 class OfferFactoryImpl implements OfferFactory {
 
-	public Offer createOffer(Collection<MessageId> offered) {
-		return new OfferImpl(offered);
+	public Offer createOffer(OfferId id, Collection<MessageId> offered) {
+		return new OfferImpl(id, offered);
 	}
 }
diff --git a/components/net/sf/briar/protocol/OfferIdReader.java b/components/net/sf/briar/protocol/OfferIdReader.java
new file mode 100644
index 0000000000..52485760cf
--- /dev/null
+++ b/components/net/sf/briar/protocol/OfferIdReader.java
@@ -0,0 +1,20 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+
+import net.sf.briar.api.protocol.OfferId;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.UniqueId;
+import net.sf.briar.api.serial.FormatException;
+import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+class OfferIdReader implements ObjectReader<OfferId> {
+
+	public OfferId readObject(Reader r) throws IOException {
+		r.readUserDefinedTag(Tags.OFFER_ID);
+		byte[] b = r.readBytes(UniqueId.LENGTH);
+		if(b.length != UniqueId.LENGTH) throw new FormatException();
+		return new OfferId(b);
+	}
+}
diff --git a/components/net/sf/briar/protocol/OfferImpl.java b/components/net/sf/briar/protocol/OfferImpl.java
index de892202e6..0ce2e6bfe9 100644
--- a/components/net/sf/briar/protocol/OfferImpl.java
+++ b/components/net/sf/briar/protocol/OfferImpl.java
@@ -4,15 +4,22 @@ import java.util.Collection;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 
 class OfferImpl implements Offer {
 
+	private final OfferId id;
 	private final Collection<MessageId> offered;
 
-	OfferImpl(Collection<MessageId> offered) {
+	OfferImpl(OfferId id, Collection<MessageId> offered) {
+		this.id = id;
 		this.offered = offered;
 	}
 
+	public OfferId getId() {
+		return id;
+	}
+
 	public Collection<MessageId> getMessageIds() {
 		return offered;
 	}
diff --git a/components/net/sf/briar/protocol/OfferReader.java b/components/net/sf/briar/protocol/OfferReader.java
index 459310dec1..e269e865a6 100644
--- a/components/net/sf/briar/protocol/OfferReader.java
+++ b/components/net/sf/briar/protocol/OfferReader.java
@@ -1,40 +1,47 @@
 package net.sf.briar.protocol;
 
 import java.io.IOException;
+import java.security.MessageDigest;
 import java.util.Collection;
 
+import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class OfferReader implements ObjectReader<Offer> {
 
+	private final MessageDigest messageDigest;
 	private final ObjectReader<MessageId> messageIdReader;
 	private final OfferFactory offerFactory;
 
-	@Inject
-	OfferReader(ObjectReader<MessageId> messageIdReader,
+	OfferReader(CryptoComponent crypto, ObjectReader<MessageId> messageIdReader,
 			OfferFactory offerFactory) {
+		messageDigest = crypto.getMessageDigest();
 		this.messageIdReader = messageIdReader;
 		this.offerFactory = offerFactory;
 	}
 
 	public Offer readObject(Reader r) throws IOException {
-		// Initialise the consumer
+		// Initialise the consumers
 		Consumer counting = new CountingConsumer(Offer.MAX_SIZE);
+		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
+		messageDigest.reset();
 		// Read the data
 		r.addConsumer(counting);
+		r.addConsumer(digesting);
 		r.readUserDefinedTag(Tags.OFFER);
 		r.addObjectReader(Tags.MESSAGE_ID, messageIdReader);
 		Collection<MessageId> messages = r.readList(MessageId.class);
 		r.removeObjectReader(Tags.MESSAGE_ID);
+		r.removeConsumer(digesting);
 		r.removeConsumer(counting);
 		// Build and return the offer
-		return offerFactory.createOffer(messages);
+		OfferId id = new OfferId(messageDigest.digest());
+		return offerFactory.createOffer(id, messages);
 	}
 }
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index 6d36d8efb3..79d4bac586 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -12,6 +12,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.ProtocolReaderFactory;
 import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.SubscriptionUpdate;
@@ -81,14 +82,21 @@ public class ProtocolModule extends AbstractModule {
 	}
 
 	@Provides
-	ObjectReader<Offer> getOfferReader(ObjectReader<MessageId> messageIdReader,
+	ObjectReader<OfferId> getOfferIdReader() {
+		return new OfferIdReader();
+	}
+
+	@Provides
+	ObjectReader<Offer> getOfferReader(CryptoComponent crypto,
+			ObjectReader<MessageId> messageIdReader,
 			OfferFactory offerFactory) {
-		return new OfferReader(messageIdReader, offerFactory);
+		return new OfferReader(crypto, messageIdReader, offerFactory);
 	}
 
 	@Provides
-	ObjectReader<Request> getRequestReader(RequestFactory requestFactory) {
-		return new RequestReader(requestFactory);
+	ObjectReader<Request> getRequestReader(ObjectReader<OfferId> offerIdReader,
+			RequestFactory requestFactory) {
+		return new RequestReader(offerIdReader, requestFactory);
 	}
 
 	@Provides
diff --git a/components/net/sf/briar/protocol/RequestFactory.java b/components/net/sf/briar/protocol/RequestFactory.java
index 005982b826..09459765a6 100644
--- a/components/net/sf/briar/protocol/RequestFactory.java
+++ b/components/net/sf/briar/protocol/RequestFactory.java
@@ -2,9 +2,10 @@ package net.sf.briar.protocol;
 
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 
 interface RequestFactory {
 
-	Request createRequest(BitSet requested);
+	Request createRequest(OfferId offerId, BitSet requested);
 }
diff --git a/components/net/sf/briar/protocol/RequestFactoryImpl.java b/components/net/sf/briar/protocol/RequestFactoryImpl.java
index 0c2c77cb1c..68cce91c54 100644
--- a/components/net/sf/briar/protocol/RequestFactoryImpl.java
+++ b/components/net/sf/briar/protocol/RequestFactoryImpl.java
@@ -2,11 +2,12 @@ package net.sf.briar.protocol;
 
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 
 class RequestFactoryImpl implements RequestFactory {
 
-	public Request createRequest(BitSet requested) {
-		return new RequestImpl(requested);
+	public Request createRequest(OfferId offerId, BitSet requested) {
+		return new RequestImpl(offerId, requested);
 	}
 }
diff --git a/components/net/sf/briar/protocol/RequestImpl.java b/components/net/sf/briar/protocol/RequestImpl.java
index ddb31898ae..42d3247ee9 100644
--- a/components/net/sf/briar/protocol/RequestImpl.java
+++ b/components/net/sf/briar/protocol/RequestImpl.java
@@ -2,16 +2,23 @@ package net.sf.briar.protocol;
 
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 
 class RequestImpl implements Request {
 
+	private final OfferId offerId;
 	private final BitSet requested;
 
-	RequestImpl(BitSet requested) {
+	RequestImpl(OfferId offerId, BitSet requested) {
+		this.offerId = offerId;
 		this.requested = requested;
 	}
 
+	public OfferId getOfferId() {
+		return offerId;
+	}
+
 	public BitSet getBitmap() {
 		return requested;
 	}
diff --git a/components/net/sf/briar/protocol/RequestReader.java b/components/net/sf/briar/protocol/RequestReader.java
index 5677790076..901a557564 100644
--- a/components/net/sf/briar/protocol/RequestReader.java
+++ b/components/net/sf/briar/protocol/RequestReader.java
@@ -3,20 +3,21 @@ package net.sf.briar.protocol;
 import java.io.IOException;
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class RequestReader implements ObjectReader<Request> {
 
+	private final ObjectReader<OfferId> offerIdReader;
 	private final RequestFactory requestFactory;
 
-	@Inject
-	RequestReader(RequestFactory requestFactory) {
+	RequestReader(ObjectReader<OfferId> offerIdReader,
+			RequestFactory requestFactory) {
+		this.offerIdReader = offerIdReader;
 		this.requestFactory = requestFactory;
 	}
 
@@ -26,6 +27,8 @@ class RequestReader implements ObjectReader<Request> {
 		// Read the data
 		r.addConsumer(counting);
 		r.readUserDefinedTag(Tags.REQUEST);
+		r.addObjectReader(Tags.OFFER_ID, offerIdReader);
+		OfferId offerId = r.readUserDefined(Tags.OFFER_ID, OfferId.class);
 		byte[] bitmap = r.readBytes(Request.MAX_SIZE);
 		r.removeConsumer(counting);
 		// Convert the bitmap into a BitSet
@@ -36,6 +39,6 @@ class RequestReader implements ObjectReader<Request> {
 				if((bitmap[i] & bit) != 0) b.set(i * 8 + j);
 			}
 		}
-		return requestFactory.createRequest(b);
+		return requestFactory.createRequest(offerId, b);
 	}
 }
diff --git a/components/net/sf/briar/protocol/SubscriptionReader.java b/components/net/sf/briar/protocol/SubscriptionReader.java
index 6c31dad6fb..8faaf647e1 100644
--- a/components/net/sf/briar/protocol/SubscriptionReader.java
+++ b/components/net/sf/briar/protocol/SubscriptionReader.java
@@ -10,14 +10,11 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class SubscriptionReader implements ObjectReader<SubscriptionUpdate> {
 
 	private final ObjectReader<Group> groupReader;
 	private final SubscriptionFactory subscriptionFactory;
 
-	@Inject
 	SubscriptionReader(ObjectReader<Group> groupReader,
 			SubscriptionFactory subscriptionFactory) {
 		this.groupReader = groupReader;
diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java
index 92a2a47905..2ea5e61b3b 100644
--- a/components/net/sf/briar/protocol/TransportReader.java
+++ b/components/net/sf/briar/protocol/TransportReader.java
@@ -10,13 +10,10 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class TransportReader implements ObjectReader<TransportUpdate> {
 
 	private final TransportFactory transportFactory;
 
-	@Inject
 	TransportReader(TransportFactory transportFactory) {
 		this.transportFactory = transportFactory;
 	}
diff --git a/components/net/sf/briar/protocol/writers/OfferWriterImpl.java b/components/net/sf/briar/protocol/writers/OfferWriterImpl.java
index c0ec46fe94..7bbac598df 100644
--- a/components/net/sf/briar/protocol/writers/OfferWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/OfferWriterImpl.java
@@ -2,9 +2,12 @@ package net.sf.briar.protocol.writers;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.writers.OfferWriter;
 import net.sf.briar.api.serial.Writer;
@@ -14,17 +17,20 @@ class OfferWriterImpl implements OfferWriter {
 
 	private final OutputStream out;
 	private final Writer w;
+	private final MessageDigest messageDigest;
 
-	private boolean started = false, finished = false;
+	private boolean started = false;
 
-	OfferWriterImpl(OutputStream out, WriterFactory writerFactory) {
-		this.out = out;
+	OfferWriterImpl(OutputStream out, WriterFactory writerFactory,
+			MessageDigest messageDigest) {
+		this.out = new DigestOutputStream(out, messageDigest);
 		w = writerFactory.createWriter(out);
+		this.messageDigest = messageDigest;
 	}
 
 	public boolean writeMessageId(MessageId m) throws IOException {
-		if(finished) throw new IllegalStateException();
 		if(!started) {
+			messageDigest.reset();
 			w.writeUserDefinedTag(Tags.OFFER);
 			w.writeListStart();
 			started = true;
@@ -35,15 +41,16 @@ class OfferWriterImpl implements OfferWriter {
 		return true;
 	}
 
-	public void finish() throws IOException {
-		if(finished) throw new IllegalStateException();
+	public OfferId finish() throws IOException {
 		if(!started) {
+			messageDigest.reset();
 			w.writeUserDefinedTag(Tags.OFFER);
 			w.writeListStart();
 			started = true;
 		}
 		w.writeListEnd();
 		out.flush();
-		finished = true;
+		started = false;
+		return new OfferId(messageDigest.digest());
 	}
 }
diff --git a/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java b/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java
index 2599940367..ce14908f1c 100644
--- a/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java
+++ b/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java
@@ -36,7 +36,7 @@ class ProtocolWriterFactoryImpl implements ProtocolWriterFactory {
 	}
 
 	public OfferWriter createOfferWriter(OutputStream out) {
-		return new OfferWriterImpl(out, writerFactory);
+		return new OfferWriterImpl(out, writerFactory, messageDigest);
 	}
 
 	public RequestWriter createRequestWriter(OutputStream out) {
diff --git a/components/net/sf/briar/protocol/writers/RequestWriterImpl.java b/components/net/sf/briar/protocol/writers/RequestWriterImpl.java
index 28bce226a2..d50909dabb 100644
--- a/components/net/sf/briar/protocol/writers/RequestWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/RequestWriterImpl.java
@@ -4,6 +4,7 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.writers.RequestWriter;
 import net.sf.briar.api.serial.Writer;
@@ -19,8 +20,11 @@ class RequestWriterImpl implements RequestWriter {
 		w = writerFactory.createWriter(out);
 	}
 
-	public void writeBitmap(BitSet b, int length) throws IOException {
+	public void writeRequest(OfferId offerId, BitSet b, int length)
+	throws IOException {
 		w.writeUserDefinedTag(Tags.REQUEST);
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(offerId.getBytes());
 		// 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];
diff --git a/test/net/sf/briar/FileReadWriteTest.java b/test/net/sf/briar/FileReadWriteTest.java
index 490c0d08b0..4988507957 100644
--- a/test/net/sf/briar/FileReadWriteTest.java
+++ b/test/net/sf/briar/FileReadWriteTest.java
@@ -27,6 +27,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.ProtocolReader;
 import net.sf.briar.api.protocol.ProtocolReaderFactory;
 import net.sf.briar.api.protocol.Request;
@@ -78,6 +79,7 @@ public class FileReadWriteTest extends TestCase {
 	private final Message message, message1, message2, message3;
 	private final String authorName = "Alice";
 	private final String messageBody = "Hello world";
+	private final OfferId offerId;
 	private final Map<String, Map<String, String>> transports;
 
 	public FileReadWriteTest() throws Exception {
@@ -114,6 +116,7 @@ public class FileReadWriteTest extends TestCase {
 		message3 = messageEncoder.encodeMessage(MessageId.NONE, group1,
 				groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
 				messageBody.getBytes("UTF-8"));
+		offerId = new OfferId(TestUtils.getRandomId());
 		transports = Collections.singletonMap("foo",
 				Collections.singletonMap("bar", "baz"));
 	}
@@ -155,7 +158,7 @@ public class FileReadWriteTest extends TestCase {
 		BitSet requested = new BitSet(4);
 		requested.set(1);
 		requested.set(3);
-		r.writeBitmap(requested, 4);
+		r.writeRequest(offerId, requested, 4);
 		packetWriter.finishPacket();
 
 		SubscriptionWriter s =
@@ -231,6 +234,7 @@ public class FileReadWriteTest extends TestCase {
 		assertTrue(protocolReader.hasRequest());
 		Request r = protocolReader.readRequest();
 		packetReader.finishPacket();
+		assertEquals(offerId, r.getOfferId());
 		BitSet requested = r.getBitmap();
 		assertFalse(requested.get(0));
 		assertTrue(requested.get(1));
diff --git a/test/net/sf/briar/db/DatabaseCleanerImplTest.java b/test/net/sf/briar/db/DatabaseCleanerImplTest.java
index 691574da04..1042338aea 100644
--- a/test/net/sf/briar/db/DatabaseCleanerImplTest.java
+++ b/test/net/sf/briar/db/DatabaseCleanerImplTest.java
@@ -12,7 +12,7 @@ import org.junit.Test;
 public class DatabaseCleanerImplTest extends TestCase {
 
 	@Test
-	public void testStoppingCleanerWakesItUp() throws DbException {
+	public void testStoppingCleanerWakesItUp() throws Exception {
 		final CountDownLatch latch = new CountDownLatch(1);
 		Callback callback = new Callback() {
 
@@ -31,11 +31,7 @@ public class DatabaseCleanerImplTest extends TestCase {
 		long start = System.currentTimeMillis();
 		// Start the cleaner and check that shouldCheckFreeSpace() is called
 		cleaner.startCleaning();
-		try {
-			assertTrue(latch.await(5, TimeUnit.SECONDS));
-		} catch(InterruptedException e) {
-			fail();
-		}
+		assertTrue(latch.await(5, TimeUnit.SECONDS));
 		// Stop the cleaner (it should be waiting between sweeps)
 		cleaner.stopCleaning();
 		long end = System.currentTimeMillis();
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 193f4392ea..f49ce59077 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -12,10 +12,10 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseListener;
+import net.sf.briar.api.db.DatabaseListener.Event;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.Status;
-import net.sf.briar.api.db.DatabaseListener.Event;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -25,6 +25,7 @@ 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.OfferId;
 import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.TransportUpdate;
 import net.sf.briar.api.protocol.writers.AckWriter;
@@ -49,6 +50,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 	protected final ContactId contactId;
 	protected final GroupId groupId;
 	protected final MessageId messageId, parentId;
+	protected final OfferId offerId;
 	private final long timestamp;
 	private final int size;
 	private final byte[] raw;
@@ -65,6 +67,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		groupId = new GroupId(TestUtils.getRandomId());
 		messageId = new MessageId(TestUtils.getRandomId());
 		parentId = new MessageId(TestUtils.getRandomId());
+		offerId = new OfferId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
@@ -1031,7 +1034,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(true)); // Visible - do not request message # 1
 			oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2);
 			will(returnValue(false)); // Not visible - request message # 2
-			oneOf(requestWriter).writeBitmap(expectedRequest, 3);
+			oneOf(offer).getId();
+			will(returnValue(offerId));
+			oneOf(requestWriter).writeRequest(offerId, expectedRequest, 3);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
diff --git a/test/net/sf/briar/protocol/ProtocolReadWriteTest.java b/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
index cee401bc54..3757ad9f04 100644
--- a/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
+++ b/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
@@ -17,6 +17,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.ProtocolReader;
 import net.sf.briar.api.protocol.ProtocolReaderFactory;
 import net.sf.briar.api.protocol.Request;
@@ -46,6 +47,7 @@ public class ProtocolReadWriteTest extends TestCase {
 	private final Group group;
 	private final Message message;
 	private final String messageBody = "Hello world";
+	private final OfferId offerId;
 	private final BitSet bitSet;
 	private final Map<Group, Long> subscriptions;
 	private final Map<String, Map<String, String>> transports;
@@ -62,6 +64,7 @@ public class ProtocolReadWriteTest extends TestCase {
 		MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
 		message = messageEncoder.encodeMessage(MessageId.NONE, group,
 				messageBody.getBytes("UTF-8"));
+		offerId = new OfferId(TestUtils.getRandomId());
 		bitSet = new BitSet();
 		bitSet.set(3);
 		bitSet.set(7);
@@ -90,7 +93,7 @@ public class ProtocolReadWriteTest extends TestCase {
 		o.finish();
 
 		RequestWriter r = writerFactory.createRequestWriter(out);
-		r.writeBitmap(bitSet, 10);
+		r.writeRequest(offerId, bitSet, 10);
 
 		SubscriptionWriter s = writerFactory.createSubscriptionWriter(out);
 		s.writeSubscriptions(subscriptions);
diff --git a/test/net/sf/briar/protocol/RequestReaderTest.java b/test/net/sf/briar/protocol/RequestReaderTest.java
index 4ff8865903..b9c2be3ef8 100644
--- a/test/net/sf/briar/protocol/RequestReaderTest.java
+++ b/test/net/sf/briar/protocol/RequestReaderTest.java
@@ -5,8 +5,10 @@ import java.io.ByteArrayOutputStream;
 import java.util.BitSet;
 
 import junit.framework.TestCase;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
@@ -38,7 +40,8 @@ public class RequestReaderTest extends TestCase {
 	@Test
 	public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
 		RequestFactory requestFactory = context.mock(RequestFactory.class);
-		RequestReader requestReader = new RequestReader(requestFactory);
+		RequestReader requestReader =
+			new RequestReader(new OfferIdReader(), requestFactory);
 
 		byte[] b = createRequest(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
@@ -56,10 +59,12 @@ public class RequestReaderTest extends TestCase {
 	public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
 		final RequestFactory requestFactory =
 			context.mock(RequestFactory.class);
-		RequestReader requestReader = new RequestReader(requestFactory);
+		RequestReader requestReader =
+			new RequestReader(new OfferIdReader(), requestFactory);
 		final Request request = context.mock(Request.class);
 		context.checking(new Expectations() {{
-			oneOf(requestFactory).createRequest(with(any(BitSet.class)));
+			oneOf(requestFactory).createRequest(with(any(OfferId.class)),
+					with(any(BitSet.class)));
 			will(returnValue(request));
 		}});
 
@@ -95,8 +100,8 @@ public class RequestReaderTest extends TestCase {
 			// Deserialise the request
 			ByteArrayInputStream in = new ByteArrayInputStream(b);
 			Reader reader = readerFactory.createReader(in);
-			RequestReader requestReader =
-				new RequestReader(new RequestFactoryImpl());
+			RequestReader requestReader = new RequestReader(new OfferIdReader(),
+						new RequestFactoryImpl());
 			reader.addObjectReader(Tags.REQUEST, requestReader);
 			Request r = reader.readUserDefined(Tags.REQUEST, Request.class);
 			BitSet decoded = r.getBitmap();
@@ -115,10 +120,15 @@ public class RequestReaderTest extends TestCase {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		w.writeUserDefinedTag(Tags.REQUEST);
-		// Allow one byte for the REQUEST tag, one byte for the BYTES tag, and
-		// five bytes for the length as an int32
-		if(tooBig) w.writeBytes(new byte[Request.MAX_SIZE - 6]);
-		else w.writeBytes(new byte[Request.MAX_SIZE - 7]);
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(new byte[UniqueId.LENGTH]);
+		// Allow one byte for the REQUEST tag, one byte for the OFFER_ID tag,
+		// one byte for the BYTES tag, one byte for the length as a uint7,
+		// UniqueID.LENGTH bytes for the offer ID, one byte for the BYTES tag,
+		// and five bytes for the length as an int32
+		int overhead = UniqueId.LENGTH + 10;
+		if(tooBig) w.writeBytes(new byte[Request.MAX_SIZE - overhead + 1]);
+		else w.writeBytes(new byte[Request.MAX_SIZE - overhead]);
 		assertEquals(tooBig, out.size() > Request.MAX_SIZE);
 		return out.toByteArray();
 	}
@@ -127,6 +137,8 @@ public class RequestReaderTest extends TestCase {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		w.writeUserDefinedTag(Tags.REQUEST);
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(new byte[UniqueId.LENGTH]);
 		w.writeBytes(bitmap);
 		return out.toByteArray();
 	}
diff --git a/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java b/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java
index b612f95bb1..e7e5d7e73f 100644
--- a/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java
+++ b/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java
@@ -5,6 +5,8 @@ import java.io.IOException;
 import java.util.BitSet;
 
 import junit.framework.TestCase;
+import net.sf.briar.api.protocol.OfferId;
+import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.protocol.writers.RequestWriter;
 import net.sf.briar.api.serial.WriterFactory;
 import net.sf.briar.serial.SerialModule;
@@ -18,11 +20,13 @@ import com.google.inject.Injector;
 public class RequestWriterImplTest extends TestCase {
 
 	private final WriterFactory writerFactory;
+	private final OfferId offerId;
 
 	public RequestWriterImplTest() {
 		super();
 		Injector i = Guice.createInjector(new SerialModule());
 		writerFactory = i.getInstance(WriterFactory.class);
+		offerId = new OfferId(new byte[UniqueId.LENGTH]);
 	}
 
 	@Test
@@ -41,10 +45,14 @@ public class RequestWriterImplTest extends TestCase {
 		b.set(11);
 		b.set(12);
 		b.set(15);
-		r.writeBitmap(b, 16);
-		// Short user tag 10, short bytes with length 2, 0xD959
+		r.writeRequest(offerId, b, 16);
+		// Short user tag 11, short user tag 10, bytes with length 32 as a
+		// uint7, 32 zero bytes, short bytes with length 2, 0xD959
 		byte[] output = out.toByteArray();
-		assertEquals("CA" + "92" + "D959", StringUtils.toHexString(output));
+		assertEquals("CB" + "CA" + "F6" + "20"
+				+ "00000000000000000000000000000000"
+				+ "00000000000000000000000000000000"
+				+ "92" + "D959", StringUtils.toHexString(output));
 	}
 
 	@Test
@@ -62,9 +70,13 @@ public class RequestWriterImplTest extends TestCase {
 		b.set(9);
 		b.set(11);
 		b.set(12);
-		r.writeBitmap(b, 13);
-		// Short user tag 10, short bytes with length 2, 0x59D8
+		r.writeRequest(offerId, b, 13);
+		// Short user tag 11, short user tag 10, bytes with length 32 as a
+		// uint7, 32 zero bytes, short bytes with length 2, 0x59D8
 		byte[] output = out.toByteArray();
-		assertEquals("CA" + "92" + "59D8", StringUtils.toHexString(output));
+		assertEquals("CB" + "CA" + "F6" + "20"
+				+ "00000000000000000000000000000000"
+				+ "00000000000000000000000000000000"
+				+ "92" + "59D8", StringUtils.toHexString(output));
 	}
 }
-- 
GitLab