Removed public/private groups from the wire protocol.

The distinction between inbox groups and other groups can be maintained
internally, there's no need to represent it on the wire.
This commit is contained in:
akwizgran
2013-12-20 13:32:36 +00:00
parent e8d864c004
commit 51b3a10be2
21 changed files with 139 additions and 199 deletions

View File

@@ -152,11 +152,11 @@ interface Database<T> {
throws DbException;
/**
* Makes a public group visible to the given contact.
* Makes a group visible to the given contact.
* <p>
* Locking: subscription write.
*/
void addVisibility(T txn, ContactId c, Group g) throws DbException;
void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
/**
* Returns true if the database contains the given contact.
@@ -217,7 +217,8 @@ interface Database<T> {
throws DbException;
/**
* Returns the status of all groups to which the user can subscribe.
* Returns the status of all groups to which the user subscribes or can
* subscribe, excluding inbox groups.
* <p>
* Locking: subscription read.
*/
@@ -522,15 +523,6 @@ interface Database<T> {
*/
Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
/**
* Returns the IDs of all private groups that are visible to the given
* contact.
* <p>
* Locking: subscription read.
*/
Collection<GroupId> getVisiblePrivateGroups(T txn, ContactId c)
throws DbException;
/**
* Increments the outgoing connection counter for the given endpoint
* in the given rotation period and returns the old value, or -1 if the
@@ -616,11 +608,11 @@ interface Database<T> {
void removeTransport(T txn, TransportId t) throws DbException;
/**
* Makes a public group invisible to the given contact.
* Makes a group invisible to the given contact.
* <p>
* Locking: subscription write.
*/
void removeVisibility(T txn, ContactId c, Group g) throws DbException;
void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
/**
* Sets the connection reordering window for the given endpoint in the
@@ -642,8 +634,8 @@ interface Database<T> {
long version) throws DbException;
/**
* Makes a private group visible to the given contact, adds it to the
* contact's subscriptions, and sets it as the inbox group for the contact.
* Makes a group visible to the given contact, adds it to the contact's
* subscriptions, and sets it as the inbox group for the contact.
* <p>
* Locking: contact read, message write, subscription write.
*/
@@ -732,11 +724,11 @@ interface Database<T> {
long version) throws DbException;
/**
* Makes a public group visible or invisible to future contacts by default.
* Makes a group visible or invisible to future contacts by default.
* <p>
* Locking: subscription write.
*/
void setVisibleToAll(T txn, Group g, boolean all) throws DbException;
void setVisibleToAll(T txn, GroupId g, boolean all) throws DbException;
/**
* Updates the expiry times of the given messages with respect to the given

View File

@@ -1770,7 +1770,6 @@ DatabaseCleaner.Callback {
}
public void setInboxGroup(ContactId c, Group g) throws DbException {
if(!g.isPrivate()) throw new IllegalArgumentException();
contactLock.readLock().lock();
try {
messageLock.writeLock().lock();
@@ -1871,9 +1870,8 @@ DatabaseCleaner.Callback {
}
}
public void setVisibility(Group g, Collection<ContactId> visible)
public void setVisibility(GroupId g, Collection<ContactId> visible)
throws DbException {
if(g.isPrivate()) throw new IllegalArgumentException();
Collection<ContactId> affected = new ArrayList<ContactId>();
contactLock.readLock().lock();
try {
@@ -1881,12 +1879,11 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
if(!db.containsGroup(txn, g.getId()))
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
// Use HashSets for O(1) lookups, O(n) overall running time
HashSet<ContactId> now = new HashSet<ContactId>(visible);
Collection<ContactId> before =
db.getVisibility(txn, g.getId());
Collection<ContactId> before = db.getVisibility(txn, g);
before = new HashSet<ContactId>(before);
// Set the group's visibility for each current contact
for(ContactId c : db.getContactIds(txn)) {
@@ -1917,8 +1914,7 @@ DatabaseCleaner.Callback {
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
}
public void setVisibleToAll(Group g, boolean all) throws DbException {
if(g.isPrivate()) throw new IllegalArgumentException();
public void setVisibleToAll(GroupId g, boolean all) throws DbException {
Collection<ContactId> affected = new ArrayList<ContactId>();
contactLock.readLock().lock();
try {
@@ -1926,14 +1922,13 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
if(!db.containsGroup(txn, g.getId()))
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
// Make the group visible or invisible to future contacts
db.setVisibleToAll(txn, g, all);
if(all) {
// Make the group visible to all current contacts
Collection<ContactId> before =
db.getVisibility(txn, g.getId());
Collection<ContactId> before = db.getVisibility(txn, g);
before = new HashSet<ContactId>(before);
for(ContactId c : db.getContactIds(txn)) {
if(!before.contains(c)) {

View File

@@ -88,7 +88,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (groupId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " salt BINARY NOT NULL,"
+ " private BOOLEAN NOT NULL,"
+ " visibleToAll BOOLEAN NOT NULL,"
+ " PRIMARY KEY (groupId))";
@@ -112,7 +111,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " groupId HASH NOT NULL," // Not a foreign key
+ " name VARCHAR NOT NULL,"
+ " salt BINARY NOT NULL,"
+ " private BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId, groupId),"
+ " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)"
@@ -646,13 +644,12 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO groups"
+ " (groupId, name, salt, private, visibleToAll)"
+ " VALUES (?, ?, ?, ?, FALSE)";
+ " (groupId, name, salt, visibleToAll)"
+ " VALUES (?, ?, ?, FALSE)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getId().getBytes());
ps.setString(2, g.getName());
ps.setBytes(3, g.getSalt());
ps.setBoolean(4, g.isPrivate());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -900,9 +897,8 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void addVisibility(Connection txn, ContactId c, Group g)
public void addVisibility(Connection txn, ContactId c, GroupId g)
throws DbException {
if(g.isPrivate()) throw new IllegalArgumentException();
PreparedStatement ps = null;
try {
String sql = "INSERT INTO groupVisibilities"
@@ -910,7 +906,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " VALUES (?, ?, FALSE)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setBytes(2, g.getId().getBytes());
ps.setBytes(2, g.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -1120,9 +1116,12 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Add all subscribed groups to the list
String sql = "SELECT groupId, name, salt, private, visibleToAll"
+ " FROM groups";
// Add all subscribed groups to the list, except inbox groups
String sql = "SELECT DISTINCT g.groupId, name, salt, visibleToAll"
+ " FROM groups AS g"
+ " LEFT OUTER JOIN groupVisibilities AS gv"
+ " ON g.groupId = gv.groupId"
+ " WHERE inbox = FALSE OR inbox IS NULL";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<GroupStatus> groups = new ArrayList<GroupStatus>();
@@ -1130,15 +1129,14 @@ abstract class JdbcDatabase implements Database<Connection> {
GroupId id = new GroupId(rs.getBytes(1));
String name = rs.getString(2);
byte[] salt = rs.getBytes(3);
boolean isPrivate = rs.getBoolean(4);
Group group = new Group(id, name, salt, isPrivate);
boolean visibleToAll = rs.getBoolean(5);
Group group = new Group(id, name, salt);
boolean visibleToAll = rs.getBoolean(4);
groups.add(new GroupStatus(group, true, visibleToAll));
}
rs.close();
ps.close();
// Add all unsubscribed groups to the list
sql = "SELECT DISTINCT cg.groupId, cg.name, cg.salt, cg.private"
sql = "SELECT DISTINCT cg.groupId, cg.name, cg.salt"
+ " FROM contactGroups AS cg"
+ " LEFT OUTER JOIN groups AS g"
+ " ON cg.groupId = g.groupId"
@@ -1149,8 +1147,7 @@ abstract class JdbcDatabase implements Database<Connection> {
GroupId id = new GroupId(rs.getBytes(1));
String name = rs.getString(2);
byte[] salt = rs.getBytes(3);
boolean isPrivate = rs.getBoolean(4);
Group group = new Group(id, name, salt, isPrivate);
Group group = new Group(id, name, salt);
groups.add(new GroupStatus(group, false, false));
}
rs.close();
@@ -1313,18 +1310,16 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT name, salt, private FROM groups"
+ " WHERE groupId = ?";
String sql = "SELECT name, salt FROM groups WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
String name = rs.getString(1);
byte[] salt = rs.getBytes(2);
boolean isPrivate = rs.getBoolean(3);
rs.close();
ps.close();
return new Group(g, name, salt, isPrivate);
return new Group(g, name, salt);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
@@ -1336,7 +1331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId, name, salt, private FROM groups";
String sql = "SELECT groupId, name, salt FROM groups";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<Group> groups = new ArrayList<Group>();
@@ -1344,8 +1339,7 @@ abstract class JdbcDatabase implements Database<Connection> {
GroupId id = new GroupId(rs.getBytes(1));
String name = rs.getString(2);
byte[] salt = rs.getBytes(3);
boolean isPrivate = rs.getBoolean(4);
groups.add(new Group(id, name, salt, isPrivate));
groups.add(new Group(id, name, salt));
}
rs.close();
ps.close();
@@ -2085,14 +2079,13 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT g.groupId, name, salt, private,"
+ " localVersion, txCount"
String sql = "SELECT g.groupId, name, salt, localVersion, txCount"
+ " FROM groups AS g"
+ " JOIN groupVisibilities AS vis"
+ " ON g.groupId = vis.groupId"
+ " JOIN groupVersions AS ver"
+ " ON vis.contactId = ver.contactId"
+ " WHERE vis.contactId = ?"
+ " JOIN groupVisibilities AS gvis"
+ " ON g.groupId = gvis.groupId"
+ " JOIN groupVersions AS gver"
+ " ON gvis.contactId = gver.contactId"
+ " WHERE gvis.contactId = ?"
+ " AND localVersion > localAcked"
+ " AND expiry < ?";
ps = txn.prepareStatement(sql);
@@ -2106,10 +2099,9 @@ abstract class JdbcDatabase implements Database<Connection> {
GroupId id = new GroupId(rs.getBytes(1));
String name = rs.getString(2);
byte[] salt = rs.getBytes(3);
boolean isPrivate = rs.getBoolean(4);
groups.add(new Group(id, name, salt, isPrivate));
version = rs.getLong(5);
txCount = rs.getInt(6);
groups.add(new Group(id, name, salt));
version = rs.getLong(4);
txCount = rs.getInt(5);
}
rs.close();
ps.close();
@@ -2335,32 +2327,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Collection<GroupId> getVisiblePrivateGroups(Connection txn,
ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT g.groupId"
+ " FROM groups AS g"
+ " JOIN groupVisibilities AS gv"
+ " ON g.groupId = gv.groupId"
+ " WHERE contactId = ?"
+ " AND private = TRUE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
List<GroupId> visible = new ArrayList<GroupId>();
while(rs.next()) visible.add(new GroupId(rs.getBytes(1)));
rs.close();
ps.close();
return Collections.unmodifiableList(visible);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public long incrementConnectionCounter(Connection txn, ContactId c,
TransportId t, long period) throws DbException {
PreparedStatement ps = null;
@@ -2619,16 +2585,15 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void removeVisibility(Connection txn, ContactId c, Group g)
public void removeVisibility(Connection txn, ContactId c, GroupId g)
throws DbException {
if(g.isPrivate()) throw new IllegalArgumentException();
PreparedStatement ps = null;
try {
String sql = "DELETE FROM groupVisibilities"
+ " WHERE contactId = ? AND groupId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setBytes(2, g.getId().getBytes());
ps.setBytes(2, g.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -2694,15 +2659,14 @@ abstract class JdbcDatabase implements Database<Connection> {
// Store the new subscriptions, if any
if(groups.isEmpty()) return true;
sql = "INSERT INTO contactGroups"
+ " (contactId, groupId, name, salt, private)"
+ " VALUES (?, ?, ?, ?, ?)";
+ " (contactId, groupId, name, salt)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
for(Group g : groups) {
ps.setBytes(2, g.getId().getBytes());
ps.setString(3, g.getName());
ps.setBytes(4, g.getSalt());
ps.setBoolean(5, g.isPrivate());
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
@@ -2740,7 +2704,6 @@ abstract class JdbcDatabase implements Database<Connection> {
public void setInboxGroup(Connection txn, ContactId c, Group g)
throws DbException {
if(!g.isPrivate()) throw new IllegalArgumentException();
PreparedStatement ps = null;
try {
// Unset any existing inbox group for the contact
@@ -2765,14 +2728,13 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
// Add the group to the contact's subscriptions
sql = "INSERT INTO contactGroups"
+ " (contactId, groupId, name, salt, private)"
+ " VALUES (?, ?, ?, ?, ?)";
+ " (contactId, groupId, name, salt)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setBytes(2, g.getId().getBytes());
ps.setString(3, g.getName());
ps.setBytes(4, g.getSalt());
ps.setBoolean(5, g.isPrivate());
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -3061,15 +3023,14 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setVisibleToAll(Connection txn, Group g, boolean all)
public void setVisibleToAll(Connection txn, GroupId g, boolean all)
throws DbException {
if(g.isPrivate()) throw new IllegalArgumentException();
PreparedStatement ps = null;
try {
String sql = "UPDATE groups SET visibleToAll = ? WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, all);
ps.setBytes(2, g.getId().getBytes());
ps.setBytes(2, g.getBytes());
int affected = ps.executeUpdate();
if(affected > 1) throw new DbStateException();
ps.close();

View File

@@ -274,7 +274,7 @@ abstract class Connector extends Thread {
contactId = db.addContact(remoteAuthor, localAuthor.getId());
// Create and store the inbox group
byte[] salt = crypto.deriveGroupSalt(secret);
Group inbox = groupFactory.createGroup("Inbox", salt, true);
Group inbox = groupFactory.createGroup("Inbox", salt);
db.addGroup(inbox);
db.setInboxGroup(contactId, inbox);
// Store the remote transport properties

View File

@@ -27,20 +27,19 @@ class GroupFactoryImpl implements GroupFactory {
this.writerFactory = writerFactory;
}
public Group createGroup(String name, boolean isPrivate) {
public Group createGroup(String name) {
byte[] salt = new byte[GROUP_SALT_LENGTH];
crypto.getSecureRandom().nextBytes(salt);
return createGroup(name, salt, isPrivate);
return createGroup(name, salt);
}
public Group createGroup(String name, byte[] salt, boolean isPrivate) {
public Group createGroup(String name, byte[] salt) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
try {
w.writeStructStart(GROUP);
w.writeString(name);
w.writeBytes(salt);
w.writeBoolean(isPrivate);
w.writeStructEnd();
} catch(IOException e) {
// Shouldn't happen with ByteArrayOutputStream
@@ -49,6 +48,6 @@ class GroupFactoryImpl implements GroupFactory {
MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(out.toByteArray());
GroupId id = new GroupId(messageDigest.digest());
return new Group(id, name, salt, isPrivate);
return new Group(id, name, salt);
}
}

View File

@@ -31,11 +31,10 @@ class GroupReader implements StructReader<Group> {
byte[] publicKey = null;
if(r.hasNull()) r.readNull();
else publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
boolean isPrivate = r.readBoolean();
r.readStructEnd();
r.removeConsumer(digesting);
// Build and return the group
GroupId id = new GroupId(messageDigest.digest());
return new Group(id, name, publicKey, isPrivate);
return new Group(id, name, publicKey);
}
}

View File

@@ -122,7 +122,6 @@ class MessageFactoryImpl implements MessageFactory {
w.writeStructStart(GROUP);
w.writeString(g.getName());
w.writeBytes(g.getSalt());
w.writeBoolean(g.isPrivate());
w.writeStructEnd();
}

View File

@@ -129,7 +129,6 @@ class PacketWriterImpl implements PacketWriter {
w.writeStructStart(GROUP);
w.writeString(g.getName());
w.writeBytes(g.getSalt());
w.writeBoolean(g.isPrivate());
w.writeStructEnd();
}
w.writeListEnd();