From e4a69f42b09fe9a95a780f9185528104e830344b Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sat, 13 Feb 2016 08:03:05 +0100 Subject: [PATCH] Fixed problem with sending broadcasts (while adding some tests) --- .../dissem/bitmessage/BitmessageContext.java | 14 +- .../ch/dissem/bitmessage/factory/Factory.java | 3 +- .../bitmessage/BitmessageContextTest.java | 164 ++++++++++++++++++ .../bitmessage/utils/MessageMatchers.java | 58 +++++++ .../java/ch/dissem/bitmessage/SystemTest.java | 38 ++-- 5 files changed, 261 insertions(+), 16 deletions(-) create mode 100644 core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index c99acfb..24a4aaf 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -26,6 +26,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.exception.DecryptionFailedException; +import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.utils.Property; import ch.dissem.bitmessage.utils.TTL; @@ -168,7 +169,7 @@ public class BitmessageContext { ctx.send( msg.getFrom(), to, - new Msg(msg), + wrapInObjectPayload(msg), TTL.msg() ); msg.setStatus(SENT); @@ -179,6 +180,17 @@ public class BitmessageContext { }); } + private ObjectPayload wrapInObjectPayload(Plaintext msg) { + switch (msg.getType()) { + case MSG: + return new Msg(msg); + case BROADCAST: + return Factory.getBroadcast(msg); + default: + throw new RuntimeException("Unknown message type " + msg.getType()); + } + } + public void startup() { ctx.getNetworkHandler().start(networkListener); } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java index 33604ab..11d4330 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -196,7 +196,8 @@ public class Factory { } } - public static ObjectPayload getBroadcast(BitmessageAddress sendingAddress, Plaintext plaintext) { + public static ObjectPayload getBroadcast(Plaintext plaintext) { + BitmessageAddress sendingAddress = plaintext.getFrom(); if (sendingAddress.getVersion() < 4) { return new V4Broadcast(sendingAddress, plaintext); } else { diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java new file mode 100644 index 0000000..3eead03 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java @@ -0,0 +1,164 @@ +/* + * 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.payload.ObjectType; +import ch.dissem.bitmessage.entity.payload.Pubkey; +import ch.dissem.bitmessage.ports.*; +import ch.dissem.bitmessage.utils.MessageMatchers; +import ch.dissem.bitmessage.utils.Singleton; +import ch.dissem.bitmessage.utils.TestUtils; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.LinkedList; +import java.util.List; + +import static ch.dissem.bitmessage.entity.payload.ObjectType.*; +import static ch.dissem.bitmessage.utils.MessageMatchers.object; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +/** + * @author Christian Basler + */ +public class BitmessageContextTest { + private BitmessageContext ctx; + private BitmessageContext.Listener listener; + + @Before + public void setUp() throws Exception { + Field field = Singleton.class.getDeclaredField("cryptography"); + field.setAccessible(true); + field.set(null, null); + + listener = mock(BitmessageContext.Listener.class); + ctx = new BitmessageContext.Builder() + .addressRepo(mock(AddressRepository.class)) + .cryptography(new BouncyCryptography()) + .inventory(mock(Inventory.class)) + .listener(listener) + .messageCallback(mock(MessageCallback.class)) + .messageRepo(mock(MessageRepository.class)) + .networkHandler(mock(NetworkHandler.class)) + .nodeRegistry(mock(NodeRegistry.class)) + .powRepo(mock(ProofOfWorkRepository.class)) + .proofOfWorkEngine(mock(ProofOfWorkEngine.class)) + .build(); + } + + @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 ensureSubscriptionIsAddedAndExistingBroadcastsRetrieved() throws Exception { + BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); + + List 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); + + ctx.addSubscribtion(address); + + verify(ctx.addresses(), times(1)).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"); + verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce()) + .putObject(object(MSG), eq(1000L), eq(1000L)); + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(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(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(Plaintext.Type.BROADCAST)); + } + + @Test(expected = IllegalArgumentException.class) + public void ensureSenderWithoutPrivateKeyThrowsException() { + Plaintext msg = new Plaintext.Builder(Plaintext.Type.BROADCAST) + .from(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .message("Subject", "Message") + .build(); + ctx.send(msg); + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java b/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java new file mode 100644 index 0000000..8bb1e36 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java @@ -0,0 +1,58 @@ +/* + * 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.utils; + +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.payload.ObjectType; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.mockito.Matchers; + +/** + * @author Christian Basler + */ +public class MessageMatchers { + public static Plaintext plaintext(final Plaintext.Type type) { + return Matchers.argThat(new BaseMatcher() { + @Override + public boolean matches(Object item) { + return item instanceof Plaintext && ((Plaintext) item).getType() == type; + } + + @Override + public void describeTo(Description description) { + description.appendText("type should be ").appendValue(type); + } + }); + } + + public static ObjectMessage object(final ObjectType type) { + return Matchers.argThat(new BaseMatcher<ObjectMessage>() { + @Override + public boolean matches(Object item) { + return item instanceof ObjectMessage && ((ObjectMessage) item).getPayload().getType() == type; + } + + @Override + public void describeTo(Description description) { + description.appendText("payload type should be ").appendValue(type); + } + }); + } +} diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index 1309336..a2e3da7 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -6,9 +6,7 @@ import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.repository.*; import ch.dissem.bitmessage.utils.TTL; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -21,16 +19,16 @@ import static org.junit.Assert.assertThat; * @author Christian Basler */ public class SystemTest { - static BitmessageContext alice; - static TestListener aliceListener = new TestListener(); - static BitmessageAddress aliceIdentity; + private BitmessageContext alice; + private TestListener aliceListener = new TestListener(); + private BitmessageAddress aliceIdentity; - static BitmessageContext bob; - static TestListener bobListener = new TestListener(); - static BitmessageAddress bobIdentity; + private BitmessageContext bob; + private TestListener bobListener = new TestListener(); + private BitmessageAddress bobIdentity; - @BeforeClass - public static void setUp() { + @Before + public void setUp() { TTL.msg(5 * MINUTE); TTL.getpubkey(5 * MINUTE); TTL.pubkey(5 * MINUTE); @@ -65,20 +63,32 @@ public class SystemTest { bobIdentity = bob.createIdentity(false); } - @AfterClass - public static void tearDown() { + @After + public void tearDown() { alice.shutdown(); bob.shutdown(); } @Test public void ensureAliceCanSendMessageToBob() throws Exception { - bobListener.reset(); String originalMessage = UUID.randomUUID().toString(); alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage); Plaintext plaintext = bobListener.get(15, TimeUnit.MINUTES); + assertThat(plaintext.getType(), equalTo(Plaintext.Type.MSG)); + assertThat(plaintext.getText(), equalTo(originalMessage)); + } + + @Test + public void ensureBobCanReceiveBroadcastFromAlice() throws Exception { + String originalMessage = UUID.randomUUID().toString(); + bob.addSubscribtion(new BitmessageAddress(aliceIdentity.getAddress())); + alice.broadcast(aliceIdentity, "Subject", originalMessage); + + Plaintext plaintext = bobListener.get(15, TimeUnit.MINUTES); + + assertThat(plaintext.getType(), equalTo(Plaintext.Type.BROADCAST)); assertThat(plaintext.getText(), equalTo(originalMessage)); } }