From ec29c4d1d38d7a42ebd6fbaadc56b7cd9dbe3adb Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 4 Aug 2011 11:07:28 +0100 Subject: [PATCH] Changed the format of transport properties from (key, value) pairs to (transport name, key, value) triples. This makes it possible for each transport plugin to update its locally stored properties atomically. --- .../sf/briar/api/db/DatabaseComponent.java | 14 +- api/net/sf/briar/api/protocol/Transports.java | 2 +- .../api/protocol/writers/TransportWriter.java | 3 +- components/net/sf/briar/db/Database.java | 17 +- components/net/sf/briar/db/JdbcDatabase.java | 176 +++++++++++------- .../db/ReadWriteLockDatabaseComponent.java | 25 ++- .../db/SynchronizedDatabaseComponent.java | 25 ++- .../sf/briar/invitation/InvitationWorker.java | 2 +- .../sf/briar/protocol/TransportFactory.java | 3 +- .../briar/protocol/TransportFactoryImpl.java | 2 +- .../sf/briar/protocol/TransportReader.java | 21 ++- .../net/sf/briar/protocol/TransportsImpl.java | 7 +- .../protocol/writers/TransportWriterImpl.java | 16 +- .../sf/briar/db/DatabaseComponentTest.java | 17 +- test/net/sf/briar/db/H2DatabaseTest.java | 98 +++++----- .../sf/briar/protocol/FileReadWriteTest.java | 8 +- 16 files changed, 264 insertions(+), 172 deletions(-) diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 7fc3908bf..4bbbaec88 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -58,7 +58,8 @@ public interface DatabaseComponent { * Adds a new contact to the database with the given transport properties * and returns an ID for the contact. */ - ContactId addContact(Map transports) throws DbException; + ContactId addContact(Map> transports) + throws DbException; /** Adds a locally generated message to the database. */ void addLocallyGeneratedMessage(Message m) throws DbException; @@ -111,10 +112,11 @@ public interface DatabaseComponent { Collection getSubscriptions() throws DbException; /** Returns the local transport properties. */ - Map getTransports() throws DbException; + Map> getTransports() throws DbException; /** Returns the transport properties for the given contact. */ - Map getTransports(ContactId c) throws DbException; + Map> getTransports(ContactId c) + throws DbException; /** Returns the contacts to which the given group is visible. */ Collection getVisibility(GroupId g) throws DbException; @@ -152,9 +154,11 @@ public interface DatabaseComponent { void setRating(AuthorId a, Rating r) throws DbException; /** - * Sets the local transport properties, replacing any existing properties. + * Sets the local transport properties for the transport with the given + * name, replacing any existing properties for that transport. */ - void setTransports(Map transports) throws DbException; + void setTransports(String name, Map transports) + throws DbException; /** * Makes the given group visible to the given set of contacts and invisible diff --git a/api/net/sf/briar/api/protocol/Transports.java b/api/net/sf/briar/api/protocol/Transports.java index d18f5ccd3..8af5d09ac 100644 --- a/api/net/sf/briar/api/protocol/Transports.java +++ b/api/net/sf/briar/api/protocol/Transports.java @@ -12,7 +12,7 @@ public interface Transports { static final int MAX_SIZE = (1024 * 1024) - 100; /** Returns the transports contained in the update. */ - Map getTransports(); + Map> getTransports(); /** * Returns the update's timestamp. Updates that are older than the newest diff --git a/api/net/sf/briar/api/protocol/writers/TransportWriter.java b/api/net/sf/briar/api/protocol/writers/TransportWriter.java index 6e03d2f56..f835e0059 100644 --- a/api/net/sf/briar/api/protocol/writers/TransportWriter.java +++ b/api/net/sf/briar/api/protocol/writers/TransportWriter.java @@ -7,5 +7,6 @@ import java.util.Map; public interface TransportWriter { /** Writes the contents of the update. */ - void writeTransports(Map transports) throws IOException; + void writeTransports(Map> transports) + throws IOException; } diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 02eabb4dc..f60389303 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -82,7 +82,7 @@ interface Database { *

* Locking: contacts write, transports write. */ - ContactId addContact(T txn, Map transports) + ContactId addContact(T txn, Map> transports) throws DbException; /** @@ -272,14 +272,15 @@ interface Database { *

* Locking: transports read. */ - Map getTransports(T txn) throws DbException; + Map> getTransports(T txn) throws DbException; /** * Returns the transport properties for the given contact. *

* Locking: contacts read, transports read. */ - Map getTransports(T txn, ContactId c) throws DbException; + Map> getTransports(T txn, ContactId c) + throws DbException; /** * Returns the contacts to which the given group is visible. @@ -397,11 +398,12 @@ interface Database { long timestamp) throws DbException; /** - * Sets the local transport properties, replacing any existing properties. + * Sets the local transport properties for the transport with the given + * name, replacing any existing properties for that transport. *

* Locking: transports write. */ - void setTransports(T txn, Map transports) + void setTransports(T txn, String name, Map transports) throws DbException; /** @@ -411,8 +413,9 @@ interface Database { *

* Locking: contacts write, transports write. */ - void setTransports(T txn, ContactId c, Map transports, - long timestamp) throws DbException; + void setTransports(T txn, ContactId c, + Map> transports, long timestamp) + throws DbException; /** * Makes the given group visible to the given set of contacts and invisible diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index bb09069af..f31fefffd 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -159,17 +159,19 @@ abstract class JdbcDatabase implements Database { private static final String CREATE_CONTACT_TRANSPORTS = "CREATE TABLE contactTransports" + " (contactId INT NOT NULL," + + " transportName VARCHAR NOT NULL," + " key VARCHAR NOT NULL," + " value VARCHAR NOT NULL," - + " PRIMARY KEY (contactId, key)," + + " PRIMARY KEY (contactId, transportName, key)," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - private static final String CREATE_LOCAL_TRANSPORTS = - "CREATE TABLE localTransports" - + " (key VARCHAR NOT NULL," + private static final String CREATE_TRANSPORTS = + "CREATE TABLE transports" + + " (transportName VARCHAR NOT NULL," + + " key VARCHAR NOT NULL," + " value VARCHAR NOT NULL," - + " PRIMARY KEY (key))"; + + " PRIMARY KEY (transportName, key))"; private static final Logger LOG = Logger.getLogger(JdbcDatabase.class.getName()); @@ -252,7 +254,7 @@ abstract class JdbcDatabase implements Database { s.executeUpdate(INDEX_STATUSES_BY_MESSAGE); s.executeUpdate(INDEX_STATUSES_BY_CONTACT); s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS)); - s.executeUpdate(insertTypeNames(CREATE_LOCAL_TRANSPORTS)); + s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS)); s.close(); } catch(SQLException e) { tryToClose(s); @@ -406,7 +408,8 @@ abstract class JdbcDatabase implements Database { } } - public ContactId addContact(Connection txn, Map transports) + public ContactId addContact(Connection txn, + Map> transports) throws DbException { PreparedStatement ps = null; ResultSet rs = null; @@ -434,24 +437,27 @@ abstract class JdbcDatabase implements Database { if(affected != 1) throw new DbStateException(); ps.close(); // Store the contact's transport properties - if(transports != null) { - sql = "INSERT INTO contactTransports (contactId, key, value)" - + " VALUES (?, ?, ?)"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - for(Entry e : transports.entrySet()) { - ps.setString(2, e.getKey()); - ps.setString(3, e.getValue()); + sql = "INSERT INTO contactTransports" + + " (contactId, transportName, key, value)" + + " VALUES (?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + int batchSize = 0; + for(Entry> e : transports.entrySet()) { + ps.setString(2, e.getKey()); + for(Entry e1 : e.getValue().entrySet()) { + ps.setString(3, e1.getKey()); + ps.setString(4, e1.getValue()); ps.addBatch(); + batchSize++; } - int[] batchAffected = ps.executeBatch(); - if(batchAffected.length != transports.size()) - throw new DbStateException(); - for(int i = 0; i < batchAffected.length; i++) { - if(batchAffected[i] != 1) throw new DbStateException(); - } - ps.close(); } + 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(); + } + ps.close(); return c; } catch(SQLException e) { tryToClose(ps); @@ -1119,19 +1125,31 @@ abstract class JdbcDatabase implements Database { } } - public Map getTransports(Connection txn) + public Map> getTransports(Connection txn) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT key, value FROM localTransports"; + String sql = "SELECT transportName, key, value" + + " FROM transports" + + " ORDER BY transportName"; ps = txn.prepareStatement(sql); rs = ps.executeQuery(); - Map transports = new TreeMap(); - while(rs.next()) transports.put(rs.getString(1), rs.getString(2)); + Map> outer = + new TreeMap>(); + Map inner = null; + String lastName = null; + while(rs.next()) { + String name = rs.getString(1); + if(!name.equals(lastName)) { + inner = new TreeMap(); + outer.put(name, inner); + } + inner.put(rs.getString(2), rs.getString(3)); + } rs.close(); ps.close(); - return transports; + return outer; } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1139,21 +1157,33 @@ abstract class JdbcDatabase implements Database { } } - public Map getTransports(Connection txn, ContactId c) - throws DbException { + public Map> getTransports(Connection txn, + ContactId c) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT key, value FROM contactTransports" - + " WHERE contactId = ?"; + String sql = "SELECT transportName, key, value" + + " FROM contactTransports" + + " WHERE contactId = ?" + + " ORDER BY transportName"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); rs = ps.executeQuery(); - Map transports = new TreeMap(); - while(rs.next()) transports.put(rs.getString(1), rs.getString(2)); + Map> outer = + new TreeMap>(); + Map inner = null; + String lastName = null; + while(rs.next()) { + String name = rs.getString(1); + if(!name.equals(lastName)) { + inner = new TreeMap(); + outer.put(name, inner); + } + inner.put(rs.getString(2), rs.getString(3)); + } rs.close(); ps.close(); - return transports; + return outer; } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1579,33 +1609,33 @@ abstract class JdbcDatabase implements Database { } } - public void setTransports(Connection txn, Map transports) - throws DbException { + public void setTransports(Connection txn, String name, + Map transports) throws DbException { PreparedStatement ps = null; try { - // Delete any existing transports - String sql = "DELETE FROM localTransports"; + // Delete any existing properties for the named transport + String sql = "DELETE FROM transports WHERE transportName = ?"; ps = txn.prepareStatement(sql); + ps.setString(1, name); ps.executeUpdate(); ps.close(); - // Store the new transports - if(!transports.isEmpty()) { - sql = "INSERT INTO localTransports (key, value)" - + " VALUES (?, ?)"; - ps = txn.prepareStatement(sql); - for(Entry e : transports.entrySet()) { - ps.setString(1, e.getKey()); - ps.setString(2, e.getValue()); - ps.addBatch(); - } - int[] batchAffected = ps.executeBatch(); - if(batchAffected.length != transports.size()) - throw new DbStateException(); - for(int i = 0; i < batchAffected.length; i++) { - if(batchAffected[i] != 1) throw new DbStateException(); - } - ps.close(); + // Store the new properties + sql = "INSERT INTO transports (transportName, key, value)" + + " VALUES (?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setString(1, name); + for(Entry e : transports.entrySet()) { + ps.setString(2, e.getKey()); + ps.setString(3, e.getValue()); + ps.addBatch(); } + int[] batchAffected = ps.executeBatch(); + if(batchAffected.length != transports.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); throw new DbException(e); @@ -1613,7 +1643,8 @@ abstract class JdbcDatabase implements Database { } public void setTransports(Connection txn, ContactId c, - Map transports, long timestamp) throws DbException { + Map> transports, long timestamp) + throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -1636,24 +1667,27 @@ abstract class JdbcDatabase implements Database { ps.executeUpdate(); ps.close(); // Store the new transports - if(transports != null) { - sql = "INSERT INTO contactTransports (contactId, key, value)" - + " VALUES (?, ?, ?)"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - for(Entry e : transports.entrySet()) { - ps.setString(2, e.getKey()); - ps.setString(3, e.getValue()); + sql = "INSERT INTO contactTransports" + + " (contactId, transportName, key, value)" + + " VALUES (?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + int batchSize = 0; + for(Entry> e : transports.entrySet()) { + ps.setString(2, e.getKey()); + for(Entry e1 : e.getValue().entrySet()) { + ps.setString(3, e1.getKey()); + ps.setString(4, e1.getValue()); ps.addBatch(); + batchSize++; } - int[] batchAffected = ps.executeBatch(); - if(batchAffected.length != transports.size()) - throw new DbStateException(); - for(int i = 0; i < batchAffected.length; i++) { - if(batchAffected[i] != 1) throw new DbStateException(); - } - ps.close(); } + 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(); + } + ps.close(); // Update the timestamp sql = "UPDATE contacts SET transportsTimestamp = ?" + " WHERE contactId = ?"; diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java index 2e7aca33c..9d85dbe78 100644 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java @@ -125,7 +125,7 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } } - public ContactId addContact(Map transports) + public ContactId addContact(Map> transports) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); contactLock.writeLock().lock(); @@ -497,7 +497,8 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { try { Txn txn = db.startTransaction(); try { - Map transports = db.getTransports(txn); + Map> transports = + db.getTransports(txn); t.writeTransports(transports); if(LOG.isLoggable(Level.FINE)) LOG.fine("Added " + transports.size() + " transports"); @@ -568,12 +569,13 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } } - public Map getTransports() throws DbException { + public Map> getTransports() throws DbException { transportLock.readLock().lock(); try { Txn txn = db.startTransaction(); try { - Map transports = db.getTransports(txn); + Map> transports = + db.getTransports(txn); db.commitTransaction(txn); return transports; } catch(DbException e) { @@ -585,7 +587,8 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } } - public Map getTransports(ContactId c) throws DbException { + public Map> getTransports(ContactId c) + throws DbException { contactLock.readLock().lock(); try { if(!containsContact(c)) throw new NoSuchContactException(); @@ -593,7 +596,8 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { try { Txn txn = db.startTransaction(); try { - Map transports = db.getTransports(txn, c); + Map> transports = + db.getTransports(txn, c); db.commitTransaction(txn); return transports; } catch(DbException e) { @@ -826,7 +830,8 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { try { Txn txn = db.startTransaction(); try { - Map transports = t.getTransports(); + Map> transports = + t.getTransports(); db.setTransports(txn, c, transports, t.getTimestamp()); if(LOG.isLoggable(Level.FINE)) LOG.fine("Received " + transports.size() @@ -902,15 +907,15 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } } - public void setTransports(Map transports) + public void setTransports(String name, Map transports) throws DbException { boolean changed = false; transportLock.writeLock().lock(); try { Txn txn = db.startTransaction(); try { - if(!transports.equals(db.getTransports(txn))) { - db.setTransports(txn, transports); + if(!transports.equals(db.getTransports(txn).get(name))) { + db.setTransports(txn, name, transports); changed = true; } db.commitTransaction(txn); diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java index d0e09670c..1d7a48350 100644 --- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java +++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java @@ -96,7 +96,7 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } - public ContactId addContact(Map transports) + public ContactId addContact(Map> transports) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); synchronized(contactLock) { @@ -366,7 +366,8 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { synchronized(transportLock) { Txn txn = db.startTransaction(); try { - Map transports = db.getTransports(txn); + Map> transports = + db.getTransports(txn); t.writeTransports(transports); if(LOG.isLoggable(Level.FINE)) LOG.fine("Added " + transports.size() + " transports"); @@ -424,11 +425,12 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } - public Map getTransports() throws DbException { + public Map> getTransports() throws DbException { synchronized(transportLock) { Txn txn = db.startTransaction(); try { - Map transports = db.getTransports(txn); + Map> transports = + db.getTransports(txn); db.commitTransaction(txn); return transports; } catch(DbException e) { @@ -438,13 +440,15 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } - public Map getTransports(ContactId c) throws DbException { + public Map> getTransports(ContactId c) + throws DbException { synchronized(contactLock) { if(!containsContact(c)) throw new NoSuchContactException(); synchronized(transportLock) { Txn txn = db.startTransaction(); try { - Map transports = db.getTransports(txn, c); + Map> transports = + db.getTransports(txn, c); db.commitTransaction(txn); return transports; } catch(DbException e) { @@ -614,7 +618,8 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { synchronized(transportLock) { Txn txn = db.startTransaction(); try { - Map transports = t.getTransports(); + Map> transports = + t.getTransports(); db.setTransports(txn, c, transports, t.getTimestamp()); if(LOG.isLoggable(Level.FINE)) LOG.fine("Received " + transports.size() @@ -668,14 +673,14 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } - public void setTransports(Map transports) + public void setTransports(String name, Map transports) throws DbException { boolean changed = false; synchronized(transportLock) { Txn txn = db.startTransaction(); try { - if(!transports.equals(db.getTransports(txn))) { - db.setTransports(txn, transports); + if(!transports.equals(db.getTransports(txn).get(name))) { + db.setTransports(txn, name, transports); changed = true; } db.commitTransaction(txn); diff --git a/components/net/sf/briar/invitation/InvitationWorker.java b/components/net/sf/briar/invitation/InvitationWorker.java index 9b05d6329..dc6d5615d 100644 --- a/components/net/sf/briar/invitation/InvitationWorker.java +++ b/components/net/sf/briar/invitation/InvitationWorker.java @@ -71,7 +71,7 @@ class InvitationWorker implements Runnable { File invitationDat = new File(dir, "invitation.dat"); callback.encryptingFile(invitationDat); // FIXME: Create a real invitation - Map transports; + Map> transports; try { transports = databaseComponent.getTransports(); } catch(DbException e) { diff --git a/components/net/sf/briar/protocol/TransportFactory.java b/components/net/sf/briar/protocol/TransportFactory.java index 8a2b39725..d56c0c582 100644 --- a/components/net/sf/briar/protocol/TransportFactory.java +++ b/components/net/sf/briar/protocol/TransportFactory.java @@ -6,5 +6,6 @@ import net.sf.briar.api.protocol.Transports; interface TransportFactory { - Transports createTransports(Map transports, long timestamp); + Transports createTransports(Map> transports, + long timestamp); } diff --git a/components/net/sf/briar/protocol/TransportFactoryImpl.java b/components/net/sf/briar/protocol/TransportFactoryImpl.java index 85ceefc19..1cabeed06 100644 --- a/components/net/sf/briar/protocol/TransportFactoryImpl.java +++ b/components/net/sf/briar/protocol/TransportFactoryImpl.java @@ -6,7 +6,7 @@ import net.sf.briar.api.protocol.Transports; class TransportFactoryImpl implements TransportFactory { - public Transports createTransports(Map transports, + public Transports createTransports(Map> transports, long timestamp) { return new TransportsImpl(transports, timestamp); } diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java index bec0dd0f6..b945c1b65 100644 --- a/components/net/sf/briar/protocol/TransportReader.java +++ b/components/net/sf/briar/protocol/TransportReader.java @@ -2,6 +2,7 @@ package net.sf.briar.protocol; import java.io.IOException; import java.util.Map; +import java.util.TreeMap; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.Transports; @@ -26,10 +27,26 @@ class TransportReader implements ObjectReader { // Read the data r.addConsumer(counting); r.readUserDefinedTag(Tags.TRANSPORTS); - Map transports = r.readMap(String.class, String.class); + // Transport maps are always written in delimited form + Map> outer = + new TreeMap>(); + r.readMapStart(); + while(!r.hasMapEnd()) { + String name = r.readString(Transports.MAX_SIZE); + Map inner = new TreeMap(); + r.readMapStart(); + while(!r.hasMapEnd()) { + String key = r.readString(Transports.MAX_SIZE); + String value = r.readString(Transports.MAX_SIZE); + inner.put(key, value); + } + r.readMapEnd(); + outer.put(name, inner); + } + r.readMapEnd(); long timestamp = r.readInt64(); r.removeConsumer(counting); // Build and return the transports update - return transportFactory.createTransports(transports, timestamp); + return transportFactory.createTransports(outer, timestamp); } } diff --git a/components/net/sf/briar/protocol/TransportsImpl.java b/components/net/sf/briar/protocol/TransportsImpl.java index 247f4aa5f..157b1d2af 100644 --- a/components/net/sf/briar/protocol/TransportsImpl.java +++ b/components/net/sf/briar/protocol/TransportsImpl.java @@ -6,15 +6,16 @@ import net.sf.briar.api.protocol.Transports; class TransportsImpl implements Transports { - private final Map transports; + private final Map> transports; private final long timestamp; - TransportsImpl(Map transports, long timestamp) { + TransportsImpl(Map> transports, + long timestamp) { this.transports = transports; this.timestamp = timestamp; } - public Map getTransports() { + public Map> getTransports() { return transports; } diff --git a/components/net/sf/briar/protocol/writers/TransportWriterImpl.java b/components/net/sf/briar/protocol/writers/TransportWriterImpl.java index 495e001b9..89dc7ac0d 100644 --- a/components/net/sf/briar/protocol/writers/TransportWriterImpl.java +++ b/components/net/sf/briar/protocol/writers/TransportWriterImpl.java @@ -3,6 +3,7 @@ package net.sf.briar.protocol.writers; import java.io.IOException; import java.io.OutputStream; import java.util.Map; +import java.util.Map.Entry; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.writers.TransportWriter; @@ -19,10 +20,21 @@ class TransportWriterImpl implements TransportWriter { w = writerFactory.createWriter(out); } - public void writeTransports(Map transports) + public void writeTransports(Map> transports) throws IOException { w.writeUserDefinedTag(Tags.TRANSPORTS); - w.writeMap(transports); + // Transport maps are always written in delimited form + w.writeMapStart(); + for(Entry> e : transports.entrySet()) { + w.writeString(e.getKey()); + w.writeMapStart(); + for(Entry e1 : e.getValue().entrySet()) { + w.writeString(e1.getKey()); + w.writeString(e1.getValue()); + } + w.writeMapEnd(); + } + w.writeMapEnd(); w.writeInt64(System.currentTimeMillis()); out.flush(); } diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index 2ddcdfd13..cf8db1128 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -52,7 +52,7 @@ public abstract class DatabaseComponentTest extends TestCase { private final byte[] raw; private final Message message; private final Group group; - private final Map transports; + private final Map> transports; public DatabaseComponentTest() { super(); @@ -68,7 +68,8 @@ public abstract class DatabaseComponentTest extends TestCase { message = new TestMessage(messageId, MessageId.NONE, groupId, authorId, timestamp, raw); group = new TestGroup(groupId, "The really exciting group", null); - transports = Collections.singletonMap("foo", "bar"); + transports = Collections.singletonMap("foo", + Collections.singletonMap("bar", "baz")); } protected abstract DatabaseComponent createDatabaseComponent( @@ -1112,9 +1113,9 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).startTransaction(); will(returnValue(txn)); oneOf(database).getTransports(txn); - will(returnValue(Collections.singletonMap("foo", "bar"))); - oneOf(database).setTransports(txn, - Collections.singletonMap("bar", "baz")); + will(returnValue(transports)); + oneOf(database).setTransports(txn, "bar", + Collections.singletonMap("baz", "bam")); oneOf(database).commitTransaction(txn); oneOf(listener).eventOccurred( DatabaseListener.Event.TRANSPORTS_UPDATED); @@ -1122,7 +1123,7 @@ public abstract class DatabaseComponentTest extends TestCase { DatabaseComponent db = createDatabaseComponent(database, cleaner); db.addListener(listener); - db.setTransports(Collections.singletonMap("bar", "baz")); + db.setTransports("bar", Collections.singletonMap("baz", "bam")); context.assertIsSatisfied(); } @@ -1138,13 +1139,13 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).startTransaction(); will(returnValue(txn)); oneOf(database).getTransports(txn); - will(returnValue(Collections.singletonMap("bar", "baz"))); + will(returnValue(transports)); oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner); db.addListener(listener); - db.setTransports(Collections.singletonMap("bar", "baz")); + db.setTransports("foo", transports.get("foo")); context.assertIsSatisfied(); } diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index ea40cab10..7de9d7953 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -60,6 +60,7 @@ public class H2DatabaseTest extends TestCase { private final byte[] raw; private final Message message; private final Group group; + private final Map> transports; public H2DatabaseTest() throws Exception { super(); @@ -78,6 +79,8 @@ public class H2DatabaseTest extends TestCase { message = new TestMessage(messageId, MessageId.NONE, groupId, authorId, timestamp, raw); group = groupFactory.createGroup(groupId, "Group name", null); + transports = Collections.singletonMap("foo", + Collections.singletonMap("bar", "baz")); } @Before @@ -91,7 +94,6 @@ public class H2DatabaseTest extends TestCase { Database db = open(false); Connection txn = db.startTransaction(); assertFalse(db.containsContact(txn, contactId)); - Map transports = Collections.singletonMap("foo", "bar"); assertEquals(contactId, db.addContact(txn, transports)); assertTrue(db.containsContact(txn, contactId)); assertFalse(db.containsSubscription(txn, groupId)); @@ -107,8 +109,7 @@ public class H2DatabaseTest extends TestCase { db = open(true); txn = db.startTransaction(); assertTrue(db.containsContact(txn, contactId)); - transports = db.getTransports(txn, contactId); - assertEquals(Collections.singletonMap("foo", "bar"), transports); + assertEquals(transports, db.getTransports(txn, contactId)); assertTrue(db.containsSubscription(txn, groupId)); assertTrue(db.containsMessage(txn, messageId)); byte[] raw1 = db.getMessage(txn, messageId); @@ -141,20 +142,20 @@ public class H2DatabaseTest extends TestCase { // Create three contacts assertFalse(db.containsContact(txn, contactId)); - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); assertTrue(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId1)); - assertEquals(contactId1, db.addContact(txn, null)); + assertEquals(contactId1, db.addContact(txn, transports)); assertTrue(db.containsContact(txn, contactId1)); assertFalse(db.containsContact(txn, contactId2)); - assertEquals(contactId2, db.addContact(txn, null)); + assertEquals(contactId2, db.addContact(txn, transports)); assertTrue(db.containsContact(txn, contactId2)); // Delete one of the contacts db.removeContact(txn, contactId1); assertFalse(db.containsContact(txn, contactId1)); // Add another contact - a new ID should be created assertFalse(db.containsContact(txn, contactId3)); - assertEquals(contactId3, db.addContact(txn, null)); + assertEquals(contactId3, db.addContact(txn, transports)); assertTrue(db.containsContact(txn, contactId3)); db.commitTransaction(txn); @@ -201,7 +202,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -239,7 +240,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -281,7 +282,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.addMessage(txn, message); @@ -318,7 +319,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -349,7 +350,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); db.addMessage(txn, message); @@ -382,7 +383,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact and some batches to ack - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addBatchToAck(txn, contactId, batchId); db.addBatchToAck(txn, contactId, batchId1); db.commitTransaction(txn); @@ -410,7 +411,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact and receive the same batch twice - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addBatchToAck(txn, contactId, batchId); db.addBatchToAck(txn, contactId, batchId); db.commitTransaction(txn); @@ -437,7 +438,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -474,7 +475,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -517,7 +518,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); // Add some outstanding batches, a few ms apart for(int i = 0; i < ids.length; i++) { db.addOutstandingBatch(txn, contactId, ids[i], @@ -556,7 +557,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); // Add some outstanding batches, a few ms apart for(int i = 0; i < ids.length; i++) { db.addOutstandingBatch(txn, contactId, ids[i], @@ -784,24 +785,28 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact with some transport properties - Map transports = Collections.singletonMap("foo", "bar"); assertEquals(contactId, db.addContact(txn, transports)); assertEquals(transports, db.getTransports(txn, contactId)); // Replace the transport properties - transports = new TreeMap(); - transports.put("foo", "bar baz"); - transports.put("bar", "baz quux"); - db.setTransports(txn, contactId, transports, 1); - assertEquals(transports, db.getTransports(txn, contactId)); + Map> transports1 = + new TreeMap>(); + transports1.put("foo", Collections.singletonMap("bar", "baz")); + transports1.put("bar", Collections.singletonMap("baz", "quux")); + db.setTransports(txn, contactId, transports1, 1); + assertEquals(transports1, db.getTransports(txn, contactId)); // Remove the transport properties db.setTransports(txn, contactId, - Collections.emptyMap(), 2); + Collections.>emptyMap(), 2); assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId)); // Set the local transport properties - db.setTransports(txn, transports); + for(String s : transports.keySet()) { + db.setTransports(txn, s, transports.get(s)); + } assertEquals(transports, db.getTransports(txn)); // Remove the local transport properties - db.setTransports(txn, Collections.emptyMap()); + for(String s : transports.keySet()) { + db.setTransports(txn, s, Collections.emptyMap()); + } assertEquals(Collections.emptyMap(), db.getTransports(txn)); db.commitTransaction(txn); @@ -814,19 +819,20 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact with some transport properties - Map transports = Collections.singletonMap("foo", "bar"); assertEquals(contactId, db.addContact(txn, transports)); assertEquals(transports, db.getTransports(txn, contactId)); // Replace the transport properties using a timestamp of 2 - Map transports1 = new TreeMap(); - transports1.put("foo", "bar baz"); - transports1.put("bar", "baz quux"); + Map> transports1 = + new TreeMap>(); + transports1.put("foo", Collections.singletonMap("bar", "baz")); + transports1.put("bar", Collections.singletonMap("baz", "quux")); db.setTransports(txn, contactId, transports1, 2); assertEquals(transports1, db.getTransports(txn, contactId)); // Try to replace the transport properties using a timestamp of 1 - Map transports2 = new TreeMap(); - transports2.put("bar", "baz"); - transports2.put("quux", "fnord"); + Map> transports2 = + new TreeMap>(); + transports2.put("bar", Collections.singletonMap("baz", "quux")); + transports2.put("baz", Collections.singletonMap("quux", "fnord")); db.setTransports(txn, contactId, transports2, 1); // The old properties should still be there assertEquals(transports1, db.getTransports(txn, contactId)); @@ -844,7 +850,6 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact - Map transports = Collections.emptyMap(); assertEquals(contactId, db.addContact(txn, transports)); // Add some subscriptions Collection subs = Collections.singletonList(group); @@ -869,7 +874,6 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact - Map transports = Collections.emptyMap(); assertEquals(contactId, db.addContact(txn, transports)); // Add some subscriptions Collection subs = Collections.singletonList(group); @@ -892,7 +896,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -910,7 +914,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); db.addMessage(txn, message); @@ -933,7 +937,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); db.addMessage(txn, message); @@ -955,7 +959,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -980,7 +984,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -999,7 +1003,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact and a neighbour subscription - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); // There's no local subscription for the group @@ -1016,7 +1020,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.addMessage(txn, message); db.setStatus(txn, contactId, messageId, Status.NEW); @@ -1035,7 +1039,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.addMessage(txn, message); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -1055,7 +1059,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -1076,7 +1080,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); db.setVisibility(txn, groupId, Collections.singleton(contactId)); db.setSubscriptions(txn, contactId, Collections.singleton(group), 1); @@ -1096,7 +1100,7 @@ public class H2DatabaseTest extends TestCase { Connection txn = db.startTransaction(); // Add a contact and subscribe to a group - assertEquals(contactId, db.addContact(txn, null)); + assertEquals(contactId, db.addContact(txn, transports)); db.addSubscription(txn, group); // The group should not be visible to the contact diff --git a/test/net/sf/briar/protocol/FileReadWriteTest.java b/test/net/sf/briar/protocol/FileReadWriteTest.java index 9f8304687..52516a380 100644 --- a/test/net/sf/briar/protocol/FileReadWriteTest.java +++ b/test/net/sf/briar/protocol/FileReadWriteTest.java @@ -10,6 +10,7 @@ import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.Map; import junit.framework.TestCase; import net.sf.briar.TestUtils; @@ -72,6 +73,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 Map> transports; public FileReadWriteTest() throws Exception { super(); @@ -111,6 +113,8 @@ public class FileReadWriteTest extends TestCase { message3 = messageEncoder.encodeMessage(MessageId.NONE, group1, groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(), messageBody.getBytes("UTF-8")); + transports = Collections.singletonMap("foo", + Collections.singletonMap("bar", "baz")); } @Before @@ -154,7 +158,7 @@ public class FileReadWriteTest extends TestCase { s.writeSubscriptions(subs); TransportWriter t = packetWriterFactory.createTransportWriter(out); - t.writeTransports(Collections.singletonMap("foo", "bar")); + t.writeTransports(transports); out.close(); assertTrue(file.exists()); @@ -229,7 +233,7 @@ public class FileReadWriteTest extends TestCase { assertTrue(reader.hasUserDefined(Tags.TRANSPORTS)); Transports t = reader.readUserDefined(Tags.TRANSPORTS, Transports.class); - assertEquals(Collections.singletonMap("foo", "bar"), t.getTransports()); + assertEquals(transports, t.getTransports()); assertTrue(t.getTimestamp() > start); assertTrue(t.getTimestamp() <= System.currentTimeMillis());