Jabit/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java

316 lines
14 KiB
Java

/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage;
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.Plaintext.Type;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.MessageMatchers;
import ch.dissem.bitmessage.utils.Singleton;
import ch.dissem.bitmessage.utils.TTL;
import ch.dissem.bitmessage.utils.TestUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import static ch.dissem.bitmessage.entity.payload.ObjectType.*;
import static ch.dissem.bitmessage.utils.MessageMatchers.object;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Christian Basler
*/
public class BitmessageContextTest {
private BitmessageContext ctx;
private BitmessageContext.Listener listener;
@Before
public void setUp() throws Exception {
Singleton.initialize(null);
listener = mock(BitmessageContext.Listener.class);
ctx = new BitmessageContext.Builder()
.addressRepo(mock(AddressRepository.class))
.cryptography(new BouncyCryptography())
.inventory(mock(Inventory.class))
.listener(listener)
.messageRepo(mock(MessageRepository.class))
.networkHandler(mock(NetworkHandler.class))
.nodeRegistry(mock(NodeRegistry.class))
.labeler(spy(new DefaultLabeler()))
.powRepo(spy(new ProofOfWorkRepository() {
Map<InventoryVector, Item> items = new HashMap<>();
@Override
public Item getItem(byte[] initialHash) {
return items.get(new InventoryVector(initialHash));
}
@Override
public List<byte[]> getItems() {
List<byte[]> result = new LinkedList<>();
for (InventoryVector iv : items.keySet()) {
result.add(iv.getHash());
}
return result;
}
@Override
public void putObject(Item item) {
items.put(new InventoryVector(cryptography().getInitialHash(item.object)), item);
}
@Override
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
items.put(new InventoryVector(cryptography().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes));
}
@Override
public void removeObject(byte[] initialHash) {
items.remove(initialHash);
}
}))
.proofOfWorkEngine(spy(new ProofOfWorkEngine() {
@Override
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
callback.onNonceCalculated(initialHash, new byte[8]);
}
}))
.build();
TTL.msg(2 * MINUTE);
}
@Test
public void ensureContactIsSavedAndPubkeyRequested() {
BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT");
ctx.addContact(contact);
verify(ctx.addresses(), times(2)).save(contact);
verify(ctx.internals().getProofOfWorkEngine())
.calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class));
}
@Test
public void ensurePubkeyIsNotRequestedIfItExists() throws Exception {
ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload");
Pubkey pubkey = (Pubkey) object.getPayload();
BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT");
contact.setPubkey(pubkey);
ctx.addContact(contact);
verify(ctx.addresses(), times(1)).save(contact);
verify(ctx.internals().getProofOfWorkEngine(), never())
.calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class));
}
@Test
public void ensureV2PubkeyIsNotRequestedIfItExistsInInventory() throws Exception {
BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT");
when(ctx.internals().getInventory().getObjects(anyLong(), anyLong(), any(ObjectType.class)))
.thenReturn(Collections.singletonList(
TestUtils.loadObjectMessage(2, "V2Pubkey.payload")
));
ctx.addContact(contact);
verify(ctx.addresses(), atLeastOnce()).save(contact);
verify(ctx.internals().getProofOfWorkEngine(), never())
.calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class));
}
@Test
public void ensureV4PubkeyIsNotRequestedIfItExistsInInventory() throws Exception {
BitmessageAddress contact = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
when(ctx.internals().getInventory().getObjects(anyLong(), anyLong(), any(ObjectType.class)))
.thenReturn(Collections.singletonList(
TestUtils.loadObjectMessage(2, "V4Pubkey.payload")
));
final BitmessageAddress stored = new BitmessageAddress(contact.getAddress());
stored.setAlias("Test");
when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(stored);
ctx.addContact(contact);
verify(ctx.addresses(), atLeastOnce()).save(argThat(new BaseMatcher<BitmessageAddress>() {
@Override
public boolean matches(Object item) {
return item instanceof BitmessageAddress
&& ((BitmessageAddress) item).getPubkey() != null
&& stored.getAlias().equals(((BitmessageAddress) item).getAlias());
}
@Override
public void describeTo(Description description) {
description.appendText("pubkey must not be null and alias must be ").appendValue(stored.getAlias());
}
}));
verify(ctx.internals().getProofOfWorkEngine(), never())
.calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class));
}
@Test
public void ensureSubscriptionIsAddedAndExistingBroadcastsRetrieved() throws Exception {
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
List<ObjectMessage> objects = new LinkedList<>();
objects.add(TestUtils.loadObjectMessage(4, "V4Broadcast.payload"));
objects.add(TestUtils.loadObjectMessage(5, "V5Broadcast.payload"));
when(ctx.internals().getInventory().getObjects(eq(address.getStream()), anyLong(), any(ObjectType.class)))
.thenReturn(objects);
when(ctx.addresses().getSubscriptions(anyLong())).thenReturn(Collections.singletonList(address));
ctx.addSubscribtion(address);
verify(ctx.addresses(), atLeastOnce()).save(address);
assertThat(address.isSubscribed(), is(true));
verify(ctx.internals().getInventory()).getObjects(eq(address.getStream()), anyLong(), any(ObjectType.class));
verify(listener).receive(any(Plaintext.class));
}
@Test
public void ensureIdentityIsCreated() {
assertThat(ctx.createIdentity(false), notNullValue());
}
@Test
public void ensureMessageIsSent() throws Exception {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
"Subject", "Message");
assertEquals(2, ctx.internals().getProofOfWorkRepository().getItems().size());
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(MSG), eq(1000L), eq(1000L));
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Type.MSG));
}
@Test
public void ensurePubkeyIsRequestedIfItIsMissing() throws Exception {
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message");
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(GET_PUBKEY), eq(1000L), eq(1000L));
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Type.MSG));
}
@Test(expected = IllegalArgumentException.class)
public void ensureSenderMustBeIdentity() {
ctx.send(new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"),
"Subject", "Message");
}
@Test
public void ensureBroadcastIsSent() throws Exception {
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
"Subject", "Message");
verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce())
.putObject(object(BROADCAST), eq(1000L), eq(1000L));
verify(ctx.internals().getProofOfWorkEngine())
.calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class));
verify(ctx.messages(), timeout(10000).atLeastOnce())
.save(MessageMatchers.plaintext(Type.BROADCAST));
}
@Test(expected = IllegalArgumentException.class)
public void ensureSenderWithoutPrivateKeyThrowsException() {
Plaintext msg = new Plaintext.Builder(Type.BROADCAST)
.from(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.message("Subject", "Message")
.build();
ctx.send(msg);
}
@Test
public void ensureChanIsJoined() {
String chanAddress = "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r";
BitmessageAddress chan = ctx.joinChan("general", chanAddress);
assertNotNull(chan);
assertEquals(chan.getAddress(), chanAddress);
assertTrue(chan.isChan());
}
@Test
public void ensureDeterministicAddressesAreCreated() {
final int expected_size = 8;
List<BitmessageAddress> addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false);
assertEquals(expected_size, addresses.size());
Set<String> expected = new HashSet<>(expected_size);
expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F");
expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN");
expected.add("BM-2cUzX8f9CKUU7L8NeB8GExZvf54PrcXq1S");
expected.add("BM-2cU7MAoQd7KE8SPF7AKFPpoEZKjk86KRqE");
expected.add("BM-2cVm8ByVBacc2DVhdTNs6rmy5ZQK6DUsrt");
expected.add("BM-2cW2af1vB6kWon2WkygDHqGwfcpfAFm2Jk");
expected.add("BM-2cWdWD7UtUN4gWChgNX9pvyvNPjUZvU8BT");
expected.add("BM-2cXkYgYcUrv4fGxSHzyEScW955Cc8sDteo");
for (BitmessageAddress a : addresses) {
assertTrue(expected.contains(a.getAddress()));
expected.remove(a.getAddress());
}
}
@Test
public void ensureShortDeterministicAddressesAreCreated() {
final int expected_size = 1;
List<BitmessageAddress> addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true);
assertEquals(expected_size, addresses.size());
Set<String> expected = new HashSet<>(expected_size);
expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78");
for (BitmessageAddress a : addresses) {
assertTrue(expected.contains(a.getAddress()));
expected.remove(a.getAddress());
}
}
@Test
public void ensureChanIsCreated() {
BitmessageAddress chan = ctx.createChan("test");
assertNotNull(chan);
assertEquals(chan.getVersion(), Pubkey.LATEST_VERSION);
assertTrue(chan.isChan());
}
@Test
public void ensureUnacknowledgedMessageIsResent() throws Exception {
Plaintext plaintext = new Plaintext.Builder(Type.MSG)
.ttl(1)
.message("subject", "message")
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
.build();
assertTrue(plaintext.getTo().has(Pubkey.Feature.DOES_ACK));
when(ctx.messages().findMessagesToResend()).thenReturn(Collections.singletonList(plaintext));
when(ctx.messages().getMessage(any(byte[].class))).thenReturn(plaintext);
ctx.internals().resendUnacknowledged();
verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext));
}
}