diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 682aa80..b19c303 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -164,6 +164,7 @@ public class BitmessageContext { if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) { throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); } + msg.setStatus(Plaintext.Status.DRAFT); BitmessageAddress to = msg.getTo(); if (to != null) { if (to.getPubkey() == null) { diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java index 15d6958..58333d5 100644 --- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -175,10 +175,13 @@ public class InternalContext { object.sign(from.getPrivateKey()); } if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) { - ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage(); + final ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage(); cryptography.doProofOfWork(ackMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, new ProofOfWorkEngine.Callback() { @Override public void onNonceCalculated(byte[] initialHash, byte[] nonce) { + // FIXME: the message gets lost if calculation is cancelled + // (e.g. by terminating the application) + ackMessage.setNonce(nonce); object.encrypt(recipient.getPubkey()); proofOfWorkService.doProofOfWork(recipient, object); } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java index 68aa5ac..a0878c2 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -29,6 +29,8 @@ import ch.dissem.bitmessage.utils.Encode; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.Arrays; +import java.util.Objects; import static ch.dissem.bitmessage.utils.Singleton.security; @@ -55,7 +57,7 @@ public class ObjectMessage implements MessagePayload { expiresTime = builder.expiresTime; objectType = builder.objectType; version = builder.payload.getVersion(); - stream = builder.streamNumber; + stream = builder.streamNumber > 0 ? builder.streamNumber : builder.payload.getStream(); payload = builder.payload; } @@ -230,4 +232,29 @@ public class ObjectMessage implements MessagePayload { return new ObjectMessage(this); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ObjectMessage that = (ObjectMessage) o; + + return expiresTime == that.expiresTime && + objectType == that.objectType && + version == that.version && + stream == that.stream && + Objects.equals(payload, that.payload); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(nonce); + result = 31 * result + (int) (expiresTime ^ (expiresTime >>> 32)); + result = 31 * result + (int) (objectType ^ (objectType >>> 32)); + result = 31 * result + (int) (version ^ (version >>> 32)); + result = 31 * result + (int) (stream ^ (stream >>> 32)); + result = 31 * result + (payload != null ? payload.hashCode() : 0); + return result; + } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java index b34206a..c0a33ef 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java @@ -173,7 +173,7 @@ public class Plaintext implements Streamable { Encode.varInt(message.length, out); out.write(message); if (type == Type.MSG) { - if (to.has(Pubkey.Feature.DOES_ACK)) { + if (to.has(Pubkey.Feature.DOES_ACK) && getAckMessage() != null) { ByteArrayOutputStream ack = new ByteArrayOutputStream(); getAckMessage().write(ack); byte[] data = ack.toByteArray(); @@ -255,7 +255,7 @@ public class Plaintext implements Streamable { return Objects.equals(encoding, plaintext.encoding) && Objects.equals(from, plaintext.from) && Arrays.equals(message, plaintext.message) && - Arrays.equals(ackData, plaintext.ackData) && + Objects.equals(getAckMessage(), plaintext.getAckMessage()) && Arrays.equals(to.getRipe(), plaintext.to.getRipe()) && Arrays.equals(signature, plaintext.signature) && Objects.equals(status, plaintext.status) && diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java deleted file mode 100644 index a8308b9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Ack.java +++ /dev/null @@ -1,33 +0,0 @@ -package ch.dissem.bitmessage.entity.payload; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * Created by chrigu on 06.11.15. - */ -public class Ack extends ObjectPayload { - private final long stream; - private final byte[] data; - - public Ack(long version, long stream, byte[] data) { - super(version); - this.stream = stream; - this.data = data; - } - - @Override - public ObjectType getType() { - return ObjectType.MSG; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(data); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java index 7e19e8f..1533a2d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java @@ -23,6 +23,7 @@ import ch.dissem.bitmessage.entity.PlaintextHolder; import ch.dissem.bitmessage.exception.DecryptionFailedException; import java.io.IOException; +import java.util.Objects; import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; import static ch.dissem.bitmessage.utils.Singleton.security; @@ -96,4 +97,18 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai public boolean isDecrypted() { return plaintext != null; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Broadcast broadcast = (Broadcast) o; + return stream == broadcast.stream && + (Objects.equals(encrypted, broadcast.encrypted) || Objects.equals(plaintext, broadcast.plaintext)); + } + + @Override + public int hashCode() { + return Objects.hash(stream); + } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java index 66cc296..4ae4696 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java @@ -39,7 +39,7 @@ public class GenericPayload extends ObjectPayload { this.data = data; } - public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException { + public static GenericPayload read(long version, long stream, InputStream is, int length) throws IOException { return new GenericPayload(version, stream, Decode.bytes(is, length)); } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java index 8974ce3..edc4643 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java @@ -24,6 +24,7 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Objects; import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; @@ -108,4 +109,19 @@ public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder { if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); encrypted.write(out); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Msg msg = (Msg) o; + return stream == msg.stream && + (Objects.equals(encrypted, msg.encrypted) || Objects.equals(plaintext, msg.plaintext)); + } + + @Override + public int hashCode() { + return (int) stream; + } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java index 33da28d..a7dbaff 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload; import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.Streamable; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.io.IOException; import java.io.OutputStream; 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 48cafff..cfa3229 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -31,6 +31,7 @@ import java.io.InputStream; import java.net.SocketException; import java.net.SocketTimeoutException; +import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG; import static ch.dissem.bitmessage.utils.Singleton.security; /** @@ -155,7 +156,7 @@ public class Factory { } // fallback: just store the message - we don't really care what it is LOG.trace("Unexpected object type: " + objectType); - return GenericPayload.read(version, stream, streamNumber, length); + return GenericPayload.read(version, streamNumber, stream, length); } private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { @@ -177,7 +178,7 @@ public class Factory { private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true); - return pubkey != null ? pubkey : GenericPayload.read(version, stream, streamNumber, length); + return pubkey != null ? pubkey : GenericPayload.read(version, streamNumber, stream, length); } private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException { @@ -192,7 +193,7 @@ public class Factory { return V5Broadcast.read(stream, streamNumber, length); default: LOG.debug("Encountered unknown broadcast version " + version); - return GenericPayload.read(version, stream, streamNumber, length); + return GenericPayload.read(version, streamNumber, stream, length); } } @@ -208,7 +209,7 @@ public class Factory { public static ObjectMessage createAck(Plaintext plaintext) { if (plaintext == null || plaintext.getAckData() == null) return null; - Ack ack = new Ack(3, plaintext.getFrom().getStream(), plaintext.getAckData()); - return new ObjectMessage.Builder().payload(ack).build(); + GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData()); + return new ObjectMessage.Builder().objectType(MSG).payload(ack).build(); } } diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java index ed095a8..91dd94e 100644 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java @@ -22,19 +22,22 @@ 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.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.TestUtils; +import ch.dissem.bitmessage.utils.*; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; import java.util.*; +import java.util.Collections; +import java.util.stream.Collectors; import static ch.dissem.bitmessage.entity.payload.ObjectType.*; import static ch.dissem.bitmessage.utils.MessageMatchers.object; +import static ch.dissem.bitmessage.utils.Singleton.security; +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.*; @@ -59,9 +62,41 @@ public class BitmessageContextTest { .messageRepo(mock(MessageRepository.class)) .networkHandler(mock(NetworkHandler.class)) .nodeRegistry(mock(NodeRegistry.class)) - .powRepo(mock(ProofOfWorkRepository.class)) - .proofOfWorkEngine(mock(ProofOfWorkEngine.class)) + .powRepo(spy(new ProofOfWorkRepository() { + Map items = new HashMap<>(); + + @Override + public Item getItem(byte[] initialHash) { + return items.get(new InventoryVector(initialHash)); + } + + @Override + public List getItems() { + List result = new LinkedList<>(); + for (InventoryVector iv : items.keySet()) { + result.add(iv.getHash()); + } + return result; + } + + @Override + public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { + items.put(new InventoryVector(security().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 @@ -161,6 +196,7 @@ public class BitmessageContextTest { public void ensureMessageIsSent() throws Exception { ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), "Subject", "Message"); + assertEquals(1, 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(Plaintext.Type.MSG)); diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java index e225924..eacaab2 100644 --- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java @@ -66,6 +66,7 @@ public class DefaultMessageListenerTest extends TestBase { when(ctx.getMessageRepository()).thenReturn(messageRepo); when(ctx.getInventory()).thenReturn(inventory); when(ctx.getNetworkHandler()).thenReturn(networkHandler); + when(ctx.getLabeler()).thenReturn(mock(Labeler.class)); listener = new DefaultMessageListener(ctx, mock(Labeler.class), mock(BitmessageContext.Listener.class)); } diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java index 9a24842..be57e4a 100644 --- a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java @@ -42,7 +42,7 @@ public class EncryptionTest extends TestBase { PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey()); - GenericPayload after = GenericPayload.read(0, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 1, 100); + GenericPayload after = GenericPayload.read(0, 1, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 100); assertEquals(before, after); } diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java index e66beb1..fa58cfe 100644 --- a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java @@ -65,6 +65,7 @@ public class ProofOfWorkServiceTest { when(ctx.getInventory()).thenReturn(inventory); when(ctx.getNetworkHandler()).thenReturn(networkHandler); when(ctx.getMessageRepository()).thenReturn(messageRepo); + when(ctx.getLabeler()).thenReturn(mock(Labeler.class)); proofOfWorkService = new ProofOfWorkService(); proofOfWorkService.setContext(ctx); diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java index 5d8777a..aa08475 100644 --- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java @@ -82,7 +82,7 @@ public class SerializationTest extends TestBase { .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) .to(TestUtils.loadContact()) .message("Subject", "Message") - .ack("ack".getBytes()) + .ackData("ack".getBytes()) .signature(new byte[0]) .build(); ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java index 770039d..21f85ff 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java @@ -72,7 +72,7 @@ public class TestUtils { public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException { BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); + ObjectMessage object = TestUtils.loadObjectMessage(3, "V4Pubkey.payload"); object.decrypt(address.getPublicDecryptionKey()); address.setPubkey((V4Pubkey) object.getPayload()); return address; diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java index 3e5695c..b0937d3 100644 --- a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java +++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java @@ -21,9 +21,7 @@ import java.io.IOException; import static ch.dissem.bitmessage.utils.UnixTime.DAY; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -82,7 +80,7 @@ public class CryptographyTest { .nonce(new byte[8]) .expiresTime(UnixTime.now(+28 * DAY)) .objectType(0) - .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0)) + .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) .build(); crypto.checkProofOfWork(objectMessage, 1000, 1000); } @@ -93,7 +91,7 @@ public class CryptographyTest { .nonce(new byte[8]) .expiresTime(UnixTime.now(+2 * MINUTE)) .objectType(0) - .payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0)) + .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) .build(); final CallbackWaiter waiter = new CallbackWaiter<>(); crypto.doProofOfWork(objectMessage, 1000, 1000, diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java index c1303e3..d451323 100644 --- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java +++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java @@ -52,7 +52,7 @@ public class CryptoCustomMessageTest extends TestBase { new CryptoCustomMessage.Reader() { @Override public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException { - return GenericPayload.read(0, in, 1, 100); + return GenericPayload.read(0, 1, in, 100); } }); GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());