Associate a timestamp with every subscription, indicating the earliest

acceptable timestamp of subscribed messages. For a new subscription,
the timestamp is initialised to the current time, so a new subscriber
to a group will not immediately receive any messages. (Subscribing to
a group is therefore more like joining a mailing list than joining a
Usenet group - you only receive messages written after you joined.)

Once the database fills up and starts expiring messages, the
timestamps of subscriptions are updated so that contacts need not send
messages that would expire immediately. This is done using the
*approximate* timestamp of the oldest message in the database, to
avoid revealing the presence or absence of any particular message.
This commit is contained in:
akwizgran
2011-08-05 13:34:58 +01:00
parent 6c5ce05c5d
commit c2045296eb
15 changed files with 283 additions and 130 deletions

View File

@@ -112,6 +112,11 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).addSubscription(txn, group);
oneOf(listener).eventOccurred(
DatabaseListener.Event.SUBSCRIPTIONS_UPDATED);
// subscribe(group) again
oneOf(group).getId();
will(returnValue(groupId));
oneOf(database).containsSubscription(txn, groupId);
will(returnValue(true));
// getSubscriptions()
oneOf(database).getSubscriptions(txn);
will(returnValue(Collections.singletonList(groupId)));
@@ -121,6 +126,9 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).removeSubscription(txn, groupId);
oneOf(listener).eventOccurred(
DatabaseListener.Event.SUBSCRIPTIONS_UPDATED);
// unsubscribe(groupId) again
oneOf(database).containsSubscription(txn, groupId);
will(returnValue(false));
// removeContact(contactId)
oneOf(database).removeContact(txn, contactId);
// close()
@@ -136,8 +144,10 @@ public abstract class DatabaseComponentTest extends TestCase {
assertEquals(Collections.singletonList(contactId), db.getContacts());
assertEquals(transports, db.getTransports(contactId));
db.subscribe(group);
db.subscribe(group); // Again - check listeners aren't called
assertEquals(Collections.singletonList(groupId), db.getSubscriptions());
db.unsubscribe(groupId);
db.unsubscribe(groupId); // Again - check listeners aren't called
db.removeContact(contactId);
db.removeListener(listener);
db.close();
@@ -336,7 +346,7 @@ public abstract class DatabaseComponentTest extends TestCase {
// addLocallyGeneratedMessage(message)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
oneOf(database).containsSubscription(txn, groupId, timestamp);
will(returnValue(false));
oneOf(database).commitTransaction(txn);
}});
@@ -357,7 +367,7 @@ public abstract class DatabaseComponentTest extends TestCase {
// addLocallyGeneratedMessage(message)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
oneOf(database).containsSubscription(txn, groupId, timestamp);
will(returnValue(true));
oneOf(database).addMessage(txn, message);
will(returnValue(false));
@@ -380,7 +390,7 @@ public abstract class DatabaseComponentTest extends TestCase {
// addLocallyGeneratedMessage(message)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
oneOf(database).containsSubscription(txn, groupId, timestamp);
will(returnValue(true));
oneOf(database).addMessage(txn, message);
will(returnValue(true));
@@ -413,7 +423,7 @@ public abstract class DatabaseComponentTest extends TestCase {
// addLocallyGeneratedMessage(message)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
oneOf(database).containsSubscription(txn, groupId, timestamp);
will(returnValue(true));
oneOf(database).addMessage(txn, message);
will(returnValue(true));
@@ -460,15 +470,21 @@ public abstract class DatabaseComponentTest extends TestCase {
context.mock(SubscriptionUpdate.class);
final TransportUpdate transportsUpdate = context.mock(TransportUpdate.class);
context.checking(new Expectations() {{
// Check whether the contact is still in the DB - which it's not
exactly(12).of(database).startTransaction();
// Check whether the contact is still in the DB (which it's not)
// once for each method
exactly(14).of(database).startTransaction();
will(returnValue(txn));
exactly(12).of(database).containsContact(txn, contactId);
exactly(14).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(12).of(database).commitTransaction(txn);
exactly(14).of(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner);
try {
db.findLostBatches(contactId);
fail();
} catch(NoSuchContactException expected) {}
try {
db.generateAck(contactId, ackWriter);
fail();
@@ -500,6 +516,11 @@ public abstract class DatabaseComponentTest extends TestCase {
fail();
} catch(NoSuchContactException expected) {}
try {
db.getTransports(contactId);
fail();
} catch(NoSuchContactException expected) {}
try {
db.hasSendableMessages(contactId);
fail();
@@ -715,10 +736,10 @@ public abstract class DatabaseComponentTest extends TestCase {
will(returnValue(true));
// Get the visible subscriptions
oneOf(database).getVisibleSubscriptions(txn, contactId);
will(returnValue(Collections.singletonList(group)));
will(returnValue(Collections.singletonMap(group, 0L)));
// Add the subscriptions to the writer
oneOf(subscriptionWriter).writeSubscriptions(
Collections.singletonList(group));
Collections.singletonMap(group, 0L));
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner);
@@ -800,7 +821,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(batch).getMessages();
will(returnValue(Collections.singletonList(message)));
oneOf(database).containsVisibleSubscription(txn, groupId,
contactId);
contactId, timestamp);
will(returnValue(false));
// The message is not stored but the batch must still be acked
oneOf(batch).getId();
@@ -832,7 +853,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(batch).getMessages();
will(returnValue(Collections.singletonList(message)));
oneOf(database).containsVisibleSubscription(txn, groupId,
contactId);
contactId, timestamp);
will(returnValue(true));
// The message is stored, but it's a duplicate
oneOf(database).addMessage(txn, message);
@@ -867,7 +888,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(batch).getMessages();
will(returnValue(Collections.singletonList(message)));
oneOf(database).containsVisibleSubscription(txn, groupId,
contactId);
contactId, timestamp);
will(returnValue(true));
// The message is stored, and it's not a duplicate
oneOf(database).addMessage(txn, message);
@@ -911,7 +932,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(batch).getMessages();
will(returnValue(Collections.singletonList(message)));
oneOf(database).containsVisibleSubscription(txn, groupId,
contactId);
contactId, timestamp);
will(returnValue(true));
// The message is stored, and it's not a duplicate
oneOf(database).addMessage(txn, message);
@@ -998,11 +1019,11 @@ public abstract class DatabaseComponentTest extends TestCase {
will(returnValue(true));
// Get the contents of the update
oneOf(subscriptionUpdate).getSubscriptions();
will(returnValue(Collections.singletonList(group)));
will(returnValue(Collections.singletonMap(group, 0L)));
oneOf(subscriptionUpdate).getTimestamp();
will(returnValue(timestamp));
oneOf(database).setSubscriptions(txn, contactId,
Collections.singletonList(group), timestamp);
Collections.singletonMap(group, 0L), timestamp);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner);
@@ -1052,7 +1073,7 @@ public abstract class DatabaseComponentTest extends TestCase {
// addLocallyGeneratedMessage(message)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
oneOf(database).containsSubscription(txn, groupId, timestamp);
will(returnValue(true));
oneOf(database).addMessage(txn, message);
will(returnValue(true));
@@ -1088,7 +1109,7 @@ public abstract class DatabaseComponentTest extends TestCase {
// addLocallyGeneratedMessage(message)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
oneOf(database).containsSubscription(txn, groupId, timestamp);
will(returnValue(true));
oneOf(database).addMessage(txn, message);
will(returnValue(false));

View File

@@ -61,6 +61,7 @@ public class H2DatabaseTest extends TestCase {
private final Message message;
private final Group group;
private final Map<String, Map<String, String>> transports;
private final Map<Group, Long> subscriptions;
public H2DatabaseTest() throws Exception {
super();
@@ -81,6 +82,7 @@ public class H2DatabaseTest extends TestCase {
group = groupFactory.createGroup(groupId, "Group name", null);
transports = Collections.singletonMap("foo",
Collections.singletonMap("bar", "baz"));
subscriptions = Collections.singletonMap(group, 0L);
}
@Before
@@ -205,7 +207,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -235,7 +237,7 @@ public class H2DatabaseTest extends TestCase {
}
@Test
public void testSendableMessagesMustBeNew() throws DbException {
public void testSendableMessagesMustHaveStatusNew() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
@@ -243,7 +245,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
@@ -296,7 +298,7 @@ public class H2DatabaseTest extends TestCase {
assertFalse(it.hasNext());
// The contact subscribing should make the message sendable
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
assertTrue(db.hasSendableMessages(txn, contactId));
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
@@ -304,7 +306,8 @@ public class H2DatabaseTest extends TestCase {
assertFalse(it.hasNext());
// The contact unsubscribing should make the message unsendable
db.setSubscriptions(txn, contactId, Collections.<Group>emptySet(), 2);
db.setSubscriptions(txn, contactId,
Collections.<Group, Long>emptyMap(), 2);
assertFalse(db.hasSendableMessages(txn, contactId));
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
@@ -313,6 +316,42 @@ public class H2DatabaseTest extends TestCase {
db.close();
}
@Test
public void testSendableMessagesMustBeNewerThanSubscriptions()
throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.setVisibility(txn, groupId, Collections.singleton(contactId));
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
// The message is older than the contact's subscription, so it should
// not be sendable
db.setSubscriptions(txn, contactId,
Collections.singletonMap(group, timestamp + 1), 1);
assertFalse(db.hasSendableMessages(txn, contactId));
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
// Changing the contact's subscription should make the message sendable
db.setSubscriptions(txn, contactId,
Collections.singletonMap(group, timestamp), 2);
assertTrue(db.hasSendableMessages(txn, contactId));
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.close();
}
@Test
public void testSendableMessagesMustFitCapacity() throws DbException {
Database<Connection> db = open(false);
@@ -322,7 +361,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -352,7 +391,7 @@ public class H2DatabaseTest extends TestCase {
// Add a contact, subscribe to a group and store a message
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -441,7 +480,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -478,7 +517,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -876,13 +915,14 @@ public class H2DatabaseTest extends TestCase {
// Add a contact
assertEquals(contactId, db.addContact(txn, transports));
// Add some subscriptions
Collection<Group> subs = Collections.singletonList(group);
db.setSubscriptions(txn, contactId, subs, 1);
assertEquals(subs, db.getSubscriptions(txn, contactId));
db.setSubscriptions(txn, contactId, subscriptions, 1);
assertEquals(Collections.singletonList(group),
db.getSubscriptions(txn, contactId));
// Update the subscriptions
Collection<Group> subs1 = Collections.singletonList(group1);
db.setSubscriptions(txn, contactId, subs1, 2);
assertEquals(subs1, db.getSubscriptions(txn, contactId));
Map<Group, Long> subscriptions1 = Collections.singletonMap(group1, 0L);
db.setSubscriptions(txn, contactId, subscriptions1, 2);
assertEquals(Collections.singletonList(group1),
db.getSubscriptions(txn, contactId));
db.commitTransaction(txn);
db.close();
@@ -900,14 +940,15 @@ public class H2DatabaseTest extends TestCase {
// Add a contact
assertEquals(contactId, db.addContact(txn, transports));
// Add some subscriptions
Collection<Group> subs = Collections.singletonList(group);
db.setSubscriptions(txn, contactId, subs, 2);
assertEquals(subs, db.getSubscriptions(txn, contactId));
db.setSubscriptions(txn, contactId, subscriptions, 2);
assertEquals(Collections.singletonList(group),
db.getSubscriptions(txn, contactId));
// Try to update the subscriptions using a timestamp of 1
Collection<Group> subs1 = Collections.singletonList(group1);
db.setSubscriptions(txn, contactId, subs1, 1);
Map<Group, Long> subscriptions1 = Collections.singletonMap(group1, 0L);
db.setSubscriptions(txn, contactId, subscriptions1, 1);
// The old subscriptions should still be there
assertEquals(subs, db.getSubscriptions(txn, contactId));
assertEquals(Collections.singletonList(group),
db.getSubscriptions(txn, contactId));
db.commitTransaction(txn);
db.close();
@@ -922,7 +963,7 @@ public class H2DatabaseTest extends TestCase {
// Add a contact and subscribe to a group
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
// The message is not in the database
assertNull(db.getMessageIfSendable(txn, contactId, messageId));
@@ -940,7 +981,7 @@ public class H2DatabaseTest extends TestCase {
// Add a contact, subscribe to a group and store a message
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
// Set the sendability to > 0
db.setSendability(txn, messageId, 1);
@@ -963,7 +1004,7 @@ public class H2DatabaseTest extends TestCase {
// Add a contact, subscribe to a group and store a message
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
// Set the sendability to 0
db.setSendability(txn, messageId, 0);
@@ -977,6 +1018,31 @@ public class H2DatabaseTest extends TestCase {
db.close();
}
@Test
public void testGetMessageIfSendableReturnsNullIfOld() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.setVisibility(txn, groupId, Collections.singleton(contactId));
// The message is older than the contact's subscription
Map<Group, Long> subs = Collections.singletonMap(group, timestamp + 1);
db.setSubscriptions(txn, contactId, subs, 1);
db.addMessage(txn, message);
// Set the sendability to > 0
db.setSendability(txn, messageId, 1);
// Set the status to NEW
db.setStatus(txn, contactId, messageId, Status.NEW);
// The message is not sendable because it's too old
assertNull(db.getMessageIfSendable(txn, contactId, messageId));
db.commitTransaction(txn);
db.close();
}
@Test
public void testGetMessageIfSendableReturnsMessage() throws DbException {
Database<Connection> db = open(false);
@@ -986,7 +1052,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
// Set the sendability to > 0
db.setSendability(txn, messageId, 1);
@@ -1011,7 +1077,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
// The message is not in the database
assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
@@ -1026,9 +1092,9 @@ public class H2DatabaseTest extends TestCase {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact and a neighbour subscription
// Add a contact with a subscription
assertEquals(contactId, db.addContact(txn, transports));
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
// There's no local subscription for the group
assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
@@ -1066,7 +1132,7 @@ public class H2DatabaseTest extends TestCase {
assertEquals(contactId, db.addContact(txn, transports));
db.addSubscription(txn, group);
db.addMessage(txn, message);
db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
// The subscription is not visible
@@ -1086,7 +1152,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
// The message has already been seen by the contact
db.setStatus(txn, contactId, messageId, Status.SEEN);
@@ -1107,7 +1173,7 @@ public class H2DatabaseTest extends TestCase {
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);
db.setSubscriptions(txn, contactId, subscriptions, 1);
db.addMessage(txn, message);
// The message has not been seen by the contact
db.setStatus(txn, contactId, messageId, Status.NEW);

View File

@@ -4,11 +4,11 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.Map;
@@ -152,9 +152,10 @@ public class FileReadWriteTest extends TestCase {
SubscriptionWriter s =
packetWriterFactory.createSubscriptionWriter(out);
Collection<Group> subs = new ArrayList<Group>();
subs.add(group);
subs.add(group1);
// Use a LinkedHashMap for predictable iteration order
Map<Group, Long> subs = new LinkedHashMap<Group, Long>();
subs.put(group, 0L);
subs.put(group1, 0L);
s.writeSubscriptions(subs);
TransportWriter t = packetWriterFactory.createTransportWriter(out);
@@ -221,11 +222,10 @@ public class FileReadWriteTest extends TestCase {
assertTrue(reader.hasUserDefined(Tags.SUBSCRIPTIONS));
SubscriptionUpdate s = reader.readUserDefined(Tags.SUBSCRIPTIONS,
SubscriptionUpdate.class);
Collection<Group> subs = s.getSubscriptions();
Map<Group, Long> subs = s.getSubscriptions();
assertEquals(2, subs.size());
Iterator<Group> it2 = subs.iterator();
checkGroupEquality(group, it2.next());
checkGroupEquality(group1, it2.next());
assertEquals(Long.valueOf(0L), subs.get(group));
assertEquals(Long.valueOf(0L), subs.get(group1));
assertTrue(s.getTimestamp() > start);
assertTrue(s.getTimestamp() <= System.currentTimeMillis());
@@ -253,13 +253,4 @@ public class FileReadWriteTest extends TestCase {
assertEquals(m1.getTimestamp(), m2.getTimestamp());
assertTrue(Arrays.equals(m1.getBytes(), m2.getBytes()));
}
private void checkGroupEquality(Group g1, Group g2) {
assertEquals(g1.getId(), g2.getId());
assertEquals(g1.getName(), g2.getName());
byte[] k1 = g1.getPublicKey();
byte[] k2 = g2.getPublicKey();
if(k1 == null) assertNull(k2);
else assertTrue(Arrays.equals(k1, k2));
}
}