diff --git a/build.gradle b/build.gradle index 78bf578..26aa243 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,19 @@ +buildscript { + ext.kotlin_version = '1.1.2-2' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} plugins { id 'com.github.ben-manes.versions' version '0.14.0' } subprojects { apply plugin: 'java' + apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'signing' apply plugin: 'jacoco' @@ -17,6 +27,9 @@ subprojects { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + } test { testLogging { diff --git a/core/build.gradle b/core/build.gradle index 785507e..f43c541 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -28,6 +28,6 @@ dependencies { compile 'ch.dissem.msgpack:msgpack:1.0.0' testCompile 'junit:junit:4.12' testCompile 'org.hamcrest:hamcrest-library:1.3' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' testCompile project(':cryptography-bc') } diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 29639e7..4bf0adc 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -66,7 +66,22 @@ public class BitmessageContext { private final boolean sendPubkeyOnIdentityCreation; private BitmessageContext(Builder builder) { - ctx = new InternalContext(builder); + ctx = new InternalContext( + builder.cryptography, + builder.inventory, + builder.nodeRegistry, + builder.networkHandler, + builder.addressRepo, + builder.messageRepo, + builder.proofOfWorkRepository, + builder.proofOfWorkEngine, + builder.customCommandHandler, + builder.listener, + builder.labeler, + builder.port, + builder.connectionTTL, + builder.connectionLimit + ); labeler = builder.labeler; ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation; @@ -103,7 +118,7 @@ public class BitmessageContext { } public BitmessageAddress joinChan(String passphrase, String address) { - BitmessageAddress chan = BitmessageAddress.chan(address, passphrase); + BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase); chan.setAlias(passphrase); ctx.getAddressRepository().save(chan); return chan; @@ -111,14 +126,14 @@ public class BitmessageContext { public BitmessageAddress createChan(String passphrase) { // FIXME: hardcoded stream number - BitmessageAddress chan = BitmessageAddress.chan(1, passphrase); + BitmessageAddress chan = BitmessageAddress.Companion.chan(1, passphrase); ctx.getAddressRepository().save(chan); return chan; } public List createDeterministicAddresses( String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { - List result = BitmessageAddress.deterministic( + List result = BitmessageAddress.Companion.deterministic( passphrase, numberOfAddresses, version, stream, shorter); for (int i = 0; i < result.size(); i++) { BitmessageAddress address = result.get(i); @@ -149,7 +164,7 @@ public class BitmessageContext { } public void send(final Plaintext msg) { - if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) { + if (msg.getFrom().getPrivateKey() == null) { throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); } labeler().markAsSending(msg); @@ -268,7 +283,7 @@ public class BitmessageContext { } private void tryToFindBroadcastsForAddress(BitmessageAddress address) { - for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) { + for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.Companion.getVersion(address), ObjectType.BROADCAST)) { try { Broadcast broadcast = (Broadcast) object.getPayload(); broadcast.decrypt(address); diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java deleted file mode 100644 index 4fb438f..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2015 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.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.ports.Labeler; -import ch.dissem.bitmessage.ports.NetworkHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED; - -class DefaultMessageListener implements NetworkHandler.MessageListener, InternalContext.ContextHolder { - private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class); - private final Labeler labeler; - private final BitmessageContext.Listener listener; - private InternalContext ctx; - - public DefaultMessageListener(Labeler labeler, BitmessageContext.Listener listener) { - this.labeler = labeler; - this.listener = listener; - } - - @Override - public void setContext(InternalContext context) { - this.ctx = context; - } - - @Override - @SuppressWarnings("ConstantConditions") - public void receive(ObjectMessage object) throws IOException { - ObjectPayload payload = object.getPayload(); - if (payload.getType() == null) { - if (payload instanceof GenericPayload) { - receive((GenericPayload) payload); - } - return; - } - - switch (payload.getType()) { - case GET_PUBKEY: { - receive(object, (GetPubkey) payload); - break; - } - case PUBKEY: { - receive(object, (Pubkey) payload); - break; - } - case MSG: { - receive(object, (Msg) payload); - break; - } - case BROADCAST: { - receive(object, (Broadcast) payload); - break; - } - default: { - throw new IllegalArgumentException("Unknown payload type " + payload.getType()); - } - } - } - - protected void receive(ObjectMessage object, GetPubkey getPubkey) { - BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag()); - if (identity != null && identity.getPrivateKey() != null && !identity.isChan()) { - LOG.info("Got pubkey request for identity " + identity); - // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days - ctx.sendPubkey(identity, object.getStream()); - } - } - - protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException { - BitmessageAddress address; - try { - if (pubkey instanceof V4Pubkey) { - V4Pubkey v4Pubkey = (V4Pubkey) pubkey; - address = ctx.getAddressRepository().findContact(v4Pubkey.getTag()); - if (address != null) { - v4Pubkey.decrypt(address.getPublicDecryptionKey()); - } - } else { - address = ctx.getAddressRepository().findContact(pubkey.getRipe()); - } - if (address != null && address.getPubkey() == null) { - updatePubkey(address, pubkey); - } - } catch (DecryptionFailedException ignore) { - } - } - - private void updatePubkey(BitmessageAddress address, Pubkey pubkey) { - address.setPubkey(pubkey); - LOG.info("Got pubkey for contact " + address); - ctx.getAddressRepository().save(address); - List messages = ctx.getMessageRepository().findMessages(PUBKEY_REQUESTED, address); - LOG.info("Sending " + messages.size() + " messages for contact " + address); - for (Plaintext msg : messages) { - ctx.getLabeler().markAsSending(msg); - ctx.getMessageRepository().save(msg); - ctx.send(msg); - } - } - - protected void receive(ObjectMessage object, Msg msg) throws IOException { - for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) { - try { - msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); - Plaintext plaintext = msg.getPlaintext(); - plaintext.setTo(identity); - if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) { - LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); - } else { - receive(object.getInventoryVector(), plaintext); - } - break; - } catch (DecryptionFailedException ignore) { - } - } - } - - protected void receive(GenericPayload ack) { - if (ack.getData().length == Msg.ACK_LENGTH) { - Plaintext msg = ctx.getMessageRepository().getMessageForAck(ack.getData()); - if (msg != null) { - ctx.getLabeler().markAsAcknowledged(msg); - ctx.getMessageRepository().save(msg); - } - } - } - - protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException { - byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null; - for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) { - if (tag != null && !Arrays.equals(tag, subscription.getTag())) { - continue; - } - try { - broadcast.decrypt(subscription.getPublicDecryptionKey()); - if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) { - LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); - } else { - receive(object.getInventoryVector(), broadcast.getPlaintext()); - } - } catch (DecryptionFailedException ignore) { - } - } - } - - protected void receive(InventoryVector iv, Plaintext msg) { - BitmessageAddress contact = ctx.getAddressRepository().getAddress(msg.getFrom().getAddress()); - if (contact != null && contact.getPubkey() == null) { - updatePubkey(contact, msg.getFrom().getPubkey()); - } - - msg.setInventoryVector(iv); - labeler.setLabels(msg); - ctx.getMessageRepository().save(msg); - listener.receive(msg); - - if (msg.getType() == Plaintext.Type.MSG && msg.getTo().has(Pubkey.Feature.DOES_ACK)) { - ObjectMessage ack = msg.getAckMessage(); - if (ack != null) { - ctx.getInventory().storeObject(ack); - ctx.getNetworkHandler().offer(ack.getInventoryVector()); - } - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt new file mode 100644 index 0000000..35a7628 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2017 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.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.ports.Labeler +import ch.dissem.bitmessage.ports.NetworkHandler +import org.slf4j.LoggerFactory +import java.util.* + +internal open class DefaultMessageListener( + private val labeler: Labeler, + private val listener: BitmessageContext.Listener +) : NetworkHandler.MessageListener { + private var ctx by InternalContext + + override fun receive(`object`: ObjectMessage) { + val payload = `object`.payload + + when (payload.type) { + ObjectType.GET_PUBKEY -> { + receive(`object`, payload as GetPubkey) + } + ObjectType.PUBKEY -> { + receive(`object`, payload as Pubkey) + } + ObjectType.MSG -> { + receive(`object`, payload as Msg) + } + ObjectType.BROADCAST -> { + receive(`object`, payload as Broadcast) + } + null -> { + if (payload is GenericPayload) { + receive(payload) + } + } + else -> { + throw IllegalArgumentException("Unknown payload type " + payload.type!!) + } + } + } + + protected fun receive(`object`: ObjectMessage, getPubkey: GetPubkey) { + val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag) + if (identity != null && identity.privateKey != null && !identity.isChan) { + LOG.info("Got pubkey request for identity " + identity) + // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days + ctx.sendPubkey(identity, `object`.stream) + } + } + + protected fun receive(`object`: ObjectMessage, pubkey: Pubkey) { + val address: BitmessageAddress? + try { + if (pubkey is V4Pubkey) { + address = ctx.addressRepository.findContact(pubkey.tag) + if (address != null) { + pubkey.decrypt(address.publicDecryptionKey) + } + } else { + address = ctx.addressRepository.findContact(pubkey.ripe) + } + if (address != null && address.pubkey == null) { + updatePubkey(address, pubkey) + } + } catch (_: DecryptionFailedException) {} + + } + + private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) { + address.pubkey = pubkey + LOG.info("Got pubkey for contact " + address) + ctx.addressRepository.save(address) + val messages = ctx.messageRepository.findMessages(PUBKEY_REQUESTED, address) + LOG.info("Sending " + messages.size + " messages for contact " + address) + for (msg in messages) { + ctx.labeler.markAsSending(msg) + ctx.messageRepository.save(msg) + ctx.send(msg) + } + } + + protected fun receive(`object`: ObjectMessage, msg: Msg) { + for (identity in ctx.addressRepository.getIdentities()) { + try { + msg.decrypt(identity.privateKey!!.privateEncryptionKey) + val plaintext = msg.plaintext!! + plaintext.to = identity + if (!`object`.isSignatureValid(plaintext.from.pubkey!!)) { + LOG.warn("Msg with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + } else { + receive(`object`.inventoryVector, plaintext) + } + break + } catch (_: DecryptionFailedException) {} + } + } + + protected fun receive(ack: GenericPayload) { + if (ack.data.size == Msg.ACK_LENGTH) { + ctx.messageRepository.getMessageForAck(ack.data)?.let { + ctx.labeler.markAsAcknowledged(it) + ctx.messageRepository.save(it) + } + } + } + + protected fun receive(`object`: ObjectMessage, broadcast: Broadcast) { + val tag = if (broadcast is V5Broadcast) broadcast.tag else null + for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { + if (tag != null && !Arrays.equals(tag, subscription.tag)) { + continue + } + try { + broadcast.decrypt(subscription.publicDecryptionKey) + if (!`object`.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { + LOG.warn("Broadcast with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + } else { + receive(`object`.inventoryVector, broadcast.plaintext!!) + } + } catch (_: DecryptionFailedException) {} + } + } + + protected fun receive(iv: InventoryVector, msg: Plaintext) { + val contact = ctx.addressRepository.getAddress(msg.from.address) + if (contact != null && contact.pubkey == null) { + updatePubkey(contact, msg.from.pubkey!!) + } + + msg.inventoryVector = iv + labeler.setLabels(msg) + ctx.messageRepository.save(msg) + listener.receive(msg) + + if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) { + msg.ackMessage?.let { + ctx.inventory.storeObject(it) + ctx.networkHandler.offer(it.inventoryVector) + } + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java deleted file mode 100644 index 007e8f2..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright 2015 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.entity.*; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TTL; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.TreeSet; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * The internal context should normally only be used for port implementations. If you need it in your client - * implementation, you're either doing something wrong, something very weird, or the BitmessageContext should - * get extended. - * <p> - * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. - * </p> - */ -public class InternalContext { - private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class); - - public final static long NETWORK_NONCE_TRIALS_PER_BYTE = 1000; - public final static long NETWORK_EXTRA_BYTES = 1000; - - private final Executor threadPool = Executors.newCachedThreadPool(); - - private final Cryptography cryptography; - private final Inventory inventory; - private final NodeRegistry nodeRegistry; - private final NetworkHandler networkHandler; - private final AddressRepository addressRepository; - private final MessageRepository messageRepository; - private final ProofOfWorkRepository proofOfWorkRepository; - private final ProofOfWorkEngine proofOfWorkEngine; - private final CustomCommandHandler customCommandHandler; - private final ProofOfWorkService proofOfWorkService; - private final Labeler labeler; - private final NetworkHandler.MessageListener networkListener; - - private final TreeSet<Long> streams = new TreeSet<>(); - private final int port; - private final long clientNonce; - private long connectionTTL; - private int connectionLimit; - - public InternalContext(BitmessageContext.Builder builder) { - this.cryptography = builder.cryptography; - this.inventory = builder.inventory; - this.nodeRegistry = builder.nodeRegistry; - this.networkHandler = builder.networkHandler; - this.addressRepository = builder.addressRepo; - this.messageRepository = builder.messageRepo; - this.proofOfWorkRepository = builder.proofOfWorkRepository; - this.proofOfWorkService = new ProofOfWorkService(); - this.proofOfWorkEngine = builder.proofOfWorkEngine; - this.clientNonce = cryptography.randomNonce(); - this.customCommandHandler = builder.customCommandHandler; - this.port = builder.port; - this.connectionLimit = builder.connectionLimit; - this.connectionTTL = builder.connectionTTL; - this.labeler = builder.labeler; - this.networkListener = new DefaultMessageListener(labeler, builder.listener); - - Singleton.initialize(cryptography); - - // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. - for (BitmessageAddress address : addressRepository.getIdentities()) { - streams.add(address.getStream()); - } - for (BitmessageAddress address : addressRepository.getSubscriptions()) { - streams.add(address.getStream()); - } - if (streams.isEmpty()) { - streams.add(1L); - } - - init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, - proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler, - networkListener); - for (BitmessageAddress identity : addressRepository.getIdentities()) { - streams.add(identity.getStream()); - } - } - - private void init(Object... objects) { - for (Object o : objects) { - if (o instanceof ContextHolder) { - ((ContextHolder) o).setContext(this); - } - } - } - - public Cryptography getCryptography() { - return cryptography; - } - - public Inventory getInventory() { - return inventory; - } - - public NodeRegistry getNodeRegistry() { - return nodeRegistry; - } - - public NetworkHandler getNetworkHandler() { - return networkHandler; - } - - public AddressRepository getAddressRepository() { - return addressRepository; - } - - public MessageRepository getMessageRepository() { - return messageRepository; - } - - public ProofOfWorkRepository getProofOfWorkRepository() { - return proofOfWorkRepository; - } - - public ProofOfWorkEngine getProofOfWorkEngine() { - return proofOfWorkEngine; - } - - public ProofOfWorkService getProofOfWorkService() { - return proofOfWorkService; - } - - public Labeler getLabeler() { - return labeler; - } - - public NetworkHandler.MessageListener getNetworkListener() { - return networkListener; - } - - public long[] getStreams() { - long[] result = new long[streams.size()]; - int i = 0; - for (long stream : streams) { - result[i++] = stream; - } - return result; - } - - public int getPort() { - return port; - } - - public void send(final Plaintext plaintext) { - if (plaintext.getAckMessage() != null) { - long expires = UnixTime.now(+plaintext.getTTL()); - LOG.info("Expires at " + expires); - proofOfWorkService.doProofOfWorkWithAck(plaintext, expires); - } else { - send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), plaintext.getTTL()); - } - } - - public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload, - final long timeToLive) { - try { - final BitmessageAddress recipient = (to != null ? to : from); - long expires = UnixTime.now(+timeToLive); - LOG.info("Expires at " + expires); - final ObjectMessage object = new ObjectMessage.Builder() - .stream(recipient.getStream()) - .expiresTime(expires) - .payload(payload) - .build(); - if (object.isSigned()) { - object.sign(from.getPrivateKey()); - } - if (payload instanceof Broadcast) { - ((Broadcast) payload).encrypt(); - } else if (payload instanceof Encrypted) { - object.encrypt(recipient.getPubkey()); - } - proofOfWorkService.doProofOfWork(to, object); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public void sendPubkey(final BitmessageAddress identity, final long targetStream) { - try { - long expires = UnixTime.now(TTL.pubkey()); - LOG.info("Expires at " + expires); - final ObjectMessage response = new ObjectMessage.Builder() - .stream(targetStream) - .expiresTime(expires) - .payload(identity.getPubkey()) - .build(); - response.sign(identity.getPrivateKey()); - response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey())); - // TODO: remember that the pubkey is just about to be sent, and on which stream! - proofOfWorkService.doProofOfWork(response); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - /** - * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback - * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB. - */ - public void requestPubkey(final BitmessageAddress contact) { - threadPool.execute(new Runnable() { - @Override - public void run() { - BitmessageAddress stored = addressRepository.getAddress(contact.getAddress()); - - tryToFindMatchingPubkey(contact); - if (contact.getPubkey() != null) { - if (stored != null) { - stored.setPubkey(contact.getPubkey()); - addressRepository.save(stored); - } else { - addressRepository.save(contact); - } - return; - } - - if (stored == null) { - addressRepository.save(contact); - } - - long expires = UnixTime.now(TTL.getpubkey()); - LOG.info("Expires at " + expires); - final ObjectMessage request = new ObjectMessage.Builder() - .stream(contact.getStream()) - .expiresTime(expires) - .payload(new GetPubkey(contact)) - .build(); - proofOfWorkService.doProofOfWork(request); - } - }); - } - - private void tryToFindMatchingPubkey(BitmessageAddress address) { - BitmessageAddress stored = addressRepository.getAddress(address.getAddress()); - if (stored != null) { - address.setAlias(stored.getAlias()); - address.setSubscribed(stored.isSubscribed()); - } - for (ObjectMessage object : inventory.getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) { - try { - Pubkey pubkey = (Pubkey) object.getPayload(); - if (address.getVersion() == 4) { - V4Pubkey v4Pubkey = (V4Pubkey) pubkey; - if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) { - v4Pubkey.decrypt(address.getPublicDecryptionKey()); - if (object.isSignatureValid(v4Pubkey)) { - address.setPubkey(v4Pubkey); - addressRepository.save(address); - break; - } else { - LOG.info("Found pubkey for " + address + " but signature is invalid"); - } - } - } else { - if (Arrays.equals(pubkey.getRipe(), address.getRipe())) { - address.setPubkey(pubkey); - addressRepository.save(address); - break; - } - } - } catch (Exception e) { - LOG.debug(e.getMessage(), e); - } - } - } - - public void resendUnacknowledged() { - List<Plaintext> messages = messageRepository.findMessagesToResend(); - for (Plaintext message : messages) { - send(message); - messageRepository.save(message); - } - } - - public long getClientNonce() { - return clientNonce; - } - - public long getConnectionTTL() { - return connectionTTL; - } - - public int getConnectionLimit() { - return connectionLimit; - } - - public CustomCommandHandler getCustomCommandHandler() { - return customCommandHandler; - } - - public interface ContextHolder { - void setContext(InternalContext context); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt new file mode 100644 index 0000000..e36a8ac --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt @@ -0,0 +1,235 @@ +/* + * Copyright 2017 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.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.ports.* +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TTL +import ch.dissem.bitmessage.utils.UnixTime +import org.slf4j.LoggerFactory +import java.util.* +import java.util.concurrent.Executors +import kotlin.properties.Delegates +import kotlin.reflect.KProperty + +/** + * The internal context should normally only be used for port implementations. If you need it in your client + * implementation, you're either doing something wrong, something very weird, or the BitmessageContext should + * get extended. + * + * + * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. + * + */ +class InternalContext( + val cryptography: Cryptography, + val inventory: ch.dissem.bitmessage.ports.Inventory, + val nodeRegistry: NodeRegistry, + val networkHandler: NetworkHandler, + val addressRepository: AddressRepository, + val messageRepository: ch.dissem.bitmessage.ports.MessageRepository, + val proofOfWorkRepository: ProofOfWorkRepository, + val proofOfWorkEngine: ProofOfWorkEngine, + val customCommandHandler: CustomCommandHandler, + listener: BitmessageContext.Listener, + val labeler: Labeler, + + val port: Int, + val connectionTTL: Long, + val connectionLimit: Int +) { + + private val threadPool = Executors.newCachedThreadPool() + + val proofOfWorkService: ProofOfWorkService = ProofOfWorkService() + val networkListener: NetworkHandler.MessageListener = DefaultMessageListener(labeler, listener) + val clientNonce: Long = cryptography.randomNonce() + private val _streams = TreeSet<Long>() + val streams: LongArray + get() = _streams.toLongArray() + + init { + instance = this + Singleton.initialize(cryptography) + + // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. + addressRepository.getIdentities().mapTo(_streams) { it.stream } + addressRepository.getSubscriptions().mapTo(_streams) { it.stream } + if (_streams.isEmpty()) { + _streams.add(1L) + } + + init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, + proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, labeler, + networkListener) + } + + private fun init(vararg objects: Any) { + objects.filter { it is ContextHolder }.forEach { (it as ContextHolder).setContext(this) } + } + + fun send(plaintext: Plaintext) { + if (plaintext.ackMessage != null) { + val expires = UnixTime.now + plaintext.ttl + LOG.info("Expires at " + expires) + proofOfWorkService.doProofOfWorkWithAck(plaintext, expires) + } else { + send(plaintext.from, plaintext.to, Msg(plaintext), plaintext.ttl) + } + } + + fun send(from: BitmessageAddress, to: BitmessageAddress?, payload: ObjectPayload, + timeToLive: Long) { + val recipient = to ?: from + val expires = UnixTime.now + timeToLive + LOG.info("Expires at " + expires) + val `object` = ObjectMessage( + stream = recipient.stream, + expiresTime = expires, + payload = payload + ) + if (`object`.isSigned) { + `object`.sign( + from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity") + ) + } + if (payload is Broadcast) { + payload.encrypt() + } else if (payload is Encrypted) { + `object`.encrypt( + recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available") + ) + } + proofOfWorkService.doProofOfWork(to, `object`) + } + + fun sendPubkey(identity: BitmessageAddress, targetStream: Long) { + val expires = UnixTime.now + TTL.pubkey + LOG.info("Expires at " + expires) + val payload = identity.pubkey ?: throw IllegalArgumentException("The given address is no identity") + val response = ObjectMessage( + expiresTime = expires, + stream = targetStream, + payload = payload + ) + response.sign( + identity.privateKey ?: throw IllegalArgumentException("The given address is no identity") + ) + response.encrypt(cryptography.createPublicKey(identity.publicDecryptionKey)) + // TODO: remember that the pubkey is just about to be sent, and on which stream! + proofOfWorkService.doProofOfWork(response) + } + + /** + * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback + * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB. + */ + fun requestPubkey(contact: BitmessageAddress) { + threadPool.execute { + val stored = addressRepository.getAddress(contact.address) + + tryToFindMatchingPubkey(contact) + if (contact.pubkey != null) { + if (stored != null) { + stored.pubkey = contact.pubkey + addressRepository.save(stored) + } else { + addressRepository.save(contact) + } + return@execute + } + + if (stored == null) { + addressRepository.save(contact) + } + + val expires = UnixTime.now + TTL.getpubkey + LOG.info("Expires at " + expires) + val payload = GetPubkey(contact) + val request = ObjectMessage( + stream = contact.stream, + expiresTime = expires, + payload = payload + ) + proofOfWorkService.doProofOfWork(request) + } + } + + private fun tryToFindMatchingPubkey(address: BitmessageAddress) { + addressRepository.getAddress(address.address)?.let { + address.alias = it.alias + address.isSubscribed = it.isSubscribed + } + for (`object` in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { + try { + val pubkey = `object`.payload as Pubkey + if (address.version == 4L) { + val v4Pubkey = pubkey as V4Pubkey + if (Arrays.equals(address.tag, v4Pubkey.tag)) { + v4Pubkey.decrypt(address.publicDecryptionKey) + if (`object`.isSignatureValid(v4Pubkey)) { + address.pubkey = v4Pubkey + addressRepository.save(address) + break + } else { + LOG.info("Found pubkey for $address but signature is invalid") + } + } + } else { + if (Arrays.equals(pubkey.ripe, address.ripe)) { + address.pubkey = pubkey + addressRepository.save(address) + break + } + } + } catch (e: Exception) { + LOG.debug(e.message, e) + } + } + } + + fun resendUnacknowledged() { + val messages = messageRepository.findMessagesToResend() + for (message in messages) { + send(message) + messageRepository.save(message) + } + } + + interface ContextHolder { + fun setContext(context: InternalContext) + } + + companion object { + private val LOG = LoggerFactory.getLogger(InternalContext::class.java) + + @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 + @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 + + private var instance: InternalContext by Delegates.notNull<InternalContext>() + + operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) { + instance = value + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java deleted file mode 100644 index 14e7c8a..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java +++ /dev/null @@ -1,125 +0,0 @@ -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.ports.Cryptography; -import ch.dissem.bitmessage.ports.MessageRepository; -import ch.dissem.bitmessage.ports.ProofOfWorkEngine; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; -import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * @author Christian Basler - */ -public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder { - private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class); - - private Cryptography cryptography; - private InternalContext ctx; - private ProofOfWorkRepository powRepo; - private MessageRepository messageRepo; - - public void doMissingProofOfWork(long delayInMilliseconds) { - final List<byte[]> items = powRepo.getItems(); - if (items.isEmpty()) return; - - // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU - new Timer().schedule(new TimerTask() { - @Override - public void run() { - LOG.info("Doing POW for " + items.size() + " tasks."); - for (byte[] initialHash : items) { - Item item = powRepo.getItem(initialHash); - cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, - ProofOfWorkService.this); - } - } - }, delayInMilliseconds); - } - - public void doProofOfWork(ObjectMessage object) { - doProofOfWork(null, object); - } - - public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) { - Pubkey pubkey = recipient == null ? null : recipient.getPubkey(); - - long nonceTrialsPerByte = pubkey == null ? NETWORK_NONCE_TRIALS_PER_BYTE : pubkey.getNonceTrialsPerByte(); - long extraBytes = pubkey == null ? NETWORK_EXTRA_BYTES : pubkey.getExtraBytes(); - - powRepo.putObject(object, nonceTrialsPerByte, extraBytes); - if (object.getPayload() instanceof PlaintextHolder) { - Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext(); - plaintext.setInitialHash(cryptography.getInitialHash(object)); - messageRepo.save(plaintext); - } - cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this); - } - - public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) { - final ObjectMessage ack = plaintext.getAckMessage(); - messageRepo.save(plaintext); - Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, - expirationTime, plaintext); - powRepo.putObject(item); - cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this); - } - - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - Item item = powRepo.getItem(initialHash); - if (item.message == null) { - ObjectMessage object = item.object; - object.setNonce(nonce); - Plaintext plaintext = messageRepo.getMessage(initialHash); - if (plaintext != null) { - plaintext.setInventoryVector(object.getInventoryVector()); - plaintext.updateNextTry(); - ctx.getLabeler().markAsSent(plaintext); - messageRepo.save(plaintext); - } - try { - ctx.getNetworkListener().receive(object); - } catch (IOException e) { - LOG.debug(e.getMessage(), e); - } - ctx.getInventory().storeObject(object); - ctx.getNetworkHandler().offer(object.getInventoryVector()); - } else { - item.message.getAckMessage().setNonce(nonce); - final ObjectMessage object = new ObjectMessage.Builder() - .stream(item.message.getStream()) - .expiresTime(item.expirationTime) - .payload(new Msg(item.message)) - .build(); - if (object.isSigned()) { - object.sign(item.message.getFrom().getPrivateKey()); - } - if (object.getPayload() instanceof Encrypted) { - object.encrypt(item.message.getTo().getPubkey()); - } - doProofOfWork(item.message.getTo(), object); - } - powRepo.removeObject(initialHash); - } - - @Override - public void setContext(InternalContext ctx) { - this.ctx = ctx; - this.cryptography = cryptography(); - this.powRepo = ctx.getProofOfWorkRepository(); - this.messageRepo = ctx.getMessageRepository(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt new file mode 100644 index 0000000..2472d67 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2017 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.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.* +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.ports.ProofOfWorkEngine +import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item +import org.slf4j.LoggerFactory +import java.io.IOException +import java.util.* + +/** + * @author Christian Basler + */ +class ProofOfWorkService : ProofOfWorkEngine.Callback { + + private val ctx by InternalContext + private val cryptography by lazy { ctx.cryptography } + private val powRepo by lazy { ctx.proofOfWorkRepository } + private val messageRepo by lazy { ctx.messageRepository } + + fun doMissingProofOfWork(delayInMilliseconds: Long) { + val items = powRepo.getItems() + if (items.isEmpty()) return + + // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU + Timer().schedule(object : TimerTask() { + override fun run() { + LOG.info("Doing POW for " + items.size + " tasks.") + for (initialHash in items) { + val (`object`, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) + cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, + this@ProofOfWorkService) + } + } + }, delayInMilliseconds) + } + + fun doProofOfWork(`object`: ObjectMessage) { + doProofOfWork(null, `object`) + } + + fun doProofOfWork(recipient: BitmessageAddress?, `object`: ObjectMessage) { + val pubkey = recipient?.pubkey + + val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE + val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES + + powRepo.putObject(`object`, nonceTrialsPerByte, extraBytes) + if (`object`.payload is PlaintextHolder) { + `object`.payload.plaintext?.let { + it.initialHash = cryptography.getInitialHash(`object`) + messageRepo.save(it) + } + } + cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, this) + } + + fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) { + val ack = plaintext.ackMessage + messageRepo.save(plaintext) + val item = Item(ack!!, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, + expirationTime, plaintext) + powRepo.putObject(item) + cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this) + } + + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + val (`object`, _, _, expirationTime, message) = powRepo.getItem(initialHash) + if (message == null) { + `object`.nonce = nonce + messageRepo.getMessage(initialHash)?.let { + it.inventoryVector = `object`.inventoryVector + it.updateNextTry() + ctx.labeler.markAsSent(it) + messageRepo.save(it) + } + try { + ctx.networkListener.receive(`object`) + } catch (e: IOException) { + LOG.debug(e.message, e) + } + + ctx.inventory.storeObject(`object`) + ctx.networkHandler.offer(`object`.inventoryVector) + } else { + message.ackMessage!!.nonce = nonce + val `object` = ObjectMessage.Builder() + .stream(message.stream) + .expiresTime(expirationTime!!) + .payload(Msg(message)) + .build() + if (`object`.isSigned) { + `object`.sign(message.from.privateKey!!) + } + if (`object`.payload is Encrypted) { + `object`.encrypt(message.to!!.pubkey!!) + } + doProofOfWork(message.to, `object`) + } + powRepo.removeObject(initialHash) + } + + companion object { + private val LOG = LoggerFactory.getLogger(ProofOfWorkService::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt b/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt new file mode 100644 index 0000000..ee4feee --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2017 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.constants + +/** + * Created by chrigu on 03.06.17. + */ +object Network { + @JvmField val NETWORK_MAGIC_NUMBER = 8 + @JvmField val HEADER_SIZE = 24 + @JvmField val MAX_PAYLOAD_SIZE = 1600003 + @JvmField val MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java deleted file mode 100644 index 73d9995..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * The 'addr' command holds a list of known active Bitmessage nodes. - */ -public class Addr implements MessagePayload { - private static final long serialVersionUID = -5117688017050138720L; - - private final List<NetworkAddress> addresses; - - private Addr(Builder builder) { - addresses = builder.addresses; - } - - @Override - public Command getCommand() { - return Command.ADDR; - } - - public List<NetworkAddress> getAddresses() { - return addresses; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(addresses.size(), out); - for (NetworkAddress address : addresses) { - address.write(out); - } - } - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(addresses.size(), buffer); - for (NetworkAddress address : addresses) { - address.write(buffer); - } - } - - public static final class Builder { - private List<NetworkAddress> addresses = new ArrayList<>(); - - public Builder addresses(Collection<NetworkAddress> addresses){ - this.addresses.addAll(addresses); - return this; - } - - public Builder addAddress(final NetworkAddress address) { - this.addresses.add(address); - return this; - } - - public Addr build() { - return new Addr(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt new file mode 100644 index 0000000..0e7e8f6 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.utils.Encode +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'addr' command holds a list of known active Bitmessage nodes. + */ +data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload { + override val command: MessagePayload.Command = MessagePayload.Command.ADDR + + override fun write(out: OutputStream) { + Encode.varInt(addresses.size.toLong(), out) + for (address in addresses) { + address.write(out) + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(addresses.size.toLong(), buffer) + for (address in addresses) { + address.write(buffer) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java deleted file mode 100644 index ce199c4..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.AccessCounter; -import ch.dissem.bitmessage.utils.Base58; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import static ch.dissem.bitmessage.utils.Decode.bytes; -import static ch.dissem.bitmessage.utils.Decode.varInt; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address - * holding private keys. - */ -public class BitmessageAddress implements Serializable { - private static final long serialVersionUID = 2386328540805994064L; - - private final long version; - private final long stream; - private final byte[] ripe; - private final byte[] tag; - /** - * Used for V4 address encryption. It's easier to just create it regardless of address version. - */ - private final byte[] publicDecryptionKey; - - private String address; - - private PrivateKey privateKey; - private Pubkey pubkey; - - private String alias; - private boolean subscribed; - private boolean chan; - - BitmessageAddress(long version, long stream, byte[] ripe) { - try { - this.version = version; - this.stream = stream; - this.ripe = ripe; - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Encode.varInt(version, os); - Encode.varInt(stream, os); - if (version < 4) { - byte[] checksum = cryptography().sha512(os.toByteArray(), ripe); - this.tag = null; - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } else { - // for tag and decryption key, the checksum has to be created with 0x00 padding - byte[] checksum = cryptography().doubleSha512(os.toByteArray(), ripe); - this.tag = Arrays.copyOfRange(checksum, 32, 64); - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } - // but for the address and its checksum they need to be stripped - int offset = Bytes.numberOfLeadingZeros(ripe); - os.write(ripe, offset, ripe.length - offset); - byte[] checksum = cryptography().doubleSha512(os.toByteArray()); - os.write(checksum, 0, 4); - this.address = "BM-" + Base58.encode(os.toByteArray()); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public BitmessageAddress(Pubkey publicKey) { - this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe()); - this.pubkey = publicKey; - } - - public BitmessageAddress(String address, String passphrase) { - this(address); - this.privateKey = new PrivateKey(this, passphrase); - this.pubkey = this.privateKey.getPubkey(); - if (!Arrays.equals(ripe, privateKey.getPubkey().getRipe())) { - throw new IllegalArgumentException("Wrong address or passphrase"); - } - } - - public static BitmessageAddress chan(String address, String passphrase) { - BitmessageAddress result = new BitmessageAddress(address, passphrase); - result.chan = true; - return result; - } - - public static BitmessageAddress chan(long stream, String passphrase) { - PrivateKey privateKey = new PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase); - BitmessageAddress result = new BitmessageAddress(privateKey); - result.chan = true; - return result; - } - - public static List<BitmessageAddress> deterministic(String passphrase, int numberOfAddresses, - long version, long stream, boolean shorter) { - List<BitmessageAddress> result = new ArrayList<>(numberOfAddresses); - List<PrivateKey> privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter); - for (PrivateKey pk : privateKeys) { - result.add(new BitmessageAddress(pk)); - } - return result; - } - - public BitmessageAddress(PrivateKey privateKey) { - this(privateKey.getPubkey()); - this.privateKey = privateKey; - } - - public BitmessageAddress(String address) { - try { - this.address = address; - byte[] bytes = Base58.decode(address.substring(3)); - ByteArrayInputStream in = new ByteArrayInputStream(bytes); - AccessCounter counter = new AccessCounter(); - this.version = varInt(in, counter); - this.stream = varInt(in, counter); - this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20); - - // test checksum - byte[] checksum = cryptography().doubleSha512(bytes, bytes.length - 4); - byte[] expectedChecksum = bytes(in, 4); - for (int i = 0; i < 4; i++) { - if (expectedChecksum[i] != checksum[i]) - throw new IllegalArgumentException("Checksum of address failed"); - } - if (version < 4) { - checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); - this.tag = null; - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } else { - checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); - this.tag = Arrays.copyOfRange(checksum, 32, 64); - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public static byte[] calculateTag(long version, long stream, byte[] ripe) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Encode.varInt(version, out); - Encode.varInt(stream, out); - out.write(ripe); - return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public long getStream() { - return stream; - } - - public long getVersion() { - return version; - } - - public Pubkey getPubkey() { - return pubkey; - } - - public void setPubkey(Pubkey pubkey) { - if (pubkey instanceof V4Pubkey) { - if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag())) - throw new IllegalArgumentException("Pubkey has incompatible tag"); - } - if (!Arrays.equals(ripe, pubkey.getRipe())) - throw new IllegalArgumentException("Pubkey has incompatible ripe"); - this.pubkey = pubkey; - } - - /** - * @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. - */ - public byte[] getPublicDecryptionKey() { - return publicDecryptionKey; - } - - public PrivateKey getPrivateKey() { - return privateKey; - } - - public String getAddress() { - return address; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - @Override - public String toString() { - return alias == null ? address : alias; - } - - public byte[] getRipe() { - return ripe; - } - - public byte[] getTag() { - return tag; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BitmessageAddress address = (BitmessageAddress) o; - return Objects.equals(version, address.version) && - Objects.equals(stream, address.stream) && - Arrays.equals(ripe, address.ripe); - } - - @Override - public int hashCode() { - return Arrays.hashCode(ripe); - } - - public boolean isSubscribed() { - return subscribed; - } - - public void setSubscribed(boolean subscribed) { - this.subscribed = subscribed; - } - - public boolean isChan() { - return chan; - } - - public void setChan(boolean chan) { - this.chan = chan; - } - - public boolean has(Feature feature) { - if (pubkey == null || feature == null) { - return false; - } - return feature.isActive(pubkey.getBehaviorBitfield()); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt new file mode 100644 index 0000000..c0e978b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.AccessCounter +import ch.dissem.bitmessage.utils.Base58 +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.varInt +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.Serializable +import java.util.* + +/** + * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address + * holding private keys. + */ +class BitmessageAddress : Serializable { + + val version: Long + val stream: Long + val ripe: ByteArray + val tag: ByteArray? + /** + * The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create + * it regardless of address version. + */ + val publicDecryptionKey: ByteArray + + val address: String + + var privateKey: PrivateKey? = null + private set + var pubkey: Pubkey? = null + set(pubkey) { + if (pubkey != null) { + if (pubkey is V4Pubkey) { + if (!Arrays.equals(tag, pubkey.tag)) + throw IllegalArgumentException("Pubkey has incompatible tag") + } + if (!Arrays.equals(ripe, pubkey.ripe)) + throw IllegalArgumentException("Pubkey has incompatible ripe") + field = pubkey + } + } + + + var alias: String? = null + var isSubscribed: Boolean = false + var isChan: Boolean = false + + internal constructor(version: Long, stream: Long, ripe: ByteArray) { + this.version = version + this.stream = stream + this.ripe = ripe + + val os = ByteArrayOutputStream() + Encode.varInt(version, os) + Encode.varInt(stream, os) + if (version < 4) { + val checksum = cryptography().sha512(os.toByteArray(), ripe) + this.tag = null + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } else { + // for tag and decryption key, the checksum has to be created with 0x00 padding + val checksum = cryptography().doubleSha512(os.toByteArray(), ripe) + this.tag = Arrays.copyOfRange(checksum, 32, 64) + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } + // but for the address and its checksum they need to be stripped + val offset = Bytes.numberOfLeadingZeros(ripe) + os.write(ripe, offset, ripe.size - offset) + val checksum = cryptography().doubleSha512(os.toByteArray()) + os.write(checksum, 0, 4) + this.address = "BM-" + Base58.encode(os.toByteArray()) + } + + constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) { + this.pubkey = publicKey + } + + constructor(address: String, passphrase: String) : this(address) { + val key = PrivateKey(this, passphrase) + if (!Arrays.equals(ripe, key.pubkey.ripe)) { + throw IllegalArgumentException("Wrong address or passphrase") + } + this.privateKey = key + this.pubkey = key.pubkey + } + + constructor(privateKey: PrivateKey) : this(privateKey.pubkey) { + this.privateKey = privateKey + } + + constructor(address: String) { + this.address = address + val bytes = Base58.decode(address.substring(3)) + val `in` = ByteArrayInputStream(bytes) + val counter = AccessCounter() + this.version = varInt(`in`, counter) + this.stream = varInt(`in`, counter) + this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20) + + // test checksum + var checksum = cryptography().doubleSha512(bytes, bytes.size - 4) + val expectedChecksum = bytes(`in`, 4) + for (i in 0..3) { + if (expectedChecksum[i] != checksum[i]) + throw IllegalArgumentException("Checksum of address failed") + } + if (version < 4) { + checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe) + this.tag = null + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } else { + checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe) + this.tag = Arrays.copyOfRange(checksum, 32, 64) + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } + } + + override fun toString(): String { + return alias ?: address + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BitmessageAddress) return false + return version == other.version && + stream == other.stream && + Arrays.equals(ripe, other.ripe) + } + + override fun hashCode(): Int { + return Arrays.hashCode(ripe) + } + + fun has(feature: Feature?): Boolean { + return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false + } + + companion object { + @JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress { + val result = BitmessageAddress(address, passphrase) + result.isChan = true + return result + } + + @JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress { + val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase) + val result = BitmessageAddress(privateKey) + result.isChan = true + return result + } + + @JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, + version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> { + val result = ArrayList<BitmessageAddress>(numberOfAddresses) + val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter) + for (pk in privateKeys) { + result.add(BitmessageAddress(pk)) + } + return result + } + + @JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray { + val out = ByteArrayOutputStream() + Encode.varInt(version, out) + Encode.varInt(stream, out) + out.write(ripe) + return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java deleted file mode 100644 index 439f003..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.AccessCounter; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.*; -import java.nio.ByteBuffer; - -import static ch.dissem.bitmessage.utils.Decode.bytes; -import static ch.dissem.bitmessage.utils.Decode.varString; - -/** - * @author Christian Basler - */ -public class CustomMessage implements MessagePayload { - private static final long serialVersionUID = -8932056829480326011L; - - public static final String COMMAND_ERROR = "ERROR"; - - private final String command; - private final byte[] data; - - public CustomMessage(String command) { - this.command = command; - this.data = null; - } - - public CustomMessage(String command, byte[] data) { - this.command = command; - this.data = data; - } - - public static CustomMessage read(InputStream in, int length) throws IOException { - AccessCounter counter = new AccessCounter(); - return new CustomMessage(varString(in, counter), bytes(in, length - counter.length())); - } - - @Override - public Command getCommand() { - return Command.CUSTOM; - } - - public String getCustomCommand() { - return command; - } - - public byte[] getData() { - if (data != null) { - return data; - } else { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - write(out); - return out.toByteArray(); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - } - - @Override - public void write(OutputStream out) throws IOException { - if (data != null) { - Encode.varString(command, out); - out.write(data); - } else { - throw new ApplicationException("Tried to write custom message without data. " + - "Programmer: did you forget to override #write()?"); - } - } - - @Override - public void write(ByteBuffer buffer) { - if (data != null) { - Encode.varString(command, buffer); - buffer.put(data); - } else { - throw new ApplicationException("Tried to write custom message without data. " + - "Programmer: did you forget to override #write()?"); - } - } - - public boolean isError() { - return COMMAND_ERROR.equals(command); - } - - public static CustomMessage error(String message) { - try { - return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt new file mode 100644 index 0000000..a303c94 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.AccessCounter +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.varString +import ch.dissem.bitmessage.utils.Encode +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * @author Christian Basler + */ +open class CustomMessage(val customCommand: String, private val data: ByteArray? = null) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM + + val isError: Boolean + + fun getData(): ByteArray { + if (data != null) { + return data + } else { + val out = ByteArrayOutputStream() + write(out) + return out.toByteArray() + } + } + + override fun write(out: OutputStream) { + if (data != null) { + Encode.varString(customCommand, out) + out.write(data) + } else { + throw ApplicationException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?") + } + } + + override fun write(buffer: ByteBuffer) { + if (data != null) { + Encode.varString(customCommand, buffer) + buffer.put(data) + } else { + throw ApplicationException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?") + } + } + + companion object { + val COMMAND_ERROR = "ERROR" + + fun read(`in`: InputStream, length: Int): CustomMessage { + val counter = AccessCounter() + return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length())) + } + + fun error(message: String): CustomMessage { + return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8"))) + } + } + + init { + this.isError = COMMAND_ERROR == customCommand + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java b/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt similarity index 63% rename from core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java rename to core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt index 8b15371..913fbc1 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,18 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity -import ch.dissem.bitmessage.exception.DecryptionFailedException; - -import java.io.IOException; +import ch.dissem.bitmessage.exception.DecryptionFailedException /** * Used for objects that have encrypted content */ -public interface Encrypted { - void encrypt(byte[] publicKey) throws IOException; +interface Encrypted { + fun encrypt(publicKey: ByteArray) - void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException; + @Throws(DecryptionFailedException::class) + fun decrypt(privateKey: ByteArray) - boolean isDecrypted(); + val isDecrypted: Boolean } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java deleted file mode 100644 index 7d14fa0..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -/** - * The 'getdata' command is used to request objects from a node. - */ -public class GetData implements MessagePayload { - private static final long serialVersionUID = 1433878785969631061L; - - public static final int MAX_INVENTORY_SIZE = 50_000; - - List<InventoryVector> inventory; - - private GetData(Builder builder) { - inventory = builder.inventory; - } - - @Override - public Command getCommand() { - return Command.GETDATA; - } - - public List<InventoryVector> getInventory() { - return inventory; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(inventory.size(), out); - for (InventoryVector iv : inventory) { - iv.write(out); - } - } - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(inventory.size(), buffer); - for (InventoryVector iv : inventory) { - iv.write(buffer); - } - } - - public static final class Builder { - private List<InventoryVector> inventory = new LinkedList<>(); - - public Builder addInventoryVector(InventoryVector inventoryVector) { - this.inventory.add(inventoryVector); - return this; - } - - public Builder inventory(List<InventoryVector> inventory) { - this.inventory = inventory; - return this; - } - - public GetData build() { - return new GetData(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt new file mode 100644 index 0000000..62fab2b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Encode +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'getdata' command is used to request objects from a node. + */ +class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.GETDATA + + override fun write(out: OutputStream) { + Encode.varInt(inventory.size.toLong(), out) + for (iv in inventory) { + iv.write(out) + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(inventory.size.toLong(), buffer) + for (iv in inventory) { + iv.write(buffer) + } + } + + companion object { + @JvmField val MAX_INVENTORY_SIZE = 50000 + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java deleted file mode 100644 index 8d0f592..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -/** - * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. - */ -public class Inv implements MessagePayload { - private static final long serialVersionUID = 3662992522956947145L; - - private List<InventoryVector> inventory; - - private Inv(Builder builder) { - inventory = builder.inventory; - } - - public List<InventoryVector> getInventory() { - return inventory; - } - - @Override - public Command getCommand() { - return Command.INV; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(inventory.size(), out); - for (InventoryVector iv : inventory) { - iv.write(out); - } - } - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(inventory.size(), buffer); - for (InventoryVector iv : inventory) { - iv.write(buffer); - } - } - - public static final class Builder { - private List<InventoryVector> inventory = new LinkedList<>(); - - public Builder addInventoryVector(InventoryVector inventoryVector) { - this.inventory.add(inventoryVector); - return this; - } - - public Builder inventory(List<InventoryVector> inventory) { - this.inventory = inventory; - return this; - } - - public Inv build() { - return new Inv(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt new file mode 100644 index 0000000..42f963f --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Encode +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. + */ +class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.INV + + override fun write(out: OutputStream) { + Encode.varInt(inventory.size.toLong(), out) + for (iv in inventory) { + iv.write(out) + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(inventory.size.toLong(), buffer) + for (iv in inventory) { + iv.write(buffer) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt similarity index 80% rename from core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java rename to core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt index 994952b..b57fc16 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity /** * A command can hold a network message payload */ -public interface MessagePayload extends Streamable { - Command getCommand(); +interface MessagePayload : Streamable { + val command: Command - enum Command { + enum class Command { VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java deleted file mode 100644 index f27384e..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * A network message is exchanged between two nodes. - */ -public class NetworkMessage implements Streamable { - private static final long serialVersionUID = 702708857104464809L; - - /** - * Magic value indicating message origin network, and used to seek to next message when stream state is unknown - */ - public final static int MAGIC = 0xE9BEB4D9; - public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array(); - - private final MessagePayload payload; - - public NetworkMessage(MessagePayload payload) { - this.payload = payload; - } - - /** - * First 4 bytes of sha512(payload) - */ - private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException { - byte[] d = cryptography().sha512(bytes); - return new byte[]{d[0], d[1], d[2], d[3]}; - } - - /** - * The actual data, a message or an object. Not to be confused with objectPayload. - */ - public MessagePayload getPayload() { - return payload; - } - - @Override - public void write(OutputStream out) throws IOException { - // magic - Encode.int32(MAGIC, out); - - // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) - String command = payload.getCommand().name().toLowerCase(); - out.write(command.getBytes("ASCII")); - for (int i = command.length(); i < 12; i++) { - out.write('\0'); - } - - byte[] payloadBytes = Encode.bytes(payload); - - // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would - // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are - // larger than this. - Encode.int32(payloadBytes.length, out); - - // checksum - try { - out.write(getChecksum(payloadBytes)); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - - // message payload - out.write(payloadBytes); - } - - /** - * A more efficient implementation of the write method, writing header data to the provided buffer and returning - * a new buffer containing the payload. - * - * @param headerBuffer where the header data is written to (24 bytes) - * @return a buffer containing the payload, ready to be read. - */ - public ByteBuffer writeHeaderAndGetPayloadBuffer(ByteBuffer headerBuffer) { - return ByteBuffer.wrap(writeHeader(headerBuffer)); - } - - /** - * For improved memory efficiency, you should use {@link #writeHeaderAndGetPayloadBuffer(ByteBuffer)} - * and write the header buffer as well as the returned payload buffer into the channel. - * - * @param buffer where everything gets written to. Needs to be large enough for the whole message - * to be written. - */ - @Override - public void write(ByteBuffer buffer) { - byte[] payloadBytes = writeHeader(buffer); - buffer.put(payloadBytes); - } - - private byte[] writeHeader(ByteBuffer out) { - // magic - Encode.int32(MAGIC, out); - - // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) - String command = payload.getCommand().name().toLowerCase(); - try { - out.put(command.getBytes("ASCII")); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - for (int i = command.length(); i < 12; i++) { - out.put((byte) 0); - } - - byte[] payloadBytes = Encode.bytes(payload); - - // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would - // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are - // larger than this. - Encode.int32(payloadBytes.length, out); - - // checksum - try { - out.put(getChecksum(payloadBytes)); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - - // message payload - return payloadBytes; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt new file mode 100644 index 0000000..3b5369e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.IOException +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * A network message is exchanged between two nodes. + */ +data class NetworkMessage( + /** + * The actual data, a message or an object. Not to be confused with objectPayload. + */ + val payload: MessagePayload +) : Streamable { + + /** + * First 4 bytes of sha512(payload) + */ + private fun getChecksum(bytes: ByteArray): ByteArray { + val d = cryptography().sha512(bytes) + return byteArrayOf(d[0], d[1], d[2], d[3]) + } + + @Throws(IOException::class) + override fun write(out: OutputStream) { + // magic + Encode.int32(MAGIC.toLong(), out) + + // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) + val command = payload.command.name.toLowerCase() + out.write(command.toByteArray(charset("ASCII"))) + for (i in command.length..11) { + out.write(0x0) + } + + val payloadBytes = Encode.bytes(payload) + + // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would + // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are + // larger than this. + Encode.int32(payloadBytes.size.toLong(), out) + + // checksum + out.write(getChecksum(payloadBytes)) + + // message payload + out.write(payloadBytes) + } + + /** + * A more efficient implementation of the write method, writing header data to the provided buffer and returning + * a new buffer containing the payload. + + * @param headerBuffer where the header data is written to (24 bytes) + * * + * @return a buffer containing the payload, ready to be read. + */ + fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer { + return ByteBuffer.wrap(writeHeader(headerBuffer)) + } + + /** + * For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer] + * and write the header buffer as well as the returned payload buffer into the channel. + + * @param buffer where everything gets written to. Needs to be large enough for the whole message + * * to be written. + */ + override fun write(buffer: ByteBuffer) { + val payloadBytes = writeHeader(buffer) + buffer.put(payloadBytes) + } + + private fun writeHeader(out: ByteBuffer): ByteArray { + // magic + Encode.int32(MAGIC.toLong(), out) + + // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) + val command = payload.command.name.toLowerCase() + out.put(command.toByteArray(charset("ASCII"))) + + for (i in command.length..11) { + out.put(0.toByte()) + } + + val payloadBytes = Encode.bytes(payload) + + // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would + // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are + // larger than this. + Encode.int32(payloadBytes.size.toLong(), out) + + // checksum + out.put(getChecksum(payloadBytes)) + + // message payload + return payloadBytes + } + + companion object { + /** + * Magic value indicating message origin network, and used to seek to next message when stream state is unknown + */ + val MAGIC = 0xE9BEB4D9.toInt() + val MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java deleted file mode 100644 index 8e386f7..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.payload.ObjectPayload; -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.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Objects; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * The 'object' command sends an object that is shared throughout the network. - */ -public class ObjectMessage implements MessagePayload { - private static final long serialVersionUID = 2495752480120659139L; - - private byte[] nonce; - private long expiresTime; - private long objectType; - /** - * The object's version - */ - private long version; - private long stream; - - private ObjectPayload payload; - private byte[] payloadBytes; - - private ObjectMessage(Builder builder) { - nonce = builder.nonce; - expiresTime = builder.expiresTime; - objectType = builder.objectType; - version = builder.payload.getVersion(); - stream = builder.streamNumber > 0 ? builder.streamNumber : builder.payload.getStream(); - payload = builder.payload; - } - - @Override - public Command getCommand() { - return Command.OBJECT; - } - - public byte[] getNonce() { - return nonce; - } - - public void setNonce(byte[] nonce) { - this.nonce = nonce; - } - - public long getExpiresTime() { - return expiresTime; - } - - public long getType() { - return objectType; - } - - public ObjectPayload getPayload() { - return payload; - } - - public long getVersion() { - return version; - } - - public long getStream() { - return stream; - } - - public InventoryVector getInventoryVector() { - return InventoryVector.fromHash( - Bytes.truncate(cryptography().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32) - ); - } - - private boolean isEncrypted() { - return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted(); - } - - public boolean isSigned() { - return payload.isSigned(); - } - - private byte[] getBytesToSign() { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - writeHeaderWithoutNonce(out); - payload.writeBytesToSign(out); - return out.toByteArray(); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public void sign(PrivateKey key) { - if (payload.isSigned()) { - payload.setSignature(cryptography().getSignature(getBytesToSign(), key)); - } - } - - public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException { - if (payload instanceof Encrypted) { - ((Encrypted) payload).decrypt(key.getPrivateEncryptionKey()); - } - } - - public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException { - if (payload instanceof Encrypted) { - ((Encrypted) payload).decrypt(privateEncryptionKey); - } - } - - public void encrypt(byte[] publicEncryptionKey) throws IOException { - if (payload instanceof Encrypted) { - ((Encrypted) payload).encrypt(publicEncryptionKey); - } - } - - public void encrypt(Pubkey publicKey) { - try { - if (payload instanceof Encrypted) { - ((Encrypted) payload).encrypt(publicKey.getEncryptionKey()); - } - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public boolean isSignatureValid(Pubkey pubkey) throws IOException { - if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first"); - return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey); - } - - @Override - public void write(OutputStream out) throws IOException { - if (nonce == null) { - out.write(new byte[8]); - } else { - out.write(nonce); - } - out.write(getPayloadBytesWithoutNonce()); - } - - @Override - public void write(ByteBuffer buffer) { - if (nonce == null) { - buffer.put(new byte[8]); - } else { - buffer.put(nonce); - } - buffer.put(getPayloadBytesWithoutNonce()); - } - - private void writeHeaderWithoutNonce(OutputStream out) throws IOException { - Encode.int64(expiresTime, out); - Encode.int32(objectType, out); - Encode.varInt(version, out); - Encode.varInt(stream, out); - } - - public byte[] getPayloadBytesWithoutNonce() { - try { - if (payloadBytes == null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - writeHeaderWithoutNonce(out); - payload.write(out); - payloadBytes = out.toByteArray(); - } - return payloadBytes; - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public static final class Builder { - private byte[] nonce; - private long expiresTime; - private long objectType = -1; - private long streamNumber; - private ObjectPayload payload; - - public Builder nonce(byte[] nonce) { - this.nonce = nonce; - return this; - } - - public Builder expiresTime(long expiresTime) { - this.expiresTime = expiresTime; - return this; - } - - public Builder objectType(long objectType) { - this.objectType = objectType; - return this; - } - - public Builder objectType(ObjectType objectType) { - this.objectType = objectType.getNumber(); - return this; - } - - public Builder stream(long streamNumber) { - this.streamNumber = streamNumber; - return this; - } - - public Builder payload(ObjectPayload payload) { - this.payload = payload; - if (this.objectType == -1) - this.objectType = payload.getType().getNumber(); - return this; - } - - public ObjectMessage build() { - 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/ObjectMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt new file mode 100644 index 0000000..255ca48 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt @@ -0,0 +1,228 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.payload.ObjectPayload +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.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * The 'object' command sends an object that is shared throughout the network. + */ +data class ObjectMessage( + var nonce: ByteArray? = null, + val expiresTime: Long, + val payload: ObjectPayload, + val type: Long, + /** + * The object's version + */ + val version: Long, + val stream: Long +) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.OBJECT + + constructor( + nonce: ByteArray? = null, + expiresTime: Long, + payload: ObjectPayload, + stream: Long + ) : this( + nonce, + expiresTime, + payload, + payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"), + payload.version, + stream + ) + + val inventoryVector: InventoryVector + get() { + return InventoryVector(Bytes.truncate(cryptography().doubleSha512( + nonce ?: throw IllegalStateException("nonce must be set"), + payloadBytesWithoutNonce + ), 32)) + } + + private val isEncrypted: Boolean + get() = payload is Encrypted && !payload.isDecrypted + + val isSigned: Boolean + get() = payload.isSigned + + private val bytesToSign: ByteArray + get() { + try { + val out = ByteArrayOutputStream() + writeHeaderWithoutNonce(out) + payload.writeBytesToSign(out) + return out.toByteArray() + } catch (e: IOException) { + throw ApplicationException(e) + } + } + + fun sign(key: PrivateKey) { + if (payload.isSigned) { + payload.signature = cryptography().getSignature(bytesToSign, key) + } + } + + @Throws(DecryptionFailedException::class) + fun decrypt(key: PrivateKey) { + if (payload is Encrypted) { + payload.decrypt(key.privateEncryptionKey) + } + } + + @Throws(DecryptionFailedException::class) + fun decrypt(privateEncryptionKey: ByteArray) { + if (payload is Encrypted) { + payload.decrypt(privateEncryptionKey) + } + } + + fun encrypt(publicEncryptionKey: ByteArray) { + if (payload is Encrypted) { + payload.encrypt(publicEncryptionKey) + } + } + + fun encrypt(publicKey: Pubkey) { + try { + if (payload is Encrypted) { + payload.encrypt(publicKey.encryptionKey) + } + } catch (e: IOException) { + throw ApplicationException(e) + } + + } + + fun isSignatureValid(pubkey: Pubkey): Boolean { + if (isEncrypted) throw IllegalStateException("Payload must be decrypted first") + return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey) + } + + override fun write(out: OutputStream) { + out.write(nonce ?: ByteArray(8)) + out.write(payloadBytesWithoutNonce) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(nonce ?: ByteArray(8)) + buffer.put(payloadBytesWithoutNonce) + } + + private fun writeHeaderWithoutNonce(out: OutputStream) { + Encode.int64(expiresTime, out) + Encode.int32(type, out) + Encode.varInt(version, out) + Encode.varInt(stream, out) + } + + val payloadBytesWithoutNonce: ByteArray by lazy { + val out = ByteArrayOutputStream() + writeHeaderWithoutNonce(out) + payload.write(out) + out.toByteArray() + } + + class Builder { + private var nonce: ByteArray? = null + private var expiresTime: Long = 0 + private var objectType: Long? = null + private var streamNumber: Long = 0 + private var payload: ObjectPayload? = null + + fun nonce(nonce: ByteArray): Builder { + this.nonce = nonce + return this + } + + fun expiresTime(expiresTime: Long): Builder { + this.expiresTime = expiresTime + return this + } + + fun objectType(objectType: Long): Builder { + this.objectType = objectType + return this + } + + fun objectType(objectType: ObjectType): Builder { + this.objectType = objectType.number + return this + } + + fun stream(streamNumber: Long): Builder { + this.streamNumber = streamNumber + return this + } + + fun payload(payload: ObjectPayload): Builder { + this.payload = payload + if (this.objectType == null) + this.objectType = payload.type?.number + return this + } + + fun build(): ObjectMessage { + return ObjectMessage( + nonce = nonce, + expiresTime = expiresTime, + type = objectType!!, + version = payload!!.version, + stream = if (streamNumber > 0) streamNumber else payload!!.stream, + payload = payload!! + ) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ObjectMessage) return false + return expiresTime == other.expiresTime && + type == other.type && + version == other.version && + stream == other.stream && + payload == other.payload + } + + override fun hashCode(): Int { + var result = Arrays.hashCode(nonce) + result = 31 * result + (expiresTime xor expiresTime.ushr(32)).toInt() + result = 31 * result + (type xor type.ushr(32)).toInt() + result = 31 * result + (version xor version.ushr(32)).toInt() + result = 31 * result + (stream xor stream.ushr(32)).toInt() + result = 31 * result + (payload.hashCode()) + 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 deleted file mode 100644 index 141edfc..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.entity.valueobject.extended.Attachment; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.ExtendedEncodingFactory; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.*; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.*; -import java.util.Collections; - -import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED; -import static ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * The unencrypted message to be sent by 'msg' or 'broadcast'. - */ -public class Plaintext implements Streamable { - private static final long serialVersionUID = -5325729856394951079L; - - private final Type type; - private final BitmessageAddress from; - private final long encoding; - private final byte[] message; - private final byte[] ackData; - private final UUID conversationId; - private ExtendedEncoding extendedData; - private ObjectMessage ackMessage; - private Object id; - private InventoryVector inventoryVector; - private BitmessageAddress to; - private byte[] signature; - private Status status; - private Long sent; - private Long received; - - private Set<Label> labels; - private byte[] initialHash; - - private long ttl; - private int retries; - private Long nextTry; - - private Plaintext(Builder builder) { - id = builder.id; - inventoryVector = builder.inventoryVector; - type = builder.type; - from = builder.from; - to = builder.to; - encoding = builder.encoding; - message = builder.message; - ackData = builder.ackData; - if (builder.ackMessage != null && builder.ackMessage.length > 0) { - ackMessage = Factory.getObjectMessage( - 3, - new ByteArrayInputStream(builder.ackMessage), - builder.ackMessage.length); - } - signature = builder.signature; - status = builder.status; - sent = builder.sent; - received = builder.received; - labels = builder.labels; - ttl = builder.ttl; - retries = builder.retries; - nextTry = builder.nextTry; - conversationId = builder.conversation; - } - - public static Plaintext read(Type type, InputStream in) throws IOException { - return readWithoutSignature(type, in) - .signature(Decode.varBytes(in)) - .received(UnixTime.now()) - .build(); - } - - public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException { - long version = Decode.varInt(in); - return new Builder(type) - .addressVersion(version) - .stream(Decode.varInt(in)) - .behaviorBitfield(Decode.int32(in)) - .publicSigningKey(Decode.bytes(in, 64)) - .publicEncryptionKey(Decode.bytes(in, 64)) - .nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0) - .extraBytes(version >= 3 ? Decode.varInt(in) : 0) - .destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null) - .encoding(Decode.varInt(in)) - .message(Decode.varBytes(in)) - .ackMessage(type == Type.MSG ? Decode.varBytes(in) : null); - } - - public InventoryVector getInventoryVector() { - return inventoryVector; - } - - public void setInventoryVector(InventoryVector inventoryVector) { - this.inventoryVector = inventoryVector; - } - - public Type getType() { - return type; - } - - public byte[] getMessage() { - return message; - } - - public BitmessageAddress getFrom() { - return from; - } - - public BitmessageAddress getTo() { - return to; - } - - public void setTo(BitmessageAddress to) { - if (this.to.getVersion() != 0) - throw new IllegalStateException("Correct address already set"); - if (!Arrays.equals(this.to.getRipe(), to.getRipe())) { - throw new IllegalArgumentException("RIPEs don't match"); - } - this.to = to; - } - - public Set<Label> getLabels() { - return labels; - } - - public Encoding getEncoding() { - return Encoding.fromCode(encoding); - } - - public long getStream() { - return from.getStream(); - } - - public byte[] getSignature() { - return signature; - } - - public void setSignature(byte[] signature) { - this.signature = signature; - } - - public boolean isUnread() { - for (Label label : labels) { - if (label.getType() == Label.Type.UNREAD) { - return true; - } - } - return false; - } - - public void write(OutputStream out, boolean includeSignature) throws IOException { - Encode.varInt(from.getVersion(), out); - Encode.varInt(from.getStream(), out); - if (from.getPubkey() == null) { - Encode.int32(0, out); - byte[] empty = new byte[64]; - out.write(empty); - out.write(empty); - if (from.getVersion() >= 3) { - Encode.varInt(0, out); - Encode.varInt(0, out); - } - } else { - Encode.int32(from.getPubkey().getBehaviorBitfield(), out); - out.write(from.getPubkey().getSigningKey(), 1, 64); - out.write(from.getPubkey().getEncryptionKey(), 1, 64); - if (from.getVersion() >= 3) { - Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out); - Encode.varInt(from.getPubkey().getExtraBytes(), out); - } - } - if (type == Type.MSG) { - out.write(to.getRipe()); - } - Encode.varInt(encoding, out); - Encode.varInt(message.length, out); - out.write(message); - if (type == Type.MSG) { - if (to.has(Feature.DOES_ACK) && getAckMessage() != null) { - ByteArrayOutputStream ack = new ByteArrayOutputStream(); - getAckMessage().write(ack); - Encode.varBytes(ack.toByteArray(), out); - } else { - Encode.varInt(0, out); - } - } - if (includeSignature) { - if (signature == null) { - Encode.varInt(0, out); - } else { - Encode.varInt(signature.length, out); - out.write(signature); - } - } - } - - public void write(ByteBuffer buffer, boolean includeSignature) { - Encode.varInt(from.getVersion(), buffer); - Encode.varInt(from.getStream(), buffer); - if (from.getPubkey() == null) { - Encode.int32(0, buffer); - byte[] empty = new byte[64]; - buffer.put(empty); - buffer.put(empty); - if (from.getVersion() >= 3) { - Encode.varInt(0, buffer); - Encode.varInt(0, buffer); - } - } else { - Encode.int32(from.getPubkey().getBehaviorBitfield(), buffer); - buffer.put(from.getPubkey().getSigningKey(), 1, 64); - buffer.put(from.getPubkey().getEncryptionKey(), 1, 64); - if (from.getVersion() >= 3) { - Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), buffer); - Encode.varInt(from.getPubkey().getExtraBytes(), buffer); - } - } - if (type == Type.MSG) { - buffer.put(to.getRipe()); - } - Encode.varInt(encoding, buffer); - Encode.varInt(message.length, buffer); - buffer.put(message); - if (type == Type.MSG) { - if (to.has(Feature.DOES_ACK) && getAckMessage() != null) { - Encode.varBytes(Encode.bytes(getAckMessage()), buffer); - } else { - Encode.varInt(0, buffer); - } - } - if (includeSignature) { - if (signature == null) { - Encode.varInt(0, buffer); - } else { - Encode.varInt(signature.length, buffer); - buffer.put(signature); - } - } - } - - @Override - public void write(OutputStream out) throws IOException { - write(out, true); - } - - @Override - public void write(ByteBuffer buffer) { - write(buffer, true); - } - - public Object getId() { - return id; - } - - public void setId(long id) { - if (this.id != null) throw new IllegalStateException("ID already set"); - this.id = id; - } - - public Long getSent() { - return sent; - } - - public Long getReceived() { - return received; - } - - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) { - sent = UnixTime.now(); - } - this.status = status; - } - - public long getTTL() { - return ttl; - } - - public int getRetries() { - return retries; - } - - public Long getNextTry() { - return nextTry; - } - - public void updateNextTry() { - if (to != null) { - if (nextTry == null) { - if (sent != null && to.has(Feature.DOES_ACK)) { - nextTry = UnixTime.now(+ttl); - retries++; - } - } else { - nextTry = nextTry + (1 << retries) * ttl; - retries++; - } - } - } - - public String getSubject() { - Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8"); - String firstLine = s.nextLine(); - if (encoding == EXTENDED.code) { - if (Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getSubject(); - } else { - return null; - } - } else if (encoding == SIMPLE.code) { - return firstLine.substring("Subject:".length()).trim(); - } else if (firstLine.length() > 50) { - return firstLine.substring(0, 50).trim() + "..."; - } else { - return firstLine; - } - } - - public String getText() { - if (encoding == EXTENDED.code) { - if (Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getBody(); - } else { - return null; - } - } else { - try { - String text = new String(message, "UTF-8"); - if (encoding == SIMPLE.code) { - return text.substring(text.indexOf("\nBody:") + 6); - } - return text; - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } - } - - protected ExtendedEncoding getExtendedData() { - if (extendedData == null && encoding == EXTENDED.code) { - // TODO: make sure errors are properly handled - extendedData = ExtendedEncodingFactory.getInstance().unzip(message); - } - return extendedData; - } - - @SuppressWarnings("unchecked") - public <T extends ExtendedEncoding.ExtendedType> T getExtendedData(Class<T> type) { - ExtendedEncoding extendedData = getExtendedData(); - if (extendedData == null) { - return null; - } - if (type == null || type.isInstance(extendedData.getContent())) { - return (T) extendedData.getContent(); - } - return null; - } - - public List<InventoryVector> getParents() { - if (getExtendedData() != null && Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getParents(); - } else { - return Collections.emptyList(); - } - } - - public List<Attachment> getFiles() { - if (Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getFiles(); - } else { - return Collections.emptyList(); - } - } - - public UUID getConversationId() { - return conversationId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Plaintext plaintext = (Plaintext) o; - return Objects.equals(encoding, plaintext.encoding) && - Objects.equals(from, plaintext.from) && - Arrays.equals(message, plaintext.message) && - Objects.equals(getAckMessage(), plaintext.getAckMessage()) && - Arrays.equals(to == null ? null : to.getRipe(), plaintext.to == null ? null : plaintext.to.getRipe()) && - Arrays.equals(signature, plaintext.signature) && - Objects.equals(status, plaintext.status) && - Objects.equals(sent, plaintext.sent) && - Objects.equals(received, plaintext.received) && - Objects.equals(labels, plaintext.labels); - } - - @Override - public int hashCode() { - return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels); - } - - public void addLabels(Label... labels) { - if (labels != null) { - Collections.addAll(this.labels, labels); - } - } - - public void addLabels(Collection<Label> labels) { - if (labels != null) { - this.labels.addAll(labels); - } - } - - public void removeLabel(Label.Type type) { - Iterator<Label> iterator = labels.iterator(); - while (iterator.hasNext()) { - Label label = iterator.next(); - if (label.getType() == type) { - iterator.remove(); - } - } - } - - public byte[] getAckData() { - return ackData; - } - - public ObjectMessage getAckMessage() { - if (ackMessage == null) { - ackMessage = Factory.createAck(this); - } - return ackMessage; - } - - public void setInitialHash(byte[] initialHash) { - this.initialHash = initialHash; - } - - public byte[] getInitialHash() { - return initialHash; - } - - @Override - public String toString() { - String subject = getSubject(); - if (subject == null || subject.length() == 0) { - return Strings.hex(initialHash).toString(); - } else { - return subject; - } - } - - public enum Encoding { - IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); - - long code; - - Encoding(long code) { - this.code = code; - } - - public long getCode() { - return code; - } - - public static Encoding fromCode(long code) { - for (Encoding e : values()) { - if (e.getCode() == code) { - return e; - } - } - return null; - } - } - - public enum Status { - DRAFT, - // For sent messages - PUBKEY_REQUESTED, - DOING_PROOF_OF_WORK, - SENT, - SENT_ACKNOWLEDGED, - RECEIVED - } - - public enum Type { - MSG, BROADCAST - } - - public static final class Builder { - private Object id; - private InventoryVector inventoryVector; - private Type type; - private BitmessageAddress from; - private BitmessageAddress to; - private long addressVersion; - private long stream; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - private long nonceTrialsPerByte; - private long extraBytes; - private byte[] destinationRipe; - private long encoding; - private byte[] message = new byte[0]; - private byte[] ackData; - private byte[] ackMessage; - private byte[] signature; - private Long sent; - private Long received; - private Status status; - private Set<Label> labels = new LinkedHashSet<>(); - private long ttl; - private int retries; - private Long nextTry; - private UUID conversation; - - public Builder(Type type) { - this.type = type; - } - - public Builder id(Object id) { - this.id = id; - return this; - } - - public Builder IV(InventoryVector iv) { - this.inventoryVector = iv; - return this; - } - - public Builder from(BitmessageAddress address) { - from = address; - return this; - } - - public Builder to(BitmessageAddress address) { - if (type != Type.MSG && to != null) - throw new IllegalArgumentException("recipient address only allowed for msg"); - to = address; - return this; - } - - private Builder addressVersion(long addressVersion) { - this.addressVersion = addressVersion; - return this; - } - - private Builder stream(long stream) { - this.stream = stream; - return this; - } - - private Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - private Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - private Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - private Builder nonceTrialsPerByte(long nonceTrialsPerByte) { - this.nonceTrialsPerByte = nonceTrialsPerByte; - return this; - } - - private Builder extraBytes(long extraBytes) { - this.extraBytes = extraBytes; - return this; - } - - private Builder destinationRipe(byte[] ripe) { - if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg"); - this.destinationRipe = ripe; - return this; - } - - public Builder encoding(Encoding encoding) { - this.encoding = encoding.getCode(); - return this; - } - - private Builder encoding(long encoding) { - this.encoding = encoding; - return this; - } - - public Builder message(ExtendedEncoding message) { - this.encoding = EXTENDED.getCode(); - this.message = message.zip(); - return this; - } - - public Builder message(String subject, String message) { - try { - this.encoding = SIMPLE.getCode(); - this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - return this; - } - - public Builder message(byte[] message) { - this.message = message; - return this; - } - - public Builder ackMessage(byte[] ack) { - if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ackMessage only allowed for msg"); - this.ackMessage = ack; - return this; - } - - public Builder ackData(byte[] ackData) { - if (type != Type.MSG && ackData != null) - throw new IllegalArgumentException("ackMessage only allowed for msg"); - this.ackData = ackData; - return this; - } - - public Builder signature(byte[] signature) { - this.signature = signature; - return this; - } - - public Builder sent(Long sent) { - this.sent = sent; - return this; - } - - public Builder received(Long received) { - this.received = received; - return this; - } - - public Builder status(Status status) { - this.status = status; - return this; - } - - public Builder labels(Collection<Label> labels) { - this.labels.addAll(labels); - return this; - } - - public Builder ttl(long ttl) { - this.ttl = ttl; - return this; - } - - public Builder retries(int retries) { - this.retries = retries; - return this; - } - - public Builder nextTry(Long nextTry) { - this.nextTry = nextTry; - return this; - } - - public Builder conversation(UUID id) { - this.conversation = id; - return this; - } - - public Plaintext build() { - if (from == null) { - from = new BitmessageAddress(Factory.createPubkey( - addressVersion, - stream, - publicSigningKey, - publicEncryptionKey, - nonceTrialsPerByte, - extraBytes, - behaviorBitfield - )); - } - if (to == null && type != Type.BROADCAST && destinationRipe != null) { - to = new BitmessageAddress(0, 0, destinationRipe); - } - if (type == Type.MSG && ackMessage == null && ackData == null) { - ackData = cryptography().randomBytes(Msg.ACK_LENGTH); - } - if (ttl <= 0) { - ttl = TTL.msg(); - } - if (conversation == null) { - conversation = UUID.randomUUID(); - } - return new Plaintext(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt new file mode 100644 index 0000000..8946d73 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -0,0 +1,706 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED +import ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.entity.valueobject.extended.Attachment +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.factory.ExtendedEncodingFactory +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.* +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.* +import java.nio.ByteBuffer +import java.util.* +import java.util.Collections +import kotlin.collections.HashSet + +/** + * The unencrypted message to be sent by 'msg' or 'broadcast'. + */ +class Plaintext private constructor( + val type: Type, + val from: BitmessageAddress, + to: BitmessageAddress?, + val encodingCode: Long, + val message: ByteArray, + val ackData: ByteArray?, + ackMessage: Lazy<ObjectMessage?>, + val conversationId: UUID = UUID.randomUUID(), + var inventoryVector: InventoryVector? = null, + var signature: ByteArray? = null, + val received: Long? = null, + var initialHash: ByteArray? = null, + ttl: Long = TTL.msg, + val labels: MutableSet<Label> = HashSet(), + status: Status +) : Streamable { + + var id: Any? = null + set(id) { + if (this.id != null) throw IllegalStateException("ID already set") + field = id + } + + var to: BitmessageAddress? = to + set(to) { + if (to == null) { + return + } + if (this.to != null) { + if (this.to!!.version != 0L) + throw IllegalStateException("Correct address already set") + if (!Arrays.equals(this.to!!.ripe, to.ripe)) { + throw IllegalArgumentException("RIPEs don't match") + } + } + field = to + } + + val stream: Long + get() = to?.stream ?: from.stream + + val extendedData: ExtendedEncoding? by lazy { + if (encodingCode == EXTENDED.code) { + ExtendedEncodingFactory.unzip(message) + } else { + null + } + } + + val ackMessage: ObjectMessage? by ackMessage + + var status: Status = status + set(status) { + if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) { + sent = UnixTime.now + } + field = status + } + + val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) } + var sent: Long? = null + private set + var retries: Int = 0 + private set + var nextTry: Long? = null + private set + val ttl: Long = ttl + @JvmName("getTTL") get + + constructor( + type: Type, + from: BitmessageAddress, + to: BitmessageAddress?, + encoding: Encoding, + message: ByteArray, + ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), + conversationId: UUID = UUID.randomUUID(), + inventoryVector: InventoryVector? = null, + signature: ByteArray? = null, + received: Long? = null, + initialHash: ByteArray? = null, + ttl: Long = TTL.msg, + labels: MutableSet<Label> = HashSet(), + status: Status + ) : this( + type, + from, + to, + encoding.code, + message, + ackData, + lazy { Factory.createAck(from, ackData, ttl) }, + conversationId, + inventoryVector, + signature, + received, + initialHash, + ttl, + labels, + status + ) + + constructor( + type: Type, + from: BitmessageAddress, + to: BitmessageAddress?, + encoding: Long, + message: ByteArray, + ackMessage: ByteArray?, + conversationId: UUID = UUID.randomUUID(), + inventoryVector: InventoryVector? = null, + signature: ByteArray? = null, + received: Long? = null, + initialHash: ByteArray? = null, + ttl: Long = TTL.msg, + labels: MutableSet<Label> = HashSet(), + status: Status + ) : this( + type, + from, + to, + encoding, + message, + null, + lazy { + if (ackMessage != null && ackMessage.isNotEmpty()) { + Factory.getObjectMessage( + 3, + ByteArrayInputStream(ackMessage), + ackMessage.size) + } else null + }, + conversationId, + inventoryVector, + signature, + received, + initialHash, + ttl, + labels, + status + ) + + constructor(builder: Builder) : this( + builder.type, + builder.from!!, + builder.to, + builder.encoding, + builder.message, + builder.ackData, + lazy { + val ackMsg = builder.ackMessage + if (ackMsg != null && ackMsg.isNotEmpty()) { + Factory.getObjectMessage( + 3, + ByteArrayInputStream(ackMsg), + ackMsg.size) + } else { + Factory.createAck(builder.from!!, builder.ackData, builder.ttl) + } + }, + builder.conversation ?: UUID.randomUUID(), + builder.inventoryVector, + builder.signature, + builder.received, + null, + builder.ttl, + builder.labels, + builder.status ?: Status.RECEIVED + ) + + fun write(out: OutputStream, includeSignature: Boolean) { + Encode.varInt(from.version, out) + Encode.varInt(from.stream, out) + if (from.pubkey == null) { + Encode.int32(0, out) + val empty = ByteArray(64) + out.write(empty) + out.write(empty) + if (from.version >= 3) { + Encode.varInt(0, out) + Encode.varInt(0, out) + } + } else { + Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), out) + out.write(from.pubkey!!.signingKey, 1, 64) + out.write(from.pubkey!!.encryptionKey, 1, 64) + if (from.version >= 3) { + Encode.varInt(from.pubkey!!.nonceTrialsPerByte, out) + Encode.varInt(from.pubkey!!.extraBytes, out) + } + } + if (type == Type.MSG) { + out.write(to!!.ripe) + } + Encode.varInt(encodingCode, out) + Encode.varInt(message.size.toLong(), out) + out.write(message) + if (type == Type.MSG) { + if (to?.has(Feature.DOES_ACK) ?: false) { + val ack = ByteArrayOutputStream() + ackMessage?.write(ack) + Encode.varBytes(ack.toByteArray(), out) + } else { + Encode.varInt(0, out) + } + } + if (includeSignature) { + if (signature == null) { + Encode.varInt(0, out) + } else { + Encode.varInt(signature!!.size.toLong(), out) + out.write(signature!!) + } + } + } + + fun write(buffer: ByteBuffer, includeSignature: Boolean) { + Encode.varInt(from.version, buffer) + Encode.varInt(from.stream, buffer) + if (from.pubkey == null) { + Encode.int32(0, buffer) + val empty = ByteArray(64) + buffer.put(empty) + buffer.put(empty) + if (from.version >= 3) { + Encode.varInt(0, buffer) + Encode.varInt(0, buffer) + } + } else { + Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), buffer) + buffer.put(from.pubkey!!.signingKey, 1, 64) + buffer.put(from.pubkey!!.encryptionKey, 1, 64) + if (from.version >= 3) { + Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer) + Encode.varInt(from.pubkey!!.extraBytes, buffer) + } + } + if (type == Type.MSG) { + buffer.put(to!!.ripe) + } + Encode.varInt(encodingCode, buffer) + Encode.varInt(message.size.toLong(), buffer) + buffer.put(message) + if (type == Type.MSG) { + if (to!!.has(Feature.DOES_ACK) && ackMessage != null) { + Encode.varBytes(Encode.bytes(ackMessage!!), buffer) + } else { + Encode.varInt(0, buffer) + } + } + if (includeSignature) { + val sig = signature + if (sig == null) { + Encode.varInt(0, buffer) + } else { + Encode.varInt(sig.size.toLong(), buffer) + buffer.put(sig) + } + } + } + + override fun write(out: OutputStream) { + write(out, true) + } + + override fun write(buffer: ByteBuffer) { + write(buffer, true) + } + + fun updateNextTry() { + if (to != null) { + if (nextTry == null) { + if (sent != null && to!!.has(Feature.DOES_ACK)) { + nextTry = UnixTime.now + ttl + retries++ + } + } else { + nextTry = nextTry!! + (1 shl retries) * ttl + retries++ + } + } + } + + val subject: String? + get() { + val s = Scanner(ByteArrayInputStream(message), "UTF-8") + val firstLine = s.nextLine() + if (encodingCode == EXTENDED.code) { + if (Message.TYPE == extendedData?.type) { + return (extendedData!!.content as Message?)?.subject + } else { + return null + } + } else if (encodingCode == SIMPLE.code) { + return firstLine.substring("Subject:".length).trim { it <= ' ' } + } else if (firstLine.length > 50) { + return firstLine.substring(0, 50).trim { it <= ' ' } + "..." + } else { + return firstLine + } + } + + val text: String? + get() { + if (encodingCode == EXTENDED.code) { + if (Message.TYPE == extendedData?.type) { + return (extendedData?.content as Message?)?.body + } else { + return null + } + } else { + val text = String(message) + if (encodingCode == SIMPLE.code) { + return text.substring(text.indexOf("\nBody:") + 6) + } + return text + } + } + + fun <T : ExtendedEncoding.ExtendedType> getExtendedData(type: Class<T>): T? { + val extendedData = extendedData ?: return null + if (type.isInstance(extendedData.content)) { + @Suppress("UNCHECKED_CAST") + return extendedData.content as T + } + return null + } + + val parents: List<InventoryVector> + get() { + val extendedData = extendedData ?: return emptyList() + if (Message.TYPE == extendedData.type) { + return (extendedData.content as Message).parents + } else { + return emptyList() + } + } + + val files: List<Attachment> + get() { + val extendedData = extendedData ?: return emptyList() + if (Message.TYPE == extendedData.type) { + return (extendedData.content as Message).files + } else { + return emptyList() + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Plaintext) return false + return encoding == other.encoding && + from == other.from && + Arrays.equals(message, other.message) && + ackMessage == other.ackMessage && + Arrays.equals(to?.ripe, other.to?.ripe) && + Arrays.equals(signature, other.signature) && + status == other.status && + sent == other.sent && + received == other.received && + labels == other.labels + } + + override fun hashCode(): Int { + return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels) + } + + fun addLabels(vararg labels: Label) { + Collections.addAll(this.labels, *labels) + } + + fun addLabels(labels: Collection<Label>?) { + if (labels != null) { + this.labels.addAll(labels) + } + } + + fun removeLabel(type: Label.Type) { + labels.removeIf { it.type == type } + } + + fun isUnread(): Boolean { + return labels.any { it.type == Label.Type.UNREAD } + } + + override fun toString(): String { + val subject = subject + if (subject?.isNotEmpty() ?: false) { + return subject!! + } else { + return Strings.hex( + initialHash ?: return super.toString() + ) + } + } + + enum class Encoding constructor(code: Long) { + IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); + + var code: Long = 0 + internal set + + init { + this.code = code + } + + companion object { + + @JvmStatic fun fromCode(code: Long): Encoding? { + for (e in values()) { + if (e.code == code) { + return e + } + } + return null + } + } + } + + enum class Status { + DRAFT, + // For sent messages + PUBKEY_REQUESTED, + DOING_PROOF_OF_WORK, + SENT, + SENT_ACKNOWLEDGED, + RECEIVED + } + + enum class Type { + MSG, BROADCAST + } + + class Builder(internal val type: Type) { + internal var id: Any? = null + internal var inventoryVector: InventoryVector? = null + internal var from: BitmessageAddress? = null + internal var to: BitmessageAddress? = null + private var addressVersion: Long = 0 + private var stream: Long = 0 + private var behaviorBitfield: Int = 0 + private var publicSigningKey: ByteArray? = null + private var publicEncryptionKey: ByteArray? = null + private var nonceTrialsPerByte: Long = 0 + private var extraBytes: Long = 0 + private var destinationRipe: ByteArray? = null + internal var encoding: Long = 0 + internal var message = ByteArray(0) + internal var ackData: ByteArray? = null + internal var ackMessage: ByteArray? = null + internal var signature: ByteArray? = null + internal var sent: Long? = null + internal var received: Long? = null + internal var status: Status? = null + internal val labels = LinkedHashSet<Label>() + internal var ttl: Long = 0 + internal var retries: Int = 0 + internal var nextTry: Long? = null + internal var conversation: UUID? = null + + fun id(id: Any): Builder { + this.id = id + return this + } + + fun IV(iv: InventoryVector?): Builder { + this.inventoryVector = iv + return this + } + + fun from(address: BitmessageAddress): Builder { + from = address + return this + } + + fun to(address: BitmessageAddress): Builder { + if (type != Type.MSG && to != null) + throw IllegalArgumentException("recipient address only allowed for msg") + to = address + return this + } + + fun addressVersion(addressVersion: Long): Builder { + this.addressVersion = addressVersion + return this + } + + fun stream(stream: Long): Builder { + this.stream = stream + return this + } + + fun behaviorBitfield(behaviorBitfield: Int): Builder { + this.behaviorBitfield = behaviorBitfield + return this + } + + fun publicSigningKey(publicSigningKey: ByteArray): Builder { + this.publicSigningKey = publicSigningKey + return this + } + + fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { + this.publicEncryptionKey = publicEncryptionKey + return this + } + + fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder { + this.nonceTrialsPerByte = nonceTrialsPerByte + return this + } + + fun extraBytes(extraBytes: Long): Builder { + this.extraBytes = extraBytes + return this + } + + fun destinationRipe(ripe: ByteArray?): Builder { + if (type != Type.MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg") + this.destinationRipe = ripe + return this + } + + fun encoding(encoding: Encoding): Builder { + this.encoding = encoding.code + return this + } + + fun encoding(encoding: Long): Builder { + this.encoding = encoding + return this + } + + fun message(message: ExtendedEncoding): Builder { + this.encoding = EXTENDED.code + this.message = message.zip() + return this + } + + fun message(subject: String, message: String): Builder { + try { + this.encoding = SIMPLE.code + this.message = "Subject:$subject\nBody:$message".toByteArray(charset("UTF-8")) + } catch (e: UnsupportedEncodingException) { + throw ApplicationException(e) + } + + return this + } + + fun message(message: ByteArray): Builder { + this.message = message + return this + } + + fun ackMessage(ack: ByteArray?): Builder { + if (type != Type.MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg") + this.ackMessage = ack + return this + } + + fun ackData(ackData: ByteArray?): Builder { + if (type != Type.MSG && ackData != null) + throw IllegalArgumentException("ackMessage only allowed for msg") + this.ackData = ackData + return this + } + + fun signature(signature: ByteArray): Builder { + this.signature = signature + return this + } + + fun sent(sent: Long?): Builder { + this.sent = sent + return this + } + + fun received(received: Long?): Builder { + this.received = received + return this + } + + fun status(status: Status): Builder { + this.status = status + return this + } + + fun labels(labels: Collection<Label>): Builder { + this.labels.addAll(labels) + return this + } + + fun ttl(ttl: Long): Builder { + this.ttl = ttl + return this + } + + fun retries(retries: Int): Builder { + this.retries = retries + return this + } + + fun nextTry(nextTry: Long?): Builder { + this.nextTry = nextTry + return this + } + + fun conversation(id: UUID): Builder { + this.conversation = id + return this + } + + fun build(): Plaintext { + if (from == null) { + from = BitmessageAddress(Factory.createPubkey( + addressVersion, + stream, + publicSigningKey!!, + publicEncryptionKey!!, + nonceTrialsPerByte, + extraBytes, + behaviorBitfield + )) + } + if (to == null && type != Type.BROADCAST && destinationRipe != null) { + to = BitmessageAddress(0, 0, destinationRipe!!) + } + if (type == Type.MSG && ackMessage == null && ackData == null) { + ackData = cryptography().randomBytes(Msg.ACK_LENGTH) + } + if (ttl <= 0) { + ttl = TTL.msg + } + return Plaintext(this) + } + } + + companion object { + + @JvmStatic fun read(type: Type, `in`: InputStream): Plaintext { + return readWithoutSignature(type, `in`) + .signature(Decode.varBytes(`in`)) + .received(UnixTime.now) + .build() + } + + @JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder { + val version = Decode.varInt(`in`) + return Builder(type) + .addressVersion(version) + .stream(Decode.varInt(`in`)) + .behaviorBitfield(Decode.int32(`in`)) + .publicSigningKey(Decode.bytes(`in`, 64)) + .publicEncryptionKey(Decode.bytes(`in`, 64)) + .nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) + .extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) + .destinationRipe(if (type == Type.MSG) Decode.bytes(`in`, 20) else null) + .encoding(Decode.varInt(`in`)) + .message(Decode.varBytes(`in`)) + .ackMessage(if (type == Type.MSG) Decode.varBytes(`in`) else null) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java b/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt similarity index 80% rename from core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java rename to core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt index 3133cab..e890b13 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity -public interface PlaintextHolder { - Plaintext getPlaintext(); +interface PlaintextHolder { + val plaintext: Plaintext? } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java deleted file mode 100644 index e75a926..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Serializable; -import java.nio.ByteBuffer; - -/** - * An object that can be written to an {@link OutputStream} - */ -public interface Streamable extends Serializable { - void write(OutputStream stream) throws IOException; - - void write(ByteBuffer buffer); -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt new file mode 100644 index 0000000..b8ad391 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2017 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.entity + +import java.io.OutputStream +import java.io.Serializable +import java.nio.ByteBuffer + +/** + * An object that can be written to an [OutputStream] + */ +interface Streamable : Serializable { + fun write(out: OutputStream) + + fun write(buffer: ByteBuffer) +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt similarity index 63% rename from core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java rename to core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt index 3d30f32..268dabe 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,27 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; +import java.io.OutputStream +import java.nio.ByteBuffer /** * The 'verack' command answers a 'version' command, accepting the other node's version. */ -public class VerAck implements MessagePayload { - private static final long serialVersionUID = -4302074845199181687L; +class VerAck : MessagePayload { - @Override - public Command getCommand() { - return Command.VERACK; - } + override val command: MessagePayload.Command = MessagePayload.Command.VERACK - @Override - public void write(OutputStream stream) throws IOException { + override fun write(out: OutputStream) { // 'verack' doesn't have any payload, so there is nothing to write } - @Override - public void write(ByteBuffer buffer) { + override fun write(buffer: ByteBuffer) { // 'verack' doesn't have any payload, so there is nothing to write } + + companion object { + private val serialVersionUID = -4302074845199181687L + } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java deleted file mode 100644 index bfb4869..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.utils.Encode; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * The 'version' command advertises this node's latest supported protocol version upon initiation. - */ -public class Version implements MessagePayload { - private static final long serialVersionUID = 7219240857343176567L; - - /** - * Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's - * version is lower but continue with the connection if it is higher. - */ - private final int version; - - /** - * bitfield of features to be enabled for this connection - */ - private final long services; - - /** - * standard UNIX timestamp in seconds - */ - private final long timestamp; - - /** - * The network address of the node receiving this message (not including the time or stream number) - */ - private final NetworkAddress addrRecv; - - /** - * The network address of the node emitting this message (not including the time or stream number and the ip itself - * is ignored by the receiver) - */ - private final NetworkAddress addrFrom; - - /** - * Random nonce used to detect connections to self. - */ - private final long nonce; - - /** - * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. - */ - private final String userAgent; - - /** - * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 - * stream numbers. - */ - private final long[] streams; - - private Version(Builder builder) { - version = builder.version; - services = builder.services; - timestamp = builder.timestamp; - addrRecv = builder.addrRecv; - addrFrom = builder.addrFrom; - nonce = builder.nonce; - userAgent = builder.userAgent; - streams = builder.streamNumbers; - } - - public int getVersion() { - return version; - } - - public long getServices() { - return services; - } - - public boolean provides(Service service) { - return service != null && service.isEnabled(services); - } - - public long getTimestamp() { - return timestamp; - } - - public NetworkAddress getAddrRecv() { - return addrRecv; - } - - public NetworkAddress getAddrFrom() { - return addrFrom; - } - - public long getNonce() { - return nonce; - } - - public String getUserAgent() { - return userAgent; - } - - public long[] getStreams() { - return streams; - } - - @Override - public Command getCommand() { - return Command.VERSION; - } - - @Override - public void write(OutputStream stream) throws IOException { - Encode.int32(version, stream); - Encode.int64(services, stream); - Encode.int64(timestamp, stream); - addrRecv.write(stream, true); - addrFrom.write(stream, true); - Encode.int64(nonce, stream); - Encode.varString(userAgent, stream); - Encode.varIntList(streams, stream); - } - - @Override - public void write(ByteBuffer buffer) { - Encode.int32(version, buffer); - Encode.int64(services, buffer); - Encode.int64(timestamp, buffer); - addrRecv.write(buffer, true); - addrFrom.write(buffer, true); - Encode.int64(nonce, buffer); - Encode.varString(userAgent, buffer); - Encode.varIntList(streams, buffer); - } - - - public static final class Builder { - private int version; - private long services; - private long timestamp; - private NetworkAddress addrRecv; - private NetworkAddress addrFrom; - private long nonce; - private String userAgent; - private long[] streamNumbers; - - public Builder defaults(long clientNonce) { - version = BitmessageContext.CURRENT_VERSION; - services = Service.getServiceFlag(Service.NODE_NETWORK); - timestamp = UnixTime.now(); - userAgent = "/Jabit:0.0.1/"; - streamNumbers = new long[]{1}; - nonce = clientNonce; - return this; - } - - public Builder version(int version) { - this.version = version; - return this; - } - - public Builder services(Service... services) { - this.services = Service.getServiceFlag(services); - return this; - } - - public Builder services(long services) { - this.services = services; - return this; - } - - public Builder timestamp(long timestamp) { - this.timestamp = timestamp; - return this; - } - - public Builder addrRecv(NetworkAddress addrRecv) { - this.addrRecv = addrRecv; - return this; - } - - public Builder addrFrom(NetworkAddress addrFrom) { - this.addrFrom = addrFrom; - return this; - } - - public Builder nonce(long nonce) { - this.nonce = nonce; - return this; - } - - public Builder userAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - public Builder streams(long... streamNumbers) { - this.streamNumbers = streamNumbers; - return this; - } - - public Version build() { - return new Version(this); - } - } - - public enum Service { - NODE_NETWORK(1); -// TODO: NODE_SSL(2); - - long flag; - - Service(long flag) { - this.flag = flag; - } - - public boolean isEnabled(long flag) { - return (flag & this.flag) != 0; - } - - public static long getServiceFlag(Service... services) { - long flag = 0; - for (Service service : services) { - flag |= service.flag; - } - return flag; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt new file mode 100644 index 0000000..3a1c3f7 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.UnixTime +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'version' command advertises this node's latest supported protocol version upon initiation. + */ +class Version constructor( + /** + * Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's + * version is lower but continue with the connection if it is higher. + */ + val version: Int = BitmessageContext.CURRENT_VERSION, + + /** + * bitfield of features to be enabled for this connection + */ + val services: Long = Version.Service.getServiceFlag(Version.Service.NODE_NETWORK), + + /** + * standard UNIX timestamp in seconds + */ + val timestamp: Long = UnixTime.now, + + /** + * The network address of the node receiving this message (not including the time or stream number) + */ + val addrRecv: NetworkAddress, + + /** + * The network address of the node emitting this message (not including the time or stream number and the ip itself + * is ignored by the receiver) + */ + val addrFrom: NetworkAddress, + + /** + * Random nonce used to detect connections to self. + */ + val nonce: Long, + + /** + * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. + */ + val userAgent: String = "/Jabit:0.0.1/", + + /** + * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 + * stream numbers. + */ + val streams: LongArray = longArrayOf(1) +) : MessagePayload { + + fun provides(service: Service?): Boolean { + return service != null && service.isEnabled(services) + } + + override val command: MessagePayload.Command = MessagePayload.Command.VERSION + + override fun write(out: OutputStream) { + Encode.int32(version.toLong(), out) + Encode.int64(services, out) + Encode.int64(timestamp, out) + addrRecv.write(out, true) + addrFrom.write(out, true) + Encode.int64(nonce, out) + Encode.varString(userAgent, out) + Encode.varIntList(streams, out) + } + + override fun write(buffer: ByteBuffer) { + Encode.int32(version.toLong(), buffer) + Encode.int64(services, buffer) + Encode.int64(timestamp, buffer) + addrRecv.write(buffer, true) + addrFrom.write(buffer, true) + Encode.int64(nonce, buffer) + Encode.varString(userAgent, buffer) + Encode.varIntList(streams, buffer) + } + + class Builder { + private var version: Int = 0 + private var services: Long = 0 + private var timestamp: Long = 0 + private var addrRecv: NetworkAddress? = null + private var addrFrom: NetworkAddress? = null + private var nonce: Long = 0 + private var userAgent: String? = null + private var streamNumbers: LongArray? = null + + fun defaults(clientNonce: Long): Builder { + version = BitmessageContext.CURRENT_VERSION + services = Service.getServiceFlag(Service.NODE_NETWORK) + timestamp = UnixTime.now + userAgent = "/Jabit:0.0.1/" + streamNumbers = longArrayOf(1) + nonce = clientNonce + return this + } + + fun version(version: Int): Builder { + this.version = version + return this + } + + fun services(vararg services: Service): Builder { + this.services = Service.getServiceFlag(*services) + return this + } + + fun services(services: Long): Builder { + this.services = services + return this + } + + fun timestamp(timestamp: Long): Builder { + this.timestamp = timestamp + return this + } + + fun addrRecv(addrRecv: NetworkAddress): Builder { + this.addrRecv = addrRecv + return this + } + + fun addrFrom(addrFrom: NetworkAddress): Builder { + this.addrFrom = addrFrom + return this + } + + fun nonce(nonce: Long): Builder { + this.nonce = nonce + return this + } + + fun userAgent(userAgent: String): Builder { + this.userAgent = userAgent + return this + } + + fun streams(vararg streamNumbers: Long): Builder { + this.streamNumbers = streamNumbers + return this + } + + fun build(): Version { + val addrRecv = this.addrRecv + val addrFrom = this.addrFrom + if (addrRecv == null || addrFrom == null) { + throw IllegalStateException("Receiving and sending address must be set") + } + + return Version( + version = version, + services = services, + timestamp = timestamp, + addrRecv = addrRecv, addrFrom = addrFrom, + nonce = nonce, + userAgent = userAgent ?: "/Jabit:0.0.1/", + streams = streamNumbers ?: longArrayOf(1) + ) + } + } + + enum class Service constructor(internal var flag: Long) { + // TODO: NODE_SSL(2); + NODE_NETWORK(1); + + fun isEnabled(flag: Long): Boolean { + return (flag and this.flag) != 0L + } + + companion object { + fun getServiceFlag(vararg services: Service): Long { + var flag: Long = 0 + for (service in services) { + flag = flag or service.flag + } + return flag + } + } + } +} 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 deleted file mode 100644 index a583330..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.entity.Plaintext; -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.cryptography; - -/** - * Users who are subscribed to the sending address will see the message appear in their inbox. - * Broadcasts are version 4 or 5. - */ -public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder { - private static final long serialVersionUID = 4064521827582239069L; - - protected final long stream; - protected CryptoBox encrypted; - protected Plaintext plaintext; - - protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { - super(version); - this.stream = stream; - this.encrypted = encrypted; - this.plaintext = plaintext; - } - - public static long getVersion(BitmessageAddress address) { - return address.getVersion() < 4 ? 4 : 5; - } - - @Override - public boolean isSigned() { - return true; - } - - @Override - public byte[] getSignature() { - return plaintext.getSignature(); - } - - @Override - public void setSignature(byte[] signature) { - plaintext.setSignature(signature); - } - - @Override - public long getStream() { - return stream; - } - - @Override - public Plaintext getPlaintext() { - return plaintext; - } - - @Override - public void encrypt(byte[] publicKey) throws IOException { - this.encrypted = new CryptoBox(plaintext, publicKey); - } - - public void encrypt() throws IOException { - encrypt(cryptography().createPublicKey(plaintext.getFrom().getPublicDecryptionKey())); - } - - @Override - public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey)); - } - - public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException { - decrypt(address.getPublicDecryptionKey()); - } - - @Override - 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/Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt new file mode 100644 index 0000000..9cc5772 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.PlaintextHolder +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.util.* + +/** + * Users who are subscribed to the sending address will see the message appear in their inbox. + * Broadcasts are version 4 or 5. + */ +abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder { + + override val isSigned: Boolean = true + + override var signature: ByteArray? + get() = plaintext?.signature + set(signature) { + plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available") + } + + override fun encrypt(publicKey: ByteArray) { + this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey) + } + + fun encrypt() { + encrypt(cryptography().createPublicKey(plaintext?.from?.publicDecryptionKey ?: return)) + } + + @Throws(DecryptionFailedException::class) + override fun decrypt(privateKey: ByteArray) { + plaintext = Plaintext.read(BROADCAST, encrypted?.decrypt(privateKey) ?: return) + } + + @Throws(DecryptionFailedException::class) + fun decrypt(address: BitmessageAddress) { + decrypt(address.publicDecryptionKey) + } + + override val isDecrypted: Boolean + get() = plaintext != null + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Broadcast) return false + return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) + } + + override fun hashCode(): Int { + return Objects.hash(stream) + } + + companion object { + fun getVersion(address: BitmessageAddress): Long { + return if (address.version < 4) 4L else 5L + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java deleted file mode 100644 index f7f3c15..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.Arrays; - -import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - - -public class CryptoBox implements Streamable { - private static final long serialVersionUID = 7217659539975573852L; - private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class); - - private final byte[] initializationVector; - private final int curveType; - private final byte[] R; - private final byte[] mac; - private byte[] encrypted; - - - public CryptoBox(Streamable data, byte[] K) throws IOException { - this(Encode.bytes(data), K); - } - - public CryptoBox(byte[] data, byte[] K) throws IOException { - curveType = 0x02CA; - - // 1. The destination public key is called K. - // 2. Generate 16 random bytes using a secure random number generator. Call them IV. - initializationVector = cryptography().randomBytes(16); - - // 3. Generate a new random EC key pair with private key called r and public key called R. - byte[] r = cryptography().randomBytes(PRIVATE_KEY_SIZE); - R = cryptography().createPublicKey(r); - // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. - byte[] P = cryptography().multiply(K, r); - byte[] X = Points.getX(P); - // 5. Use the X component of public key P and calculate the SHA512 hash H. - byte[] H = cryptography().sha512(X); - // 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. - byte[] key_e = Arrays.copyOfRange(H, 0, 32); - byte[] key_m = Arrays.copyOfRange(H, 32, 64); - // 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. - // 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text. - encrypted = cryptography().crypt(true, data, key_e, initializationVector); - // 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC. - mac = calculateMac(key_m); - - // The resulting data is: IV + R + cipher text + MAC - } - - private CryptoBox(Builder builder) { - initializationVector = builder.initializationVector; - curveType = builder.curveType; - R = cryptography().createPoint(builder.xComponent, builder.yComponent); - encrypted = builder.encrypted; - mac = builder.mac; - } - - public static CryptoBox read(InputStream stream, int length) throws IOException { - AccessCounter counter = new AccessCounter(); - return new Builder() - .IV(Decode.bytes(stream, 16, counter)) - .curveType(Decode.uint16(stream, counter)) - .X(Decode.shortVarBytes(stream, counter)) - .Y(Decode.shortVarBytes(stream, counter)) - .encrypted(Decode.bytes(stream, length - counter.length() - 32)) - .MAC(Decode.bytes(stream, 32)) - .build(); - } - - /** - * @param k a private key, typically should be 32 bytes long - * @return an InputStream yielding the decrypted data - * @throws DecryptionFailedException if the payload can't be decrypted using this private key - * @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a> - */ - public InputStream decrypt(byte[] k) throws DecryptionFailedException { - // 1. The private key used to decrypt is called k. - // 2. Do an EC point multiply with private key k and public key R. This gives you public key P. - byte[] P = cryptography().multiply(R, k); - // 3. Use the X component of public key P and calculate the SHA512 hash H. - byte[] H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33)); - // 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. - byte[] key_e = Arrays.copyOfRange(H, 0, 32); - byte[] key_m = Arrays.copyOfRange(H, 32, 64); - - // 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data. - // 6. Compare MAC with MAC'. If not equal, decryption will fail. - if (!Arrays.equals(mac, calculateMac(key_m))) { - throw new DecryptionFailedException(); - } - - // 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key - // and the cipher text as payload. The output is the padded input text. - return new ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)); - } - - private byte[] calculateMac(byte[] key_m) { - try { - ByteArrayOutputStream macData = new ByteArrayOutputStream(); - writeWithoutMAC(macData); - return cryptography().mac(key_m, macData.toByteArray()); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - private void writeWithoutMAC(OutputStream out) throws IOException { - out.write(initializationVector); - Encode.int16(curveType, out); - writeCoordinateComponent(out, Points.getX(R)); - writeCoordinateComponent(out, Points.getY(R)); - out.write(encrypted); - } - - private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException { - int offset = Bytes.numberOfLeadingZeros(x); - int length = x.length - offset; - Encode.int16(length, out); - out.write(x, offset, length); - } - - private void writeCoordinateComponent(ByteBuffer buffer, byte[] x) { - int offset = Bytes.numberOfLeadingZeros(x); - int length = x.length - offset; - Encode.int16(length, buffer); - buffer.put(x, offset, length); - } - - @Override - public void write(OutputStream stream) throws IOException { - writeWithoutMAC(stream); - stream.write(mac); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(initializationVector); - Encode.int16(curveType, buffer); - writeCoordinateComponent(buffer, Points.getX(R)); - writeCoordinateComponent(buffer, Points.getY(R)); - buffer.put(encrypted); - buffer.put(mac); - } - - public static final class Builder { - private byte[] initializationVector; - private int curveType; - private byte[] xComponent; - private byte[] yComponent; - private byte[] encrypted; - private byte[] mac; - - public Builder IV(byte[] initializationVector) { - this.initializationVector = initializationVector; - return this; - } - - public Builder curveType(int curveType) { - if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType); - this.curveType = curveType; - return this; - } - - public Builder X(byte[] xComponent) { - this.xComponent = xComponent; - return this; - } - - public Builder Y(byte[] yComponent) { - this.yComponent = yComponent; - return this; - } - - private Builder encrypted(byte[] encrypted) { - this.encrypted = encrypted; - return this; - } - - public Builder MAC(byte[] mac) { - this.mac = mac; - return this; - } - - public CryptoBox build() { - return new CryptoBox(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt new file mode 100644 index 0000000..15f8bef --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.* +import ch.dissem.bitmessage.utils.Singleton.cryptography +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + + +class CryptoBox : Streamable { + + private val initializationVector: ByteArray + private val curveType: Int + private val R: ByteArray + private val mac: ByteArray + private var encrypted: ByteArray + + constructor(data: Streamable, K: ByteArray) : this(Encode.bytes(data), K) + + constructor(data: ByteArray, K: ByteArray) { + curveType = 0x02CA + + // 1. The destination public key is called K. + // 2. Generate 16 random bytes using a secure random number generator. Call them IV. + initializationVector = cryptography().randomBytes(16) + + // 3. Generate a new random EC key pair with private key called r and public key called R. + val r = cryptography().randomBytes(PRIVATE_KEY_SIZE) + R = cryptography().createPublicKey(r) + // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. + val P = cryptography().multiply(K, r) + val X = Points.getX(P) + // 5. Use the X component of public key P and calculate the SHA512 hash H. + val H = cryptography().sha512(X) + // 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. + val key_e = Arrays.copyOfRange(H, 0, 32) + val key_m = Arrays.copyOfRange(H, 32, 64) + // 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. + // 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text. + encrypted = cryptography().crypt(true, data, key_e, initializationVector) + // 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC. + mac = calculateMac(key_m) + + // The resulting data is: IV + R + cipher text + MAC + } + + private constructor(builder: Builder) { + initializationVector = builder.initializationVector!! + curveType = builder.curveType + R = cryptography().createPoint(builder.xComponent!!, builder.yComponent!!) + encrypted = builder.encrypted!! + mac = builder.mac!! + } + + /** + * @param k a private key, typically should be 32 bytes long + * * + * @return an InputStream yielding the decrypted data + * * + * @throws DecryptionFailedException if the payload can't be decrypted using this private key + * * + * @see [https://bitmessage.org/wiki/Encryption.Decryption](https://bitmessage.org/wiki/Encryption.Decryption) + */ + @Throws(DecryptionFailedException::class) + fun decrypt(k: ByteArray): InputStream { + // 1. The private key used to decrypt is called k. + // 2. Do an EC point multiply with private key k and public key R. This gives you public key P. + val P = cryptography().multiply(R, k) + // 3. Use the X component of public key P and calculate the SHA512 hash H. + val H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33)) + // 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. + val key_e = Arrays.copyOfRange(H, 0, 32) + val key_m = Arrays.copyOfRange(H, 32, 64) + + // 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data. + // 6. Compare MAC with MAC'. If not equal, decryption will fail. + if (!Arrays.equals(mac, calculateMac(key_m))) { + throw DecryptionFailedException() + } + + // 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key + // and the cipher text as payload. The output is the padded input text. + return ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)) + } + + private fun calculateMac(key_m: ByteArray): ByteArray { + val macData = ByteArrayOutputStream() + writeWithoutMAC(macData) + return cryptography().mac(key_m, macData.toByteArray()) + } + + private fun writeWithoutMAC(out: OutputStream) { + out.write(initializationVector) + Encode.int16(curveType.toLong(), out) + writeCoordinateComponent(out, Points.getX(R)) + writeCoordinateComponent(out, Points.getY(R)) + out.write(encrypted) + } + + private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { + val offset = Bytes.numberOfLeadingZeros(x) + val length = x.size - offset + Encode.int16(length.toLong(), out) + out.write(x, offset, length) + } + + private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) { + val offset = Bytes.numberOfLeadingZeros(x) + val length = x.size - offset + Encode.int16(length.toLong(), buffer) + buffer.put(x, offset, length) + } + + override fun write(out: OutputStream) { + writeWithoutMAC(out) + out.write(mac) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(initializationVector) + Encode.int16(curveType.toLong(), buffer) + writeCoordinateComponent(buffer, Points.getX(R)) + writeCoordinateComponent(buffer, Points.getY(R)) + buffer.put(encrypted) + buffer.put(mac) + } + + class Builder { + internal var initializationVector: ByteArray? = null + internal var curveType: Int = 0 + internal var xComponent: ByteArray? = null + internal var yComponent: ByteArray? = null + internal var encrypted: ByteArray? = null + internal var mac: ByteArray? = null + + fun IV(initializationVector: ByteArray): Builder { + this.initializationVector = initializationVector + return this + } + + fun curveType(curveType: Int): Builder { + if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType) + this.curveType = curveType + return this + } + + fun X(xComponent: ByteArray): Builder { + this.xComponent = xComponent + return this + } + + fun Y(yComponent: ByteArray): Builder { + this.yComponent = yComponent + return this + } + + fun encrypted(encrypted: ByteArray): Builder { + this.encrypted = encrypted + return this + } + + fun MAC(mac: ByteArray): Builder { + this.mac = mac + return this + } + + fun build(): CryptoBox { + return CryptoBox(this) + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) + + fun read(stream: InputStream, length: Int): CryptoBox { + val counter = AccessCounter() + return Builder() + .IV(Decode.bytes(stream, 16, counter)) + .curveType(Decode.uint16(stream, counter)) + .X(Decode.shortVarBytes(stream, counter)) + .Y(Decode.shortVarBytes(stream, counter)) + .encrypted(Decode.bytes(stream, length - counter.length() - 32)) + .MAC(Decode.bytes(stream, 32)) + .build() + } + } +} 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 deleted file mode 100644 index 9312160..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really - * have to know what it is. - */ -public class GenericPayload extends ObjectPayload { - private static final long serialVersionUID = -912314085064185940L; - - private long stream; - private byte[] data; - - public GenericPayload(long version, long stream, byte[] data) { - super(version); - this.stream = stream; - this.data = data; - } - - public static GenericPayload read(long version, long stream, InputStream is, int length) throws IOException { - return new GenericPayload(version, stream, Decode.bytes(is, length)); - } - - @Override - public ObjectType getType() { - return null; - } - - @Override - public long getStream() { - return stream; - } - - public byte[] getData() { - return data; - } - - @Override - public void write(OutputStream stream) throws IOException { - stream.write(data); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(data); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GenericPayload that = (GenericPayload) o; - - if (stream != that.stream) return false; - return Arrays.equals(data, that.data); - } - - @Override - public int hashCode() { - int result = (int) (stream ^ (stream >>> 32)); - result = 31 * result + Arrays.hashCode(data); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt new file mode 100644 index 0000000..336630a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.utils.Decode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really + * have to know what it is. + */ +class GenericPayload(version: Long, override val stream: Long, val data: ByteArray) : ObjectPayload(version) { + + override val type: ObjectType? = null + + override fun write(out: OutputStream) { + out.write(data) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(data) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GenericPayload) return false + + if (stream != other.stream) return false + return Arrays.equals(data, other.data) + } + + override fun hashCode(): Int { + var result = (stream xor stream.ushr(32)).toInt() + result = 31 * result + Arrays.hashCode(data) + return result + } + + companion object { + fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { + return GenericPayload(version, stream, Decode.bytes(`is`, length)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java deleted file mode 100644 index d889489..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Request for a public key. - */ -public class GetPubkey extends ObjectPayload { - private static final long serialVersionUID = -3634516646972610180L; - - private long stream; - private byte[] ripeTag; - - public GetPubkey(BitmessageAddress address) { - super(address.getVersion()); - this.stream = address.getStream(); - if (address.getVersion() < 4) - this.ripeTag = address.getRipe(); - else - this.ripeTag = address.getTag(); - } - - private GetPubkey(long version, long stream, byte[] ripeOrTag) { - super(version); - this.stream = stream; - this.ripeTag = ripeOrTag; - } - - public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { - return new GetPubkey(version, stream, Decode.bytes(is, length)); - } - - /** - * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the - * address version. - */ - public byte[] getRipeTag() { - return ripeTag; - } - - @Override - public ObjectType getType() { - return ObjectType.GET_PUBKEY; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public void write(OutputStream stream) throws IOException { - stream.write(ripeTag); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(ripeTag); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt new file mode 100644 index 0000000..3ae9370 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.utils.Decode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Request for a public key. + */ +class GetPubkey : ObjectPayload { + + override val type: ObjectType = ObjectType.GET_PUBKEY + + override var stream: Long = 0 + private set + + /** + * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the + * * address version. + */ + val ripeTag: ByteArray + + constructor(address: BitmessageAddress) : super(address.version) { + this.stream = address.stream + this.ripeTag = if (address.version < 4) address.ripe else + address.tag ?: throw IllegalStateException("Address of version 4 without tag shouldn't exist!") + } + + private constructor(version: Long, stream: Long, ripeOrTag: ByteArray) : super(version) { + this.stream = stream + this.ripeTag = ripeOrTag + } + + override fun write(out: OutputStream) { + out.write(ripeTag) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(ripeTag) + } + + companion object { + fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { + return GetPubkey(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 deleted file mode 100644 index dc36bb1..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.PlaintextHolder; -import ch.dissem.bitmessage.exception.DecryptionFailedException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; - -/** - * Used for person-to-person messages. - */ -public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder { - private static final long serialVersionUID = 4327495048296365733L; - public static final int ACK_LENGTH = 32; - - private long stream; - private CryptoBox encrypted; - private Plaintext plaintext; - - private Msg(long stream, CryptoBox encrypted) { - super(1); - this.stream = stream; - this.encrypted = encrypted; - } - - public Msg(Plaintext plaintext) { - super(1); - this.stream = plaintext.getStream(); - this.plaintext = plaintext; - } - - public static Msg read(InputStream in, long stream, int length) throws IOException { - return new Msg(stream, CryptoBox.read(in, length)); - } - - @Override - public Plaintext getPlaintext() { - return plaintext; - } - - @Override - public ObjectType getType() { - return ObjectType.MSG; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public boolean isSigned() { - return true; - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - plaintext.write(out, false); - } - - @Override - public byte[] getSignature() { - return plaintext.getSignature(); - } - - @Override - public void setSignature(byte[] signature) { - plaintext.setSignature(signature); - } - - @Override - public void encrypt(byte[] publicKey) throws IOException { - this.encrypted = new CryptoBox(plaintext, publicKey); - } - - @Override - public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey)); - } - - @Override - public boolean isDecrypted() { - return plaintext != null; - } - - @Override - public void write(OutputStream out) throws IOException { - if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); - encrypted.write(out); - } - - @Override - public void write(ByteBuffer buffer) { - if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); - encrypted.write(buffer); - } - - @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/Msg.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt new file mode 100644 index 0000000..597220e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.PlaintextHolder +import ch.dissem.bitmessage.exception.DecryptionFailedException +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Used for person-to-person messages. + */ +class Msg : ObjectPayload, Encrypted, PlaintextHolder { + + override val stream: Long + private var encrypted: CryptoBox? + override var plaintext: Plaintext? + private set + + private constructor(stream: Long, encrypted: CryptoBox) : super(1) { + this.stream = stream + this.encrypted = encrypted + this.plaintext = null + } + + constructor(plaintext: Plaintext) : super(1) { + this.stream = plaintext.stream + this.encrypted = null + this.plaintext = plaintext + } + + override val type: ObjectType = ObjectType.MSG + + override val isSigned: Boolean = true + + override fun writeBytesToSign(out: OutputStream) { + plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") + } + + override var signature: ByteArray? + get() = plaintext?.signature + set(signature) { + plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available") + } + + override fun encrypt(publicKey: ByteArray) { + this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey) + } + + @Throws(DecryptionFailedException::class) + override fun decrypt(privateKey: ByteArray) { + plaintext = Plaintext.read(MSG, encrypted!!.decrypt(privateKey)) + } + + override val isDecrypted: Boolean + get() = plaintext != null + + override fun write(out: OutputStream) { + encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") + } + + override fun write(buffer: ByteBuffer) { + encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Msg) return false + if (!super.equals(other)) return false + + return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) + } + + override fun hashCode(): Int { + return stream.toInt() + } + + companion object { + val ACK_LENGTH = 32 + + fun read(`in`: InputStream, stream: Long, length: Int): Msg { + return Msg(stream, CryptoBox.read(`in`, length)) + } + } +} 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 deleted file mode 100644 index 33da28d..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Streamable; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * The payload of an 'object' command. This is shared by the network. - */ -public abstract class ObjectPayload implements Streamable { - private static final long serialVersionUID = -5034977402902364482L; - - private final long version; - - protected ObjectPayload(long version) { - this.version = version; - } - - - public abstract ObjectType getType(); - - public abstract long getStream(); - - public long getVersion() { - return version; - } - - public boolean isSigned() { - return false; - } - - public void writeBytesToSign(OutputStream out) throws IOException { - // nothing to do - } - - /** - * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, - * appended with the data described in this table down to the extra_bytes. Therefore, this must - * be checked and set in the {@link ObjectMessage} object. - */ - public byte[] getSignature() { - return null; - } - - public void setSignature(byte[] signature) { - // nothing to do - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt new file mode 100644 index 0000000..5650652 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Streamable +import java.io.OutputStream + +/** + * The payload of an 'object' command. This is shared by the network. + */ +abstract class ObjectPayload protected constructor(val version: Long) : Streamable { + + abstract val type: ObjectType? + + abstract val stream: Long + + open val isSigned: Boolean = false + + open fun writeBytesToSign(out: OutputStream) { + // nothing to do + } + + /** + * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, + * * appended with the data described in this table down to the extra_bytes. Therefore, this must + * * be checked and set in the [ObjectMessage] object. + */ + open var signature: ByteArray? = null +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt similarity index 64% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt index 06ea92f..967a3fd 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,31 +14,20 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity.payload; +package ch.dissem.bitmessage.entity.payload /** * Known types for 'object' messages. Must not be used where an unknown type must be resent. */ -public enum ObjectType { +enum class ObjectType constructor(val number: Long) { GET_PUBKEY(0), PUBKEY(1), MSG(2), BROADCAST(3); - int number; - - ObjectType(int number) { - this.number = number; - } - - public static ObjectType fromNumber(long number) { - for (ObjectType type : values()) { - if (type.number == number) return type; + companion object { + fun fromNumber(number: Long): ObjectType? { + return values().firstOrNull { it.number == number } } - return null; - } - - public long getNumber() { - return number; } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java deleted file mode 100644 index 27d2da9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Public keys for signing and encryption, the answer to a 'getpubkey' request. - */ -public abstract class Pubkey extends ObjectPayload { - private static final long serialVersionUID = -6634533361454999619L; - - public final static long LATEST_VERSION = 4; - - protected Pubkey(long version) { - super(version); - } - - public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) { - return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)); - } - - public abstract byte[] getSigningKey(); - - public abstract byte[] getEncryptionKey(); - - public abstract int getBehaviorBitfield(); - - public byte[] getRipe() { - return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey())); - } - - public long getNonceTrialsPerByte() { - return 0; - } - - public long getExtraBytes() { - return 0; - } - - public void writeUnencrypted(OutputStream out) throws IOException { - write(out); - } - - public void writeUnencrypted(ByteBuffer buffer){ - write(buffer); - } - - protected byte[] add0x04(byte[] key) { - if (key.length == 65) return key; - byte[] result = new byte[65]; - result[0] = 4; - System.arraycopy(key, 0, result, 1, 64); - return result; - } - - /** - * Bits 0 through 29 are yet undefined - */ - public enum Feature { - /** - * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg - * messages bound for them. - */ - INCLUDE_DESTINATION(30), - /** - * If true, the receiving node does send acknowledgements (rather than dropping them). - */ - DOES_ACK(31); - - private int bit; - - Feature(int bitNumber) { - // The Bitmessage Protocol Specification starts counting at the most significant bit, - // thus the slightly awkward calculation. - // https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features - this.bit = 1 << (31 - bitNumber); - } - - public static int bitfield(Feature... features) { - int bits = 0; - for (Feature feature : features) { - bits |= feature.bit; - } - return bits; - } - - public static Feature[] features(int bitfield) { - ArrayList<Feature> features = new ArrayList<>(Feature.values().length); - for (Feature feature : Feature.values()) { - if ((bitfield & feature.bit) != 0) { - features.add(feature); - } - } - return features.toArray(new Feature[features.size()]); - } - - public boolean isActive(int bitfield) { - return (bitfield & bit) != 0; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt new file mode 100644 index 0000000..87e1355 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * Public keys for signing and encryption, the answer to a 'getpubkey' request. + */ +abstract class Pubkey protected constructor(version: Long) : ObjectPayload(version) { + + override val type: ObjectType = ObjectType.PUBKEY + + abstract val signingKey: ByteArray + + abstract val encryptionKey: ByteArray + + abstract val behaviorBitfield: Int + + val ripe: ByteArray by lazy { cryptography().ripemd160(cryptography().sha512(signingKey, encryptionKey)) } + + open val nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE + + open val extraBytes: Long = NETWORK_EXTRA_BYTES + + open fun writeUnencrypted(out: OutputStream) { + write(out) + } + + open fun writeUnencrypted(buffer: ByteBuffer) { + write(buffer) + } + + /** + * Bits 0 through 29 are yet undefined + */ + enum class Feature constructor(bitNumber: Int) { + /** + * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg + * messages bound for them. + */ + INCLUDE_DESTINATION(30), + /** + * If true, the receiving node does send acknowledgements (rather than dropping them). + */ + DOES_ACK(31); + + // The Bitmessage Protocol Specification starts counting at the most significant bit, + // thus the slightly awkward calculation. + // https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features + private val bit: Int = 1 shl 31 - bitNumber + + fun isActive(bitfield: Int): Boolean { + return bitfield and bit != 0 + } + + companion object { + @JvmStatic fun bitfield(vararg features: Feature): Int { + var bits = 0 + for (feature in features) { + bits = bits or feature.bit + } + return bits + } + + @JvmStatic fun features(bitfield: Int): Array<Feature> { + val features = ArrayList<Feature>(Feature.values().size) + for (feature in Feature.values()) { + if (bitfield and feature.bit != 0) { + features.add(feature) + } + } + return features.toTypedArray() + } + } + } + + companion object { + @JvmField val LATEST_VERSION: Long = 4 + + fun getRipe(publicSigningKey: ByteArray, publicEncryptionKey: ByteArray): ByteArray { + return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)) + } + + fun add0x04(key: ByteArray): ByteArray { + if (key.size == 65) return key + val result = ByteArray(65) + result[0] = 4 + System.arraycopy(key, 0, result, 1, 64) + return result + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java deleted file mode 100644 index d2901c1..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * A version 2 public key. - */ -public class V2Pubkey extends Pubkey { - private static final long serialVersionUID = -257598690676510460L; - - protected long stream; - protected int behaviorBitfield; - protected byte[] publicSigningKey; // 64 Bytes - protected byte[] publicEncryptionKey; // 64 Bytes - - protected V2Pubkey(long version) { - super(version); - } - - private V2Pubkey(long version, Builder builder) { - super(version); - stream = builder.streamNumber; - behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = add0x04(builder.publicSigningKey); - publicEncryptionKey = add0x04(builder.publicEncryptionKey); - } - - public static V2Pubkey read(InputStream is, long stream) throws IOException { - return new V2Pubkey.Builder() - .stream(stream) - .behaviorBitfield((int) Decode.uint32(is)) - .publicSigningKey(Decode.bytes(is, 64)) - .publicEncryptionKey(Decode.bytes(is, 64)) - .build(); - } - - @Override - public long getVersion() { - return 2; - } - - @Override - public ObjectType getType() { - return ObjectType.PUBKEY; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public byte[] getSigningKey() { - return publicSigningKey; - } - - @Override - public byte[] getEncryptionKey() { - return publicEncryptionKey; - } - - @Override - public int getBehaviorBitfield() { - return behaviorBitfield; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.int32(behaviorBitfield, out); - out.write(publicSigningKey, 1, 64); - out.write(publicEncryptionKey, 1, 64); - } - - @Override - public void write(ByteBuffer buffer) { - Encode.int32(behaviorBitfield, buffer); - buffer.put(publicSigningKey, 1, 64); - buffer.put(publicEncryptionKey, 1, 64); - } - - public static class Builder { - private long streamNumber; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - - public Builder stream(long streamNumber) { - this.streamNumber = streamNumber; - return this; - } - - public Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - public Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - public Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - public V2Pubkey build() { - return new V2Pubkey(2, this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt new file mode 100644 index 0000000..f599153 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Encode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * A version 2 public key. + */ +open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) { + + override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey + override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey + + override fun write(out: OutputStream) { + Encode.int32(behaviorBitfield.toLong(), out) + out.write(signingKey, 1, 64) + out.write(encryptionKey, 1, 64) + } + + override fun write(buffer: ByteBuffer) { + Encode.int32(behaviorBitfield.toLong(), buffer) + buffer.put(signingKey, 1, 64) + buffer.put(encryptionKey, 1, 64) + } + + class Builder { + internal var streamNumber: Long = 0 + internal var behaviorBitfield: Int = 0 + internal var publicSigningKey: ByteArray? = null + internal var publicEncryptionKey: ByteArray? = null + + fun stream(streamNumber: Long): Builder { + this.streamNumber = streamNumber + return this + } + + fun behaviorBitfield(behaviorBitfield: Int): Builder { + this.behaviorBitfield = behaviorBitfield + return this + } + + fun publicSigningKey(publicSigningKey: ByteArray): Builder { + this.publicSigningKey = publicSigningKey + return this + } + + fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { + this.publicEncryptionKey = publicEncryptionKey + return this + } + + fun build(): V2Pubkey { + return V2Pubkey( + version = 2, + stream = streamNumber, + behaviorBitfield = behaviorBitfield, + signingKey = add0x04(publicSigningKey!!), + encryptionKey = add0x04(publicEncryptionKey!!) + ) + } + } + + companion object { + fun read(`in`: InputStream, stream: Long): V2Pubkey { + return V2Pubkey( + version = 2, + stream = stream, + behaviorBitfield = Decode.uint32(`in`).toInt(), + signingKey = Decode.bytes(`in`, 64), + encryptionKey = Decode.bytes(`in`, 64) + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java deleted file mode 100644 index 5260060..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Objects; - -/** - * A version 3 public key. - */ -public class V3Pubkey extends V2Pubkey { - private static final long serialVersionUID = 6958853116648528319L; - - long nonceTrialsPerByte; - long extraBytes; - byte[] signature; - - protected V3Pubkey(long version, Builder builder) { - super(version); - stream = builder.streamNumber; - behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = add0x04(builder.publicSigningKey); - publicEncryptionKey = add0x04(builder.publicEncryptionKey); - nonceTrialsPerByte = builder.nonceTrialsPerByte; - extraBytes = builder.extraBytes; - signature = builder.signature; - } - - public static V3Pubkey read(InputStream is, long stream) throws IOException { - return new V3Pubkey.Builder() - .stream(stream) - .behaviorBitfield(Decode.int32(is)) - .publicSigningKey(Decode.bytes(is, 64)) - .publicEncryptionKey(Decode.bytes(is, 64)) - .nonceTrialsPerByte(Decode.varInt(is)) - .extraBytes(Decode.varInt(is)) - .signature(Decode.varBytes(is)) - .build(); - } - - @Override - public void write(OutputStream out) throws IOException { - writeBytesToSign(out); - Encode.varBytes(signature, out); - } - - @Override - public void write(ByteBuffer buffer) { - super.write(buffer); - Encode.varInt(nonceTrialsPerByte, buffer); - Encode.varInt(extraBytes, buffer); - Encode.varBytes(signature, buffer); - } - - @Override - public long getVersion() { - return 3; - } - - public long getNonceTrialsPerByte() { - return nonceTrialsPerByte; - } - - public long getExtraBytes() { - return extraBytes; - } - - public boolean isSigned() { - return true; - } - - public void writeBytesToSign(OutputStream out) throws IOException { - super.write(out); - Encode.varInt(nonceTrialsPerByte, out); - Encode.varInt(extraBytes, out); - } - - @Override - public byte[] getSignature() { - return signature; - } - - @Override - public void setSignature(byte[] signature) { - this.signature = signature; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - V3Pubkey pubkey = (V3Pubkey) o; - return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) && - Objects.equals(extraBytes, pubkey.extraBytes) && - stream == pubkey.stream && - behaviorBitfield == pubkey.behaviorBitfield && - Arrays.equals(publicSigningKey, pubkey.publicSigningKey) && - Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey); - } - - @Override - public int hashCode() { - return Objects.hash(nonceTrialsPerByte, extraBytes); - } - - public static class Builder { - private long streamNumber; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - private long nonceTrialsPerByte; - private long extraBytes; - private byte[] signature = new byte[0]; - - public Builder stream(long streamNumber) { - this.streamNumber = streamNumber; - return this; - } - - public Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - public Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - public Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - public Builder nonceTrialsPerByte(long nonceTrialsPerByte) { - this.nonceTrialsPerByte = nonceTrialsPerByte; - return this; - } - - public Builder extraBytes(long extraBytes) { - this.extraBytes = extraBytes; - return this; - } - - public Builder signature(byte[] signature) { - this.signature = signature; - return this; - } - - public V3Pubkey build() { - return new V3Pubkey(3, this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt new file mode 100644 index 0000000..80dec2b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Encode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * A version 3 public key. + */ +class V3Pubkey protected constructor( + version: Long, stream: Long, behaviorBitfield: Int, + signingKey: ByteArray, encryptionKey: ByteArray, + override val nonceTrialsPerByte: Long, + override val extraBytes: Long, + override var signature: ByteArray? = null +) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) { + + override fun write(out: OutputStream) { + writeBytesToSign(out) + Encode.varBytes( + signature ?: throw IllegalStateException("signature not available"), + out + ) + } + + override fun write(buffer: ByteBuffer) { + super.write(buffer) + Encode.varInt(nonceTrialsPerByte, buffer) + Encode.varInt(extraBytes, buffer) + Encode.varBytes( + signature ?: throw IllegalStateException("signature not available"), + buffer + ) + } + + override val isSigned: Boolean = true + + override fun writeBytesToSign(out: OutputStream) { + super.write(out) + Encode.varInt(nonceTrialsPerByte, out) + Encode.varInt(extraBytes, out) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is V3Pubkey) return false + return nonceTrialsPerByte == other.nonceTrialsPerByte && + extraBytes == other.extraBytes && + stream == other.stream && + behaviorBitfield == other.behaviorBitfield && + Arrays.equals(signingKey, other.signingKey) && + Arrays.equals(encryptionKey, other.encryptionKey) + } + + override fun hashCode(): Int { + return Objects.hash(nonceTrialsPerByte, extraBytes) + } + + class Builder { + private var streamNumber: Long = 0 + private var behaviorBitfield: Int = 0 + private var publicSigningKey: ByteArray? = null + private var publicEncryptionKey: ByteArray? = null + private var nonceTrialsPerByte: Long = 0 + private var extraBytes: Long = 0 + private var signature = ByteArray(0) + + fun stream(streamNumber: Long): Builder { + this.streamNumber = streamNumber + return this + } + + fun behaviorBitfield(behaviorBitfield: Int): Builder { + this.behaviorBitfield = behaviorBitfield + return this + } + + fun publicSigningKey(publicSigningKey: ByteArray): Builder { + this.publicSigningKey = publicSigningKey + return this + } + + fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { + this.publicEncryptionKey = publicEncryptionKey + return this + } + + fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder { + this.nonceTrialsPerByte = nonceTrialsPerByte + return this + } + + fun extraBytes(extraBytes: Long): Builder { + this.extraBytes = extraBytes + return this + } + + fun signature(signature: ByteArray): Builder { + this.signature = signature + return this + } + + fun build(): V3Pubkey { + return V3Pubkey( + version = 3, + stream = streamNumber, + behaviorBitfield = behaviorBitfield, + signingKey = publicSigningKey!!, + encryptionKey = publicEncryptionKey!!, + nonceTrialsPerByte = nonceTrialsPerByte, + extraBytes = extraBytes, + signature = signature + ) + } + } + + companion object { + fun read(`is`: InputStream, stream: Long): V3Pubkey { + return V3Pubkey( + version = 3, + stream = stream, + behaviorBitfield = Decode.int32(`is`), + signingKey = Decode.bytes(`is`, 64), + encryptionKey = Decode.bytes(`is`, 64), + nonceTrialsPerByte = Decode.varInt(`is`), + extraBytes = Decode.varInt(`is`), + signature = Decode.varBytes(`is`) + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java deleted file mode 100644 index 323da33..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Users who are subscribed to the sending address will see the message appear in their inbox. - * Broadcasts are version 4 or 5. - */ -public class V4Broadcast extends Broadcast { - private static final long serialVersionUID = 195663108282762711L; - - protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { - super(version, stream, encrypted, plaintext); - } - - public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { - super(4, senderAddress.getStream(), null, plaintext); - if (senderAddress.getVersion() >= 4) - throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion()); - } - - public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { - return new V4Broadcast(4, stream, CryptoBox.read(in, length), null); - } - - @Override - public ObjectType getType() { - return ObjectType.BROADCAST; - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - plaintext.write(out, false); - } - - @Override - public void write(OutputStream out) throws IOException { - encrypted.write(out); - } - - @Override - public void write(ByteBuffer buffer) { - encrypted.write(buffer); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt new file mode 100644 index 0000000..b5ef096 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext + +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Users who are subscribed to the sending address will see the message appear in their inbox. + * Broadcasts are version 4 or 5. + */ +open class V4Broadcast : Broadcast { + + override val type: ObjectType = ObjectType.BROADCAST + + protected constructor(version: Long, stream: Long, encrypted: CryptoBox?, plaintext: Plaintext?) : super(version, stream, encrypted, plaintext) + + constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(4, senderAddress.stream, null, plaintext) { + if (senderAddress.version >= 4) + throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version) + } + + + override fun writeBytesToSign(out: OutputStream) { + plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") + } + + override fun write(out: OutputStream) { + encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted") + } + + override fun write(buffer: ByteBuffer) { + encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted") + } + + companion object { + fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { + return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java deleted file mode 100644 index 179a475..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is - * done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and - * use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them - * to create messages to be used in spam or in flooding attacks. - */ -public class V4Pubkey extends Pubkey implements Encrypted { - private static final long serialVersionUID = 1556710353694033093L; - - private long stream; - private byte[] tag; - private CryptoBox encrypted; - private V3Pubkey decrypted; - - private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) { - super(4); - this.stream = stream; - this.tag = tag; - this.encrypted = encrypted; - } - - public V4Pubkey(V3Pubkey decrypted) { - super(4); - this.decrypted = decrypted; - this.stream = decrypted.stream; - this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe()); - } - - public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException { - if (encrypted) - return new V4Pubkey(stream, - Decode.bytes(in, 32), - CryptoBox.read(in, length - 32)); - else - return new V4Pubkey(V3Pubkey.read(in, stream)); - } - - @Override - public void encrypt(byte[] publicKey) throws IOException { - if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption."); - this.encrypted = new CryptoBox(decrypted, publicKey); - } - - @Override - public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream); - } - - @Override - public boolean isDecrypted() { - return decrypted != null; - } - - @Override - public void write(OutputStream stream) throws IOException { - stream.write(tag); - encrypted.write(stream); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(tag); - encrypted.write(buffer); - } - - @Override - public void writeUnencrypted(OutputStream out) throws IOException { - decrypted.write(out); - } - - @Override - public void writeUnencrypted(ByteBuffer buffer) { - decrypted.write(buffer); - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - out.write(tag); - decrypted.writeBytesToSign(out); - } - - @Override - public long getVersion() { - return 4; - } - - @Override - public ObjectType getType() { - return ObjectType.PUBKEY; - } - - @Override - public long getStream() { - return stream; - } - - public byte[] getTag() { - return tag; - } - - @Override - public byte[] getSigningKey() { - return decrypted.getSigningKey(); - } - - @Override - public byte[] getEncryptionKey() { - return decrypted.getEncryptionKey(); - } - - @Override - public int getBehaviorBitfield() { - return decrypted.getBehaviorBitfield(); - } - - @Override - public byte[] getSignature() { - if (decrypted != null) - return decrypted.getSignature(); - else - return null; - } - - @Override - public void setSignature(byte[] signature) { - decrypted.setSignature(signature); - } - - @Override - public boolean isSigned() { - return true; - } - - public long getNonceTrialsPerByte() { - return decrypted.getNonceTrialsPerByte(); - } - - public long getExtraBytes() { - return decrypted.getExtraBytes(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - V4Pubkey v4Pubkey = (V4Pubkey) o; - - if (stream != v4Pubkey.stream) return false; - if (!Arrays.equals(tag, v4Pubkey.tag)) return false; - return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null); - - } - - @Override - public int hashCode() { - int result = (int) (stream ^ (stream >>> 32)); - result = 31 * result + Arrays.hashCode(tag); - result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt new file mode 100644 index 0000000..d755864 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.Decode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is + * done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and + * use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them + * to create messages to be used in spam or in flooding attacks. + */ +class V4Pubkey : Pubkey, Encrypted { + + override val stream: Long + val tag: ByteArray + private var encrypted: CryptoBox? = null + private var decrypted: V3Pubkey? = null + + private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(4) { + this.stream = stream + this.tag = tag + this.encrypted = encrypted + } + + constructor(decrypted: V3Pubkey) : super(4) { + this.stream = decrypted.stream + this.decrypted = decrypted + this.tag = BitmessageAddress.calculateTag(4, decrypted.stream, decrypted.ripe) + } + + override fun encrypt(publicKey: ByteArray) { + if (signature == null) throw IllegalStateException("Pubkey must be signed before encryption.") + this.encrypted = CryptoBox(decrypted ?: throw IllegalStateException("no plaintext pubkey data available"), publicKey) + } + + @Throws(DecryptionFailedException::class) + override fun decrypt(privateKey: ByteArray) { + decrypted = V3Pubkey.read(encrypted?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"), stream) + } + + override val isDecrypted: Boolean + get() = decrypted != null + + override fun write(out: OutputStream) { + out.write(tag) + encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun write(buffer: ByteBuffer) { + buffer.put(tag) + encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeUnencrypted(out: OutputStream) { + decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeUnencrypted(buffer: ByteBuffer) { + decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeBytesToSign(out: OutputStream) { + out.write(tag) + decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override val signingKey: ByteArray + get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted") + + override val encryptionKey: ByteArray + get() = decrypted?.encryptionKey ?: throw IllegalStateException("pubkey is encrypted") + + override val behaviorBitfield: Int + get() = decrypted?.behaviorBitfield ?: throw IllegalStateException("pubkey is encrypted") + + override var signature: ByteArray? + get() = decrypted?.signature + set(signature) { + decrypted?.signature = signature + } + + override val isSigned: Boolean = true + + override val nonceTrialsPerByte: Long + get() = decrypted?.nonceTrialsPerByte ?: throw IllegalStateException("pubkey is encrypted") + + override val extraBytes: Long + get() = decrypted?.extraBytes ?: throw IllegalStateException("pubkey is encrypted") + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is V4Pubkey) return false + + if (stream != other.stream) return false + if (!Arrays.equals(tag, other.tag)) return false + return !if (decrypted != null) decrypted != other.decrypted else other.decrypted != null + + } + + override fun hashCode(): Int { + var result = (stream xor stream.ushr(32)).toInt() + result = 31 * result + Arrays.hashCode(tag) + result = 31 * result + if (decrypted != null) decrypted!!.hashCode() else 0 + return result + } + + companion object { + fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { + if (encrypted) + return V4Pubkey(stream, + Decode.bytes(`in`, 32), + CryptoBox.read(`in`, length - 32)) + else + return V4Pubkey(V3Pubkey.read(`in`, stream)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java deleted file mode 100644 index 8f07a30..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015 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.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Users who are subscribed to the sending address will see the message appear in their inbox. - */ -public class V5Broadcast extends V4Broadcast { - private static final long serialVersionUID = 920649721626968644L; - - private byte[] tag; - - private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { - super(5, stream, encrypted, null); - this.tag = tag; - } - - public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { - super(5, senderAddress.getStream(), null, plaintext); - if (senderAddress.getVersion() < 4) - throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion()); - this.tag = senderAddress.getTag(); - } - - public static V5Broadcast read(InputStream is, long stream, int length) throws IOException { - return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32)); - } - - public byte[] getTag() { - return tag; - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - out.write(tag); - super.writeBytesToSign(out); - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(tag); - super.write(out); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt new file mode 100644 index 0000000..4ab9af9 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2017 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.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.utils.Decode + +import java.io.InputStream +import java.io.OutputStream + +/** + * Users who are subscribed to the sending address will see the message appear in their inbox. + */ +class V5Broadcast : V4Broadcast { + + val tag: ByteArray + + private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(5, stream, encrypted, null) { + this.tag = tag + } + + constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(5, senderAddress.stream, null, plaintext) { + if (senderAddress.version < 4) + throw IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.version) + this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag") + } + + override fun writeBytesToSign(out: OutputStream) { + out.write(tag) + super.writeBytesToSign(out) + } + + override fun write(out: OutputStream) { + out.write(tag) + super.write(out) + } + + companion object { + fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { + return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java deleted file mode 100644 index 3f3d1ec..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java +++ /dev/null @@ -1,76 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.msgpack.types.MPMap; -import ch.dissem.msgpack.types.MPString; -import ch.dissem.msgpack.types.MPType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.Objects; -import java.util.zip.DeflaterOutputStream; - -/** - * Extended encoding message object. - */ -public class ExtendedEncoding implements Serializable { - private static final long serialVersionUID = 3876871488247305200L; - private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncoding.class); - - private ExtendedType content; - - public ExtendedEncoding(ExtendedType content) { - this.content = content; - } - - public String getType() { - if (content == null) { - return null; - } else { - return content.getType(); - } - } - - public ExtendedType getContent() { - return content; - } - - public byte[] zip() { - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - try (DeflaterOutputStream zipper = new DeflaterOutputStream(out)) { - content.pack().pack(zipper); - } - return out.toByteArray(); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ExtendedEncoding that = (ExtendedEncoding) o; - return Objects.equals(content, that.content); - } - - @Override - public int hashCode() { - return Objects.hash(content); - } - - public interface Unpacker<T extends ExtendedType> { - String getType(); - - T unpack(MPMap<MPString, MPType<?>> map); - } - - public interface ExtendedType extends Serializable { - String getType(); - - MPMap<MPString, MPType<?>> pack() throws IOException; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt new file mode 100644 index 0000000..87d85b3 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.entity.valueobject + +import ch.dissem.msgpack.types.MPMap +import ch.dissem.msgpack.types.MPString +import ch.dissem.msgpack.types.MPType +import java.io.ByteArrayOutputStream +import java.io.Serializable +import java.util.zip.DeflaterOutputStream + +/** + * Extended encoding message object. + */ +data class ExtendedEncoding(val content: ExtendedEncoding.ExtendedType) : Serializable { + + val type: String? = content.type + + fun zip(): ByteArray { + ByteArrayOutputStream().use { out -> + DeflaterOutputStream(out).use { zipper -> content.pack().pack(zipper) } + return out.toByteArray() + } + } + + interface Unpacker<out T : ExtendedType> { + val type: String + + fun unpack(map: MPMap<MPString, MPType<*>>): T + } + + interface ExtendedType : Serializable { + val type: String + + fun pack(): MPMap<MPString, MPType<*>> + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java deleted file mode 100644 index e1f8f60..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 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.entity.valueobject; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.utils.Strings; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.util.Arrays; - -public class InventoryVector implements Streamable, Serializable { - private static final long serialVersionUID = -7349009673063348719L; - - /** - * Hash of the object - */ - private final byte[] hash; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof InventoryVector)) return false; - - InventoryVector that = (InventoryVector) o; - - return Arrays.equals(hash, that.hash); - } - - @Override - public int hashCode() { - return hash == null ? 0 : Arrays.hashCode(hash); - } - - public byte[] getHash() { - return hash; - } - - private InventoryVector(byte[] hash) { - if (hash == null) throw new IllegalArgumentException("hash must not be null"); - this.hash = hash; - } - - public static InventoryVector fromHash(byte[] hash) { - if (hash == null) { - return null; - } else { - return new InventoryVector(hash); - } - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(hash); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(hash); - } - - @Override - public String toString() { - return Strings.hex(hash).toString(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt new file mode 100644 index 0000000..e46ea72 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2017 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.entity.valueobject + +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.utils.Strings +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +data class InventoryVector constructor( + /** + * Hash of the object + */ + val hash: ByteArray) : Streamable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is InventoryVector) return false + + return Arrays.equals(hash, other.hash) + } + + override fun hashCode(): Int { + return Arrays.hashCode(hash) + } + + override fun write(out: OutputStream) { + out.write(hash) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(hash) + } + + override fun toString(): String { + return Strings.hex(hash).toString() + } + + companion object { + @JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? { + return InventoryVector( + hash ?: return null + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java deleted file mode 100644 index facbe55..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015 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.entity.valueobject; - -import java.io.Serializable; -import java.util.Objects; - -public class Label implements Serializable { - private static final long serialVersionUID = 831782893630994914L; - - private Object id; - private String label; - private Type type; - private int color; - - public Label(String label, Type type, int color) { - this.label = label; - this.type = type; - this.color = color; - } - - /** - * @return RGBA representation for the color. - */ - public int getColor() { - return color; - } - - /** - * @param color RGBA representation for the color. - */ - public void setColor(int color) { - this.color = color; - } - - @Override - public String toString() { - return label; - } - - public Object getId() { - return id; - } - - public void setId(Object id) { - this.id = id; - } - - public Type getType() { - return type; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Label label1 = (Label) o; - return Objects.equals(label, label1.label); - } - - @Override - public int hashCode() { - return Objects.hash(label); - } - - public enum Type { - INBOX, - BROADCAST, - DRAFT, - OUTBOX, - SENT, - UNREAD, - TRASH - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt new file mode 100644 index 0000000..378aa35 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2017 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.entity.valueobject + +import java.io.Serializable +import java.util.* + +data class Label(private val label: String, val type: Label.Type, + /** + * RGBA representation for the color. + */ + var color: Int) : Serializable { + + var id: Any? = null + + override fun toString(): String { + return label + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Label) return false + return label == other.label + } + + override fun hashCode(): Int { + return Objects.hash(label) + } + + enum class Type { + INBOX, + BROADCAST, + DRAFT, + OUTBOX, + SENT, + UNREAD, + TRASH + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java deleted file mode 100644 index fe3bfcb..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015 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.entity.valueobject; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.Version; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Encode; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * A node's address. It's written in IPv6 format. - */ -public class NetworkAddress implements Streamable { - private static final long serialVersionUID = 2500120578167100300L; - - private long time; - - /** - * Stream number for this node - */ - private final long stream; - - /** - * same service(s) listed in version - */ - private final long services; - - /** - * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address - * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). - */ - private final byte[] ipv6; - private final int port; - - private NetworkAddress(Builder builder) { - time = builder.time; - stream = builder.stream; - services = builder.services; - ipv6 = builder.ipv6; - port = builder.port; - } - - public byte[] getIPv6() { - return ipv6; - } - - public int getPort() { - return port; - } - - public long getServices() { - return services; - } - - public boolean provides(Version.Service service) { - if (service == null) { - return false; - } - return service.isEnabled(services); - } - - public long getStream() { - return stream; - } - - public long getTime() { - return time; - } - - public void setTime(long time) { - this.time = time; - } - - public InetAddress toInetAddress() { - try { - return InetAddress.getByAddress(ipv6); - } catch (UnknownHostException e) { - throw new ApplicationException(e); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - NetworkAddress that = (NetworkAddress) o; - - return port == that.port && Arrays.equals(ipv6, that.ipv6); - } - - @Override - public int hashCode() { - int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0; - result = 31 * result + port; - return result; - } - - @Override - public String toString() { - return "[" + toInetAddress() + "]:" + port; - } - - @Override - public void write(OutputStream stream) throws IOException { - write(stream, false); - } - - public void write(OutputStream out, boolean light) throws IOException { - if (!light) { - Encode.int64(time, out); - Encode.int32(stream, out); - } - Encode.int64(services, out); - out.write(ipv6); - Encode.int16(port, out); - } - - @Override - public void write(ByteBuffer buffer) { - write(buffer, false); - } - - public void write(ByteBuffer buffer, boolean light) { - if (!light) { - Encode.int64(time, buffer); - Encode.int32(stream, buffer); - } - Encode.int64(services, buffer); - buffer.put(ipv6); - Encode.int16(port, buffer); - } - - public static final class Builder { - private long time; - private long stream; - private long services = 1; - private byte[] ipv6; - private int port; - - public Builder time(final long time) { - this.time = time; - return this; - } - - public Builder stream(final long stream) { - this.stream = stream; - return this; - } - - public Builder services(final long services) { - this.services = services; - return this; - } - - public Builder ip(InetAddress inetAddress) { - byte[] addr = inetAddress.getAddress(); - if (addr.length == 16) { - this.ipv6 = addr; - } else if (addr.length == 4) { - this.ipv6 = new byte[16]; - this.ipv6[10] = (byte) 0xff; - this.ipv6[11] = (byte) 0xff; - System.arraycopy(addr, 0, this.ipv6, 12, 4); - } else { - throw new IllegalArgumentException("Weird address " + inetAddress); - } - return this; - } - - public Builder ipv6(byte[] ipv6) { - this.ipv6 = ipv6; - return this; - } - - public Builder ipv6(int p00, int p01, int p02, int p03, - int p04, int p05, int p06, int p07, - int p08, int p09, int p10, int p11, - int p12, int p13, int p14, int p15) { - this.ipv6 = new byte[]{ - (byte) p00, (byte) p01, (byte) p02, (byte) p03, - (byte) p04, (byte) p05, (byte) p06, (byte) p07, - (byte) p08, (byte) p09, (byte) p10, (byte) p11, - (byte) p12, (byte) p13, (byte) p14, (byte) p15 - }; - return this; - } - - public Builder ipv4(int p00, int p01, int p02, int p03) { - this.ipv6 = new byte[]{ - (byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00, - (byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00, - (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff, - (byte) p00, (byte) p01, (byte) p02, (byte) p03 - }; - return this; - } - - public Builder port(final int port) { - this.port = port; - return this; - } - - public Builder address(SocketAddress address) { - if (address instanceof InetSocketAddress) { - InetSocketAddress inetAddress = (InetSocketAddress) address; - ip(inetAddress.getAddress()); - port(inetAddress.getPort()); - } else { - throw new IllegalArgumentException("Unknown type of address: " + address.getClass()); - } - return this; - } - - public NetworkAddress build() { - if (time == 0) { - time = UnixTime.now(); - } - return new NetworkAddress(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt new file mode 100644 index 0000000..e332a04 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2017 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.entity.valueobject + +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.Version +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.UnixTime +import java.io.OutputStream +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.SocketAddress +import java.nio.ByteBuffer +import java.util.* + +/** + * A node's address. It's written in IPv6 format. + */ +data class NetworkAddress constructor( + var time: Long, + + /** + * Stream number for this node + */ + val stream: Long, + + /** + * same service(s) listed in version + */ + val services: Long, + + /** + * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address + * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). + */ + val iPv6: ByteArray, + val port: Int +) : Streamable { + + fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false + + fun toInetAddress(): InetAddress { + return InetAddress.getByAddress(iPv6) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is NetworkAddress) return false + + return port == other.port && Arrays.equals(iPv6, other.iPv6) + } + + override fun hashCode(): Int { + var result = Arrays.hashCode(iPv6) + result = 31 * result + port + return result + } + + override fun toString(): String { + return "[" + toInetAddress() + "]:" + port + } + + override fun write(out: OutputStream) { + write(out, false) + } + + fun write(out: OutputStream, light: Boolean) { + if (!light) { + Encode.int64(time, out) + Encode.int32(stream, out) + } + Encode.int64(services, out) + out.write(iPv6) + Encode.int16(port.toLong(), out) + } + + override fun write(buffer: ByteBuffer) { + write(buffer, false) + } + + fun write(buffer: ByteBuffer, light: Boolean) { + if (!light) { + Encode.int64(time, buffer) + Encode.int32(stream, buffer) + } + Encode.int64(services, buffer) + buffer.put(iPv6) + Encode.int16(port.toLong(), buffer) + } + + class Builder { + internal var time: Long? = null + internal var stream: Long = 0 + internal var services: Long = 1 + internal var ipv6: ByteArray? = null + internal var port: Int = 0 + + fun time(time: Long): Builder { + this.time = time + return this + } + + fun stream(stream: Long): Builder { + this.stream = stream + return this + } + + fun services(services: Long): Builder { + this.services = services + return this + } + + fun ip(inetAddress: InetAddress): Builder { + val addr = inetAddress.address + if (addr.size == 16) { + this.ipv6 = addr + } else if (addr.size == 4) { + val ipv6 = ByteArray(16) + ipv6[10] = 0xff.toByte() + ipv6[11] = 0xff.toByte() + System.arraycopy(addr, 0, ipv6, 12, 4) + this.ipv6 = ipv6 + } else { + throw IllegalArgumentException("Weird address " + inetAddress) + } + return this + } + + fun ipv6(ipv6: ByteArray): Builder { + this.ipv6 = ipv6 + return this + } + + fun ipv6(p00: Int, p01: Int, p02: Int, p03: Int, + p04: Int, p05: Int, p06: Int, p07: Int, + p08: Int, p09: Int, p10: Int, p11: Int, + p12: Int, p13: Int, p14: Int, p15: Int): Builder { + this.ipv6 = byteArrayOf(p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte(), p04.toByte(), p05.toByte(), p06.toByte(), p07.toByte(), p08.toByte(), p09.toByte(), p10.toByte(), p11.toByte(), p12.toByte(), p13.toByte(), p14.toByte(), p15.toByte()) + return this + } + + fun ipv4(p00: Int, p01: Int, p02: Int, p03: Int): Builder { + this.ipv6 = byteArrayOf(0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0xff.toByte(), 0xff.toByte(), p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte()) + return this + } + + fun port(port: Int): Builder { + this.port = port + return this + } + + fun address(address: SocketAddress): Builder { + if (address is InetSocketAddress) { + val inetAddress = address + ip(inetAddress.address) + port(inetAddress.port) + } else { + throw IllegalArgumentException("Unknown type of address: " + address.javaClass) + } + return this + } + + fun build(): NetworkAddress { + return NetworkAddress( + time ?: UnixTime.now, stream, services, ipv6!!, port + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java deleted file mode 100644 index 7621ca5..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2015 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.entity.valueobject; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying - * {@link Pubkey} object. - */ -public class PrivateKey implements Streamable { - private static final long serialVersionUID = 8562555470709110558L; - - public static final int PRIVATE_KEY_SIZE = 32; - - private final byte[] privateSigningKey; - private final byte[] privateEncryptionKey; - - private final Pubkey pubkey; - - public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { - byte[] privSK; - byte[] pubSK; - byte[] privEK; - byte[] pubEK; - byte[] ripe; - do { - privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE); - privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE); - pubSK = cryptography().createPublicKey(privSK); - pubEK = cryptography().createPublicKey(privEK); - ripe = Pubkey.getRipe(pubSK, pubEK); - } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); - this.privateSigningKey = privSK; - this.privateEncryptionKey = privEK; - this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, - nonceTrialsPerByte, extraBytes, features); - } - - public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) { - this.privateSigningKey = privateSigningKey; - this.privateEncryptionKey = privateEncryptionKey; - this.pubkey = pubkey; - } - - public PrivateKey(BitmessageAddress address, String passphrase) { - this(address.getVersion(), address.getStream(), passphrase); - } - - public PrivateKey(long version, long stream, String passphrase) { - this(new Builder(version, stream, false).seed(passphrase).generate()); - } - - private PrivateKey(Builder builder) { - this.privateSigningKey = builder.privSK; - this.privateEncryptionKey = builder.privEK; - this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK, builder.pubEK, - InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES); - } - - private static class Builder { - final long version; - final long stream; - final boolean shorter; - - byte[] seed; - long nextNonce; - - byte[] privSK, privEK; - byte[] pubSK, pubEK; - - private Builder(long version, long stream, boolean shorter) { - this.version = version; - this.stream = stream; - this.shorter = shorter; - } - - Builder seed(String passphrase) { - try { - seed = passphrase.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - return this; - } - - Builder generate() { - long signingKeyNonce = nextNonce; - long encryptionKeyNonce = nextNonce + 1; - byte[] ripe; - do { - privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32); - privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32); - pubSK = cryptography().createPublicKey(privSK); - pubEK = cryptography().createPublicKey(privEK); - ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK)); - - signingKeyNonce += 2; - encryptionKeyNonce += 2; - } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); - nextNonce = signingKeyNonce; - return this; - } - } - - public static List<PrivateKey> deterministic(String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { - List<PrivateKey> result = new ArrayList<>(numberOfAddresses); - Builder builder = new Builder(version, stream, shorter).seed(passphrase); - for (int i = 0; i < numberOfAddresses; i++) { - builder.generate(); - result.add(new PrivateKey(builder)); - } - return result; - } - - public static PrivateKey read(InputStream is) throws IOException { - int version = (int) Decode.varInt(is); - long stream = Decode.varInt(is); - int len = (int) Decode.varInt(is); - Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false); - len = (int) Decode.varInt(is); - byte[] signingKey = Decode.bytes(is, len); - len = (int) Decode.varInt(is); - byte[] encryptionKey = Decode.bytes(is, len); - return new PrivateKey(signingKey, encryptionKey, pubkey); - } - - public byte[] getPrivateSigningKey() { - return privateSigningKey; - } - - public byte[] getPrivateEncryptionKey() { - return privateEncryptionKey; - } - - public Pubkey getPubkey() { - return pubkey; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(pubkey.getVersion(), out); - Encode.varInt(pubkey.getStream(), out); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - pubkey.writeUnencrypted(baos); - Encode.varInt(baos.size(), out); - out.write(baos.toByteArray()); - Encode.varInt(privateSigningKey.length, out); - out.write(privateSigningKey); - Encode.varInt(privateEncryptionKey.length, out); - out.write(privateEncryptionKey); - } - - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(pubkey.getVersion(), buffer); - Encode.varInt(pubkey.getStream(), buffer); - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - pubkey.writeUnencrypted(baos); - Encode.varBytes(baos.toByteArray(), buffer); - } catch (IOException e) { - throw new ApplicationException(e); - } - Encode.varBytes(privateSigningKey, buffer); - Encode.varBytes(privateEncryptionKey, buffer); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt new file mode 100644 index 0000000..1cfabda --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2017 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.entity.valueobject + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.* +import java.nio.ByteBuffer +import java.util.* + +/** + * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying + * [Pubkey] object. + */ +class PrivateKey : Streamable { + + val privateSigningKey: ByteArray + val privateEncryptionKey: ByteArray + + val pubkey: Pubkey + + constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) { + var privSK: ByteArray + var pubSK: ByteArray + var privEK: ByteArray + var pubEK: ByteArray + var ripe: ByteArray + do { + privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE) + privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE) + pubSK = cryptography().createPublicKey(privSK) + pubEK = cryptography().createPublicKey(privEK) + ripe = Pubkey.getRipe(pubSK, pubEK) + } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0) + this.privateSigningKey = privSK + this.privateEncryptionKey = privEK + this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, + nonceTrialsPerByte, extraBytes, *features) + } + + constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) { + this.privateSigningKey = privateSigningKey + this.privateEncryptionKey = privateEncryptionKey + this.pubkey = pubkey + } + + constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase) + + constructor(version: Long, stream: Long, passphrase: String) : this(Builder(version, stream, false).seed(passphrase).generate()) + + private constructor(builder: Builder) { + this.privateSigningKey = builder.privSK!! + this.privateEncryptionKey = builder.privEK!! + this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!, + InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES) + } + + private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) { + + internal var seed: ByteArray? = null + internal var nextNonce: Long = 0 + + internal var privSK: ByteArray? = null + internal var privEK: ByteArray? = null + internal var pubSK: ByteArray? = null + internal var pubEK: ByteArray? = null + + internal fun seed(passphrase: String): Builder { + try { + seed = passphrase.toByteArray(charset("UTF-8")) + } catch (e: UnsupportedEncodingException) { + throw ApplicationException(e) + } + + return this + } + + internal fun generate(): Builder { + var signingKeyNonce = nextNonce + var encryptionKeyNonce = nextNonce + 1 + var ripe: ByteArray + do { + privEK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(encryptionKeyNonce)), 32) + privSK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(signingKeyNonce)), 32) + pubSK = cryptography().createPublicKey(privSK!!) + pubEK = cryptography().createPublicKey(privEK!!) + ripe = cryptography().ripemd160(cryptography().sha512(pubSK!!, pubEK!!)) + + signingKeyNonce += 2 + encryptionKeyNonce += 2 + } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0) + nextNonce = signingKeyNonce + return this + } + } + + override fun write(out: OutputStream) { + Encode.varInt(pubkey.version, out) + Encode.varInt(pubkey.stream, out) + val baos = ByteArrayOutputStream() + pubkey.writeUnencrypted(baos) + Encode.varInt(baos.size().toLong(), out) + out.write(baos.toByteArray()) + Encode.varInt(privateSigningKey.size.toLong(), out) + out.write(privateSigningKey) + Encode.varInt(privateEncryptionKey.size.toLong(), out) + out.write(privateEncryptionKey) + } + + + override fun write(buffer: ByteBuffer) { + Encode.varInt(pubkey.version, buffer) + Encode.varInt(pubkey.stream, buffer) + try { + val baos = ByteArrayOutputStream() + pubkey.writeUnencrypted(baos) + Encode.varBytes(baos.toByteArray(), buffer) + } catch (e: IOException) { + throw ApplicationException(e) + } + + Encode.varBytes(privateSigningKey, buffer) + Encode.varBytes(privateEncryptionKey, buffer) + } + + companion object { + @JvmField val PRIVATE_KEY_SIZE = 32 + + @JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> { + val result = ArrayList<PrivateKey>(numberOfAddresses) + val builder = Builder(version, stream, shorter).seed(passphrase) + for (i in 0..numberOfAddresses - 1) { + builder.generate() + result.add(PrivateKey(builder)) + } + return result + } + + @JvmStatic fun read(`is`: InputStream): PrivateKey { + val version = Decode.varInt(`is`).toInt() + val stream = Decode.varInt(`is`) + val len = Decode.varInt(`is`).toInt() + val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered") + val signingKey = Decode.varBytes(`is`) + val encryptionKey = Decode.varBytes(`is`) + return PrivateKey(signingKey, encryptionKey, pubkey) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java deleted file mode 100644 index 42afc4f..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java +++ /dev/null @@ -1,100 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject.extended; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Objects; - -/** - * A "file" attachment as used by extended encoding type messages. Could either be an attachment, - * or used inline to be used by a HTML message, for example. - */ -public class Attachment implements Serializable { - private static final long serialVersionUID = 7319139427666943189L; - - private String name; - private byte[] data; - private String type; - private Disposition disposition; - - public String getName() { - return name; - } - - public byte[] getData() { - return data; - } - - public String getType() { - return type; - } - - public Disposition getDisposition() { - return disposition; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Attachment that = (Attachment) o; - return Objects.equals(name, that.name) && - Arrays.equals(data, that.data) && - Objects.equals(type, that.type) && - disposition == that.disposition; - } - - @Override - public int hashCode() { - return Objects.hash(name, data, type, disposition); - } - - public enum Disposition { - inline, attachment - } - - public static final class Builder { - private String name; - private byte[] data; - private String type; - private Disposition disposition; - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder data(byte[] data) { - this.data = data; - return this; - } - - public Builder type(String type) { - this.type = type; - return this; - } - - public Builder inline() { - this.disposition = Disposition.inline; - return this; - } - - public Builder attachment() { - this.disposition = Disposition.attachment; - return this; - } - - public Builder disposition(Disposition disposition) { - this.disposition = disposition; - return this; - } - - public Attachment build() { - Attachment attachment = new Attachment(); - attachment.type = this.type; - attachment.disposition = this.disposition; - attachment.data = this.data; - attachment.name = this.name; - return attachment; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt new file mode 100644 index 0000000..c410ba6 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2017 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.entity.valueobject.extended + +import java.io.Serializable +import java.util.* + +/** + * A "file" attachment as used by extended encoding type messages. Could either be an attachment, + * or used inline to be used by a HTML message, for example. + */ +data class Attachment constructor( + val name: String, + val data: ByteArray, + val type: String, + val disposition: Disposition +) : Serializable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Attachment) return false + return name == other.name && + Arrays.equals(data, other.data) && + type == other.type && + disposition == other.disposition + } + + override fun hashCode(): Int { + return Objects.hash(name, data, type, disposition) + } + + enum class Disposition { + inline, attachment + } + + class Builder { + private var name: String? = null + private var data: ByteArray? = null + private var type: String? = null + private var disposition: Disposition? = null + + fun name(name: String): Builder { + this.name = name + return this + } + + fun data(data: ByteArray): Builder { + this.data = data + return this + } + + fun type(type: String): Builder { + this.type = type + return this + } + + fun inline(): Builder { + this.disposition = Disposition.inline + return this + } + + fun attachment(): Builder { + this.disposition = Disposition.attachment + return this + } + + fun disposition(disposition: Disposition): Builder { + this.disposition = disposition + return this + } + + fun build(): Attachment { + return Attachment(name!!, data!!, type!!, disposition!!) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java deleted file mode 100644 index aea4368..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java +++ /dev/null @@ -1,219 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject.extended; - -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.msgpack.types.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.URLConnection; -import java.nio.file.Files; -import java.util.*; - -import static ch.dissem.bitmessage.entity.valueobject.extended.Attachment.Disposition.attachment; -import static ch.dissem.bitmessage.utils.Strings.str; -import static ch.dissem.msgpack.types.Utils.mp; - -/** - * Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work - * properly with future PyBitmessage implementations. - */ -public class Message implements ExtendedEncoding.ExtendedType { - private static final long serialVersionUID = -2724977231484285467L; - private static final Logger LOG = LoggerFactory.getLogger(Message.class); - - public static final String TYPE = "message"; - - private String subject; - private String body; - private List<InventoryVector> parents; - private List<Attachment> files; - - private Message(Builder builder) { - subject = builder.subject; - body = builder.body; - parents = Collections.unmodifiableList(builder.parents); - files = Collections.unmodifiableList(builder.files); - } - - @Override - public String getType() { - return TYPE; - } - - public String getSubject() { - return subject; - } - - public String getBody() { - return body; - } - - public List<InventoryVector> getParents() { - return parents; - } - - public List<Attachment> getFiles() { - return files; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Message message = (Message) o; - return Objects.equals(subject, message.subject) && - Objects.equals(body, message.body) && - Objects.equals(parents, message.parents) && - Objects.equals(files, message.files); - } - - @Override - public int hashCode() { - return Objects.hash(subject, body, parents, files); - } - - @Override - public MPMap<MPString, MPType<?>> pack() throws IOException { - MPMap<MPString, MPType<?>> result = new MPMap<>(); - result.put(mp(""), mp(TYPE)); - result.put(mp("subject"), mp(subject)); - result.put(mp("body"), mp(body)); - - if (!files.isEmpty()) { - MPArray<MPMap<MPString, MPType<?>>> items = new MPArray<>(); - result.put(mp("files"), items); - for (Attachment file : files) { - MPMap<MPString, MPType<?>> item = new MPMap<>(); - item.put(mp("name"), mp(file.getName())); - item.put(mp("data"), mp(file.getData())); - item.put(mp("type"), mp(file.getType())); - item.put(mp("disposition"), mp(file.getDisposition().name())); - items.add(item); - } - } - if (!parents.isEmpty()) { - MPArray<MPBinary> items = new MPArray<>(); - result.put(mp("parents"), items); - for (InventoryVector parent : parents) { - items.add(mp(parent.getHash())); - } - } - return result; - } - - public static class Builder { - private String subject; - private String body; - private List<InventoryVector> parents = new LinkedList<>(); - private List<Attachment> files = new LinkedList<>(); - - public Builder subject(String subject) { - this.subject = subject; - return this; - } - - public Builder body(String body) { - this.body = body; - return this; - } - - public Builder addParent(Plaintext parent) { - if (parent != null) { - InventoryVector iv = parent.getInventoryVector(); - if (iv == null) { - LOG.debug("Ignored parent without IV"); - } else { - parents.add(iv); - } - } - return this; - } - - public Builder addParent(InventoryVector iv) { - if (iv != null) { - parents.add(iv); - } - return this; - } - - public Builder addFile(File file, Attachment.Disposition disposition) { - if (file != null) { - try { - files.add(new Attachment.Builder() - .name(file.getName()) - .disposition(disposition) - .type(URLConnection.guessContentTypeFromStream(new FileInputStream(file))) - .data(Files.readAllBytes(file.toPath())) - .build()); - } catch (IOException e) { - LOG.error(e.getMessage(), e); - } - } - return this; - } - - public Builder addFile(Attachment file) { - if (file != null) { - files.add(file); - } - return this; - } - - public ExtendedEncoding build() { - return new ExtendedEncoding(new Message(this)); - } - } - - public static class Unpacker implements ExtendedEncoding.Unpacker<Message> { - @Override - public String getType() { - return TYPE; - } - - @Override - public Message unpack(MPMap<MPString, MPType<?>> map) { - Message.Builder builder = new Message.Builder(); - builder.subject(str(map.get(mp("subject")))); - builder.body(str(map.get(mp("body")))); - @SuppressWarnings("unchecked") - MPArray<MPBinary> parents = (MPArray<MPBinary>) map.get(mp("parents")); - if (parents != null) { - for (MPBinary parent : parents) { - builder.addParent(InventoryVector.fromHash(parent.getValue())); - } - } - @SuppressWarnings("unchecked") - MPArray<MPMap<MPString, MPType<?>>> files = (MPArray<MPMap<MPString, MPType<?>>>) map.get(mp("files")); - if (files != null) { - for (MPMap<MPString, MPType<?>> item : files) { - Attachment.Builder b = new Attachment.Builder(); - b.name(str(item.get(mp("name")))); - b.data(bin(item.get(mp("data")))); - b.type(str(item.get(mp("type")))); - String disposition = str(item.get(mp("disposition"))); - if ("inline".equals(disposition)) { - b.inline(); - } else if ("attachment".equals(disposition)) { - b.attachment(); - } - builder.addFile(b.build()); - } - } - - return new Message(builder); - } - - private byte[] bin(MPType data) { - if (data instanceof MPBinary) { - return ((MPBinary) data).getValue(); - } else { - return null; - } - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt new file mode 100644 index 0000000..c378394 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt @@ -0,0 +1,184 @@ +/* + * Copyright 2017 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.entity.valueobject.extended + +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Strings.str +import ch.dissem.msgpack.types.* +import ch.dissem.msgpack.types.Utils.mp +import org.slf4j.LoggerFactory +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.net.URLConnection +import java.nio.file.Files +import java.util.* + +/** + * Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work + * properly with future PyBitmessage implementations. + */ +data class Message constructor( + val subject: String, + val body: String, + val parents: List<InventoryVector>, + val files: List<Attachment> +) : ExtendedEncoding.ExtendedType { + + override val type: String = TYPE + + override fun pack(): MPMap<MPString, MPType<*>> { + val result = MPMap<MPString, MPType<*>>() + result.put(mp(""), mp(TYPE)) + result.put(mp("subject"), mp(subject)) + result.put(mp("body"), mp(body)) + + if (!files.isEmpty()) { + val items = MPArray<MPMap<MPString, MPType<*>>>() + result.put(mp("files"), items) + for (file in files) { + val item = MPMap<MPString, MPType<*>>() + item.put(mp("name"), mp(file.name)) + item.put(mp("data"), mp(*file.data)) + item.put(mp("type"), mp(file.type)) + item.put(mp("disposition"), mp(file.disposition.name)) + items.add(item) + } + } + if (!parents.isEmpty()) { + val items = MPArray<MPBinary>() + result.put(mp("parents"), items) + for ((hash) in parents) { + items.add(mp(*hash)) + } + } + return result + } + + class Builder { + private var subject: String? = null + private var body: String? = null + private val parents = LinkedList<InventoryVector>() + private val files = LinkedList<Attachment>() + + fun subject(subject: String): Builder { + this.subject = subject + return this + } + + fun body(body: String): Builder { + this.body = body + return this + } + + fun addParent(parent: Plaintext?): Builder { + if (parent != null) { + val iv = parent.inventoryVector + if (iv == null) { + LOG.debug("Ignored parent without IV") + } else { + parents.add(iv) + } + } + return this + } + + fun addParent(iv: InventoryVector?): Builder { + if (iv != null) { + parents.add(iv) + } + return this + } + + fun addFile(file: File?, disposition: Attachment.Disposition): Builder { + if (file != null) { + try { + files.add(Attachment.Builder() + .name(file.name) + .disposition(disposition) + .type(URLConnection.guessContentTypeFromStream(FileInputStream(file))) + .data(Files.readAllBytes(file.toPath())) + .build()) + } catch (e: IOException) { + LOG.error(e.message, e) + } + + } + return this + } + + fun addFile(file: Attachment?): Builder { + if (file != null) { + files.add(file) + } + return this + } + + fun build(): ExtendedEncoding { + return ExtendedEncoding(Message(subject!!, body!!, parents, files)) + } + } + + class Unpacker : ExtendedEncoding.Unpacker<Message> { + override val type: String = TYPE + + override fun unpack(map: MPMap<MPString, MPType<*>>): Message { + val subject = str(map[mp("subject")]) ?: "" + val body = str(map[mp("body")]) ?: "" + val parents = LinkedList<InventoryVector>() + val files = LinkedList<Attachment>() + val mpParents = map[mp("parents")] as? MPArray<*> + for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) { + parents.add(InventoryVector.fromHash( + (parent as? MPBinary)?.value ?: continue + ) ?: continue) + } + val mpFiles = map[mp("files")] as? MPArray<*> + for (item in mpFiles ?: emptyList<Any>()) { + if (item is MPMap<*, *>) { + val b = Attachment.Builder() + b.name(str(item[mp("name")])!!) + b.data( + bin(item[mp("data")] ?: continue) ?: continue + ) + b.type(str(item[mp("type")])!!) + val disposition = str(item[mp("disposition")]) + if ("inline" == disposition) { + b.inline() + } else if ("attachment" == disposition) { + b.attachment() + } + files.add(b.build()) + } + } + + return Message(subject, body, parents, files) + } + + private fun bin(data: MPType<*>): ByteArray? { + return (data as? MPBinary)?.value + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(Message::class.java) + + val TYPE = "message" + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java deleted file mode 100644 index 1c13440..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java +++ /dev/null @@ -1,114 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject.extended; - -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.msgpack.types.*; - -import java.io.IOException; -import java.util.Objects; - -import static ch.dissem.bitmessage.utils.Strings.str; -import static ch.dissem.msgpack.types.Utils.mp; - -/** - * Extended encoding type 'vote'. Specification still outstanding, so this will need some work. - */ -public class Vote implements ExtendedEncoding.ExtendedType { - private static final long serialVersionUID = -8427038604209964837L; - - public static final String TYPE = "vote"; - - private InventoryVector msgId; - private String vote; - - private Vote(Builder builder) { - msgId = builder.msgId; - vote = builder.vote; - } - - @Override - public String getType() { - return TYPE; - } - - public InventoryVector getMsgId() { - return msgId; - } - - public String getVote() { - return vote; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Vote vote1 = (Vote) o; - return Objects.equals(msgId, vote1.msgId) && - Objects.equals(vote, vote1.vote); - } - - @Override - public int hashCode() { - return Objects.hash(msgId, vote); - } - - @Override - public MPMap<MPString, MPType<?>> pack() throws IOException { - MPMap<MPString, MPType<?>> result = new MPMap<>(); - result.put(mp(""), mp(TYPE)); - result.put(mp("msgId"), mp(msgId.getHash())); - result.put(mp("vote"), mp(vote)); - return result; - } - - public static class Builder { - private InventoryVector msgId; - private String vote; - - public ExtendedEncoding up(Plaintext message) { - msgId = message.getInventoryVector(); - vote = "1"; - return new ExtendedEncoding(new Vote(this)); - } - - public ExtendedEncoding down(Plaintext message) { - msgId = message.getInventoryVector(); - vote = "1"; - return new ExtendedEncoding(new Vote(this)); - } - - public Builder msgId(InventoryVector iv) { - this.msgId = iv; - return this; - } - - public Builder vote(String vote) { - this.vote = vote; - return this; - } - - public ExtendedEncoding build() { - return new ExtendedEncoding(new Vote(this)); - } - } - - public static class Unpacker implements ExtendedEncoding.Unpacker<Vote> { - @Override - public String getType() { - return TYPE; - } - - @Override - public Vote unpack(MPMap<MPString, MPType<?>> map) { - Vote.Builder builder = new Vote.Builder(); - MPType<?> msgId = map.get(mp("msgId")); - if (msgId instanceof MPBinary) { - builder.msgId(InventoryVector.fromHash(((MPBinary) msgId).getValue())); - } - builder.vote(str(map.get(mp("vote")))); - return new Vote(builder); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt new file mode 100644 index 0000000..d26d38a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2017 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.entity.valueobject.extended + +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Strings.str +import ch.dissem.msgpack.types.MPBinary +import ch.dissem.msgpack.types.MPMap +import ch.dissem.msgpack.types.MPString +import ch.dissem.msgpack.types.MPType +import ch.dissem.msgpack.types.Utils.mp + +/** + * Extended encoding type 'vote'. Specification still outstanding, so this will need some work. + */ +data class Vote constructor(val msgId: InventoryVector, val vote: String) : ExtendedEncoding.ExtendedType { + + override val type: String = TYPE + + override fun pack(): MPMap<MPString, MPType<*>> { + val result = MPMap<MPString, MPType<*>>() + result.put(mp(""), mp(TYPE)) + result.put(mp("msgId"), mp(*msgId.hash)) + result.put(mp("vote"), mp(vote)) + return result + } + + class Builder { + private var msgId: InventoryVector? = null + private var vote: String? = null + + fun up(message: Plaintext): ExtendedEncoding { + msgId = message.inventoryVector + vote = "1" + return ExtendedEncoding(Vote(msgId!!, vote!!)) + } + + fun down(message: Plaintext): ExtendedEncoding { + msgId = message.inventoryVector + vote = "-1" + return ExtendedEncoding(Vote(msgId!!, vote!!)) + } + + fun msgId(iv: InventoryVector): Builder { + this.msgId = iv + return this + } + + fun vote(vote: String): Builder { + this.vote = vote + return this + } + + fun build(): ExtendedEncoding { + return ExtendedEncoding(Vote(msgId!!, vote!!)) + } + } + + class Unpacker : ExtendedEncoding.Unpacker<Vote> { + override val type: String + get() = TYPE + + override fun unpack(map: MPMap<MPString, MPType<*>>): Vote { + val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId") + val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given") + return Vote(msgId, vote) + } + } + + companion object { + @JvmField val TYPE = "vote" + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java b/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt similarity index 67% rename from core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java rename to core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt index 9f6674d..d838ed8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java +++ b/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,9 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +package ch.dissem.bitmessage.exception /** * Indicates an illegal Bitmessage address */ -public class AddressFormatException extends RuntimeException { - private static final long serialVersionUID = 6943764578672021573L; - - public AddressFormatException(String message) { - super(message); - } -} +class AddressFormatException(message: String) : RuntimeException(message) diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java b/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt similarity index 69% rename from core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java rename to core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt index 8e49586..b520c11 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java +++ b/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,15 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.MessagePayload; +package ch.dissem.bitmessage.exception /** * @author Christian Basler */ -public interface CustomCommandHandler { - MessagePayload handle(CustomMessage request); +class ApplicationException : RuntimeException { + + constructor(cause: Throwable) : super(cause) + + constructor(message: String) : super(message) + } diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt b/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt new file mode 100644 index 0000000..4d898f8 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2017 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.exception + +class DecryptionFailedException : Exception() diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java deleted file mode 100644 index 94a5d84..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015 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.exception; - -import ch.dissem.bitmessage.utils.Strings; - -import java.io.IOException; -import java.util.Arrays; - -public class InsufficientProofOfWorkException extends IOException { - private static final long serialVersionUID = 9105580366564571318L; - - public InsufficientProofOfWorkException(byte[] target, byte[] hash) { - super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved."); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt new file mode 100644 index 0000000..a878d88 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2017 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.exception + +import ch.dissem.bitmessage.utils.Strings +import java.io.IOException +import java.util.* + +class InsufficientProofOfWorkException(target: ByteArray, hash: ByteArray) : IOException( + "Insufficient proof of work: " + Strings.hex(target) + " required, " + + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.") diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java b/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt similarity index 64% rename from core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java rename to core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt index cecd950..0536991 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java +++ b/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,13 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +package ch.dissem.bitmessage.exception /** * An exception on the node that's severe enough to cause the client to disconnect this node. - * + * @author Ch. Basler */ -public class NodeException extends RuntimeException { - private static final long serialVersionUID = 2965325796118227802L; - - public NodeException(String message) { - super(message); - } - - public NodeException(String message, Throwable cause) { - super(message, cause); - } +class NodeException(message: String?, cause: Throwable? = null) : RuntimeException(message ?: cause?.message, cause) { + constructor(message: String) : this(message, null) } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java deleted file mode 100644 index 65c1d34..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.factory; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.Map; -import java.util.Stack; -import java.util.TreeMap; - -import static ch.dissem.bitmessage.ports.NetworkHandler.HEADER_SIZE; -import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE; - -/** - * A pool for {@link ByteBuffer}s. As they may use up a lot of memory, - * they should be reused as efficiently as possible. - */ -class BufferPool { - private static final Logger LOG = LoggerFactory.getLogger(BufferPool.class); - - public static final BufferPool bufferPool = new BufferPool(); - - private final Map<Integer, Stack<ByteBuffer>> pools = new TreeMap<>(); - - private BufferPool() { - pools.put(HEADER_SIZE, new Stack<ByteBuffer>()); - pools.put(54, new Stack<ByteBuffer>()); - pools.put(1000, new Stack<ByteBuffer>()); - pools.put(60000, new Stack<ByteBuffer>()); - pools.put(MAX_PAYLOAD_SIZE, new Stack<ByteBuffer>()); - } - - public synchronized ByteBuffer allocate(int capacity) { - Integer targetSize = getTargetSize(capacity); - Stack<ByteBuffer> pool = pools.get(targetSize); - if (pool.isEmpty()) { - LOG.trace("Creating new buffer of size " + targetSize); - return ByteBuffer.allocate(targetSize); - } else { - return pool.pop(); - } - } - - /** - * Returns a buffer that has the size of the Bitmessage network message header, 24 bytes. - * - * @return a buffer of size 24 - */ - public synchronized ByteBuffer allocateHeaderBuffer() { - Stack<ByteBuffer> pool = pools.get(HEADER_SIZE); - if (pool.isEmpty()) { - return ByteBuffer.allocate(HEADER_SIZE); - } else { - return pool.pop(); - } - } - - public synchronized void deallocate(ByteBuffer buffer) { - buffer.clear(); - Stack<ByteBuffer> pool = pools.get(buffer.capacity()); - if (pool == null) { - throw new IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() + - " one of " + pools.keySet() + " expected."); - } else { - pool.push(buffer); - } - } - - private Integer getTargetSize(int capacity) { - for (Integer size : pools.keySet()) { - if (size >= capacity) return size; - } - throw new IllegalArgumentException("Requested capacity too large: " + - "requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt new file mode 100644 index 0000000..a301d37 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2017 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.factory + +import ch.dissem.bitmessage.constants.Network.HEADER_SIZE +import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE +import org.slf4j.LoggerFactory +import java.nio.ByteBuffer +import java.util.* + +/** + * A pool for [ByteBuffer]s. As they may use up a lot of memory, + * they should be reused as efficiently as possible. + */ +object BufferPool { + private val LOG = LoggerFactory.getLogger(BufferPool::class.java) + + private val pools = mapOf( + HEADER_SIZE to Stack<ByteBuffer>(), + 54 to Stack<ByteBuffer>(), + 1000 to Stack<ByteBuffer>(), + 60000 to Stack<ByteBuffer>(), + MAX_PAYLOAD_SIZE to Stack<ByteBuffer>() + ) + + @Synchronized fun allocate(capacity: Int): ByteBuffer { + val targetSize = getTargetSize(capacity) + val pool = pools[targetSize] + if (pool == null || pool.isEmpty()) { + LOG.trace("Creating new buffer of size " + targetSize!!) + return ByteBuffer.allocate(targetSize) + } else { + return pool.pop() + } + } + + /** + * Returns a buffer that has the size of the Bitmessage network message header, 24 bytes. + + * @return a buffer of size 24 + */ + @Synchronized fun allocateHeaderBuffer(): ByteBuffer { + val pool = pools[HEADER_SIZE] + if (pool == null || pool.isEmpty()) { + return ByteBuffer.allocate(HEADER_SIZE) + } else { + return pool.pop() + } + } + + @Synchronized fun deallocate(buffer: ByteBuffer) { + buffer.clear() + val pool = pools[buffer.capacity()] + pool?.push(buffer) ?: throw IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() + + " one of " + pools.keys + " expected.") + } + + private fun getTargetSize(capacity: Int): Int? { + for (size in pools.keys) { + if (size >= capacity) return size + } + throw IllegalArgumentException("Requested capacity too large: " + + "requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java deleted file mode 100644 index b94b5f9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package ch.dissem.bitmessage.factory; - -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.entity.valueobject.extended.Vote; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.msgpack.Reader; -import ch.dissem.msgpack.types.MPMap; -import ch.dissem.msgpack.types.MPString; -import ch.dissem.msgpack.types.MPType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.InflaterInputStream; - -import static ch.dissem.bitmessage.utils.Strings.str; - -/** - * Factory that creates {@link ExtendedEncoding} objects from byte arrays. You can register your own types by adding a - * {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}. - */ -public class ExtendedEncodingFactory { - private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class); - private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory(); - private static final MPString KEY_MESSAGE_TYPE = new MPString(""); - private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>(); - - private ExtendedEncodingFactory() { - registerFactory(new Message.Unpacker()); - registerFactory(new Vote.Unpacker()); - } - - public void registerFactory(ExtendedEncoding.Unpacker<?> factory) { - factories.put(factory.getType(), factory); - } - - - public ExtendedEncoding unzip(byte[] zippedData) { - try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) { - Reader reader = Reader.getInstance(); - @SuppressWarnings("unchecked") - MPMap<MPString, MPType<?>> map = (MPMap<MPString, MPType<?>>) reader.read(unzipper); - MPType<?> messageType = map.get(KEY_MESSAGE_TYPE); - if (messageType == null) { - LOG.error("Missing message type"); - return null; - } - ExtendedEncoding.Unpacker<?> factory = factories.get(str(messageType)); - return new ExtendedEncoding(factory.unpack(map)); - } catch (ClassCastException | IOException e) { - throw new ApplicationException(e); - } - } - - public static ExtendedEncodingFactory getInstance() { - return INSTANCE; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt new file mode 100644 index 0000000..d8833c7 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 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.factory + +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.entity.valueobject.extended.Vote +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.Strings.str +import ch.dissem.msgpack.Reader +import ch.dissem.msgpack.types.MPMap +import ch.dissem.msgpack.types.MPString +import ch.dissem.msgpack.types.MPType +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.util.* +import java.util.zip.InflaterInputStream + +/** + * Factory that creates [ExtendedEncoding] objects from byte arrays. You can register your own types by adding a + * [ExtendedEncoding.Unpacker] using [.registerFactory]. + */ +object ExtendedEncodingFactory { + + private val LOG = LoggerFactory.getLogger(ExtendedEncodingFactory::class.java) + private val KEY_MESSAGE_TYPE = MPString("") + + private val factories = HashMap<String, ExtendedEncoding.Unpacker<*>>() + + init { + registerFactory(Message.Unpacker()) + registerFactory(Vote.Unpacker()) + } + + fun registerFactory(factory: ExtendedEncoding.Unpacker<*>) { + factories.put(factory.type, factory) + } + + + fun unzip(zippedData: ByteArray): ExtendedEncoding? { + try { + InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper -> + val reader = Reader.getInstance() + @Suppress("UNCHECKED_CAST") + val map = reader.read(unzipper) as MPMap<MPString, MPType<*>> + val messageType = map[KEY_MESSAGE_TYPE] + if (messageType == null) { + LOG.error("Missing message type") + return null + } + val factory = factories[str(messageType)] + return ExtendedEncoding( + factory?.unpack(map) ?: return null + ) + } + } catch (e: ClassCastException) { + throw ApplicationException(e) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java deleted file mode 100644 index 07b3161..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2015 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.factory; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.utils.TTL; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -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.cryptography; - -/** - * Creates {@link NetworkMessage} objects from {@link InputStream InputStreams} - */ -public class Factory { - private static final Logger LOG = LoggerFactory.getLogger(Factory.class); - - public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException { - try { - return V3MessageFactory.read(stream); - } catch (SocketTimeoutException | NodeException e) { - throw e; - } catch (SocketException e) { - throw new NodeException(e.getMessage(), e); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - return null; - } - } - - public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) { - try { - return V3MessageFactory.readObject(stream, length); - } catch (IOException e) { - LOG.error(e.getMessage(), e); - return null; - } - } - - public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey, - long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { - return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes, - Pubkey.Feature.bitfield(features)); - } - - public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey, - long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) { - if (publicSigningKey.length != 64 && publicSigningKey.length != 65) - throw new IllegalArgumentException("64 bytes signing key expected, but it was " - + publicSigningKey.length + " bytes long."); - if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65) - throw new IllegalArgumentException("64 bytes encryption key expected, but it was " - + publicEncryptionKey.length + " bytes long."); - - switch ((int) version) { - case 2: - return new V2Pubkey.Builder() - .stream(stream) - .publicSigningKey(publicSigningKey) - .publicEncryptionKey(publicEncryptionKey) - .behaviorBitfield(behaviourBitfield) - .build(); - case 3: - return new V3Pubkey.Builder() - .stream(stream) - .publicSigningKey(publicSigningKey) - .publicEncryptionKey(publicEncryptionKey) - .behaviorBitfield(behaviourBitfield) - .nonceTrialsPerByte(nonceTrialsPerByte) - .extraBytes(extraBytes) - .build(); - case 4: - return new V4Pubkey( - new V3Pubkey.Builder() - .stream(stream) - .publicSigningKey(publicSigningKey) - .publicEncryptionKey(publicEncryptionKey) - .behaviorBitfield(behaviourBitfield) - .nonceTrialsPerByte(nonceTrialsPerByte) - .extraBytes(extraBytes) - .build() - ); - default: - throw new IllegalArgumentException("Unexpected pubkey version " + version); - } - } - - public static BitmessageAddress createIdentityFromPrivateKey(String address, - byte[] privateSigningKey, byte[] privateEncryptionKey, - long nonceTrialsPerByte, long extraBytes, - int behaviourBitfield) { - BitmessageAddress temp = new BitmessageAddress(address); - PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey, - createPubkey(temp.getVersion(), temp.getStream(), - cryptography().createPublicKey(privateSigningKey), - cryptography().createPublicKey(privateEncryptionKey), - nonceTrialsPerByte, extraBytes, behaviourBitfield)); - BitmessageAddress result = new BitmessageAddress(privateKey); - if (!result.getAddress().equals(address)) { - throw new IllegalArgumentException("Address not matching private key. Address: " + address - + "; Address derived from private key: " + result.getAddress()); - } - return result; - } - - public static BitmessageAddress generatePrivateAddress(boolean shorter, - long stream, - Pubkey.Feature... features) { - return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features)); - } - - static ObjectPayload getObjectPayload(long objectType, - long version, - long streamNumber, - InputStream stream, - int length) throws IOException { - ObjectType type = ObjectType.fromNumber(objectType); - if (type != null) { - switch (type) { - case GET_PUBKEY: - return parseGetPubkey(version, streamNumber, stream, length); - case PUBKEY: - return parsePubkey(version, streamNumber, stream, length); - case MSG: - return parseMsg(version, streamNumber, stream, length); - case BROADCAST: - return parseBroadcast(version, streamNumber, stream, length); - default: - LOG.error("This should not happen, someone broke something in the code!"); - } - } - // fallback: just store the message - we don't really care what it is - LOG.trace("Unexpected object type: " + objectType); - return GenericPayload.read(version, streamNumber, stream, length); - } - - private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { - return GetPubkey.read(stream, streamNumber, length, version); - } - - public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException { - switch ((int) version) { - case 2: - return V2Pubkey.read(is, stream); - case 3: - return V3Pubkey.read(is, stream); - case 4: - return V4Pubkey.read(is, stream, length, encrypted); - } - LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object"); - return null; - } - - 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, streamNumber, stream, length); - } - - private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException { - return Msg.read(stream, streamNumber, length); - } - - private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException { - switch ((int) version) { - case 4: - return V4Broadcast.read(stream, streamNumber, length); - case 5: - return V5Broadcast.read(stream, streamNumber, length); - default: - LOG.debug("Encountered unknown broadcast version " + version); - return GenericPayload.read(version, streamNumber, stream, length); - } - } - - public static Broadcast getBroadcast(Plaintext plaintext) { - BitmessageAddress sendingAddress = plaintext.getFrom(); - if (sendingAddress.getVersion() < 4) { - return new V4Broadcast(sendingAddress, plaintext); - } else { - return new V5Broadcast(sendingAddress, plaintext); - } - } - - public static ObjectMessage createAck(Plaintext plaintext) { - if (plaintext == null || plaintext.getAckData() == null) - return null; - GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData()); - return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(plaintext.getTTL())).build(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt new file mode 100644 index 0000000..767e66b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2017 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.factory + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.NetworkMessage +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.entity.payload.ObjectType.MSG +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.UnixTime +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream +import java.net.SocketException +import java.net.SocketTimeoutException + +/** + * Creates [NetworkMessage] objects from [InputStreams][InputStream] + */ +object Factory { + private val LOG = LoggerFactory.getLogger(Factory::class.java) + + @Throws(SocketTimeoutException::class) + @JvmStatic fun getNetworkMessage(@Suppress("UNUSED_PARAMETER") version: Int, stream: InputStream): NetworkMessage? { + try { + return V3MessageFactory.read(stream) + } catch (e: Exception) { + when (e) { + is SocketTimeoutException, + is NodeException -> throw e + is SocketException -> throw NodeException(e.message, e) + else -> { + LOG.error(e.message, e) + return null + } + } + } + + } + + @JvmStatic fun getObjectMessage(version: Int, stream: InputStream, length: Int): ObjectMessage? { + try { + return V3MessageFactory.readObject(stream, length) + } catch (e: IOException) { + LOG.error(e.message, e) + return null + } + } + + @JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey { + return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes, + Pubkey.Feature.bitfield(*features)) + } + + @JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, behaviourBitfield: Int): Pubkey { + if (publicSigningKey.size != 64 && publicSigningKey.size != 65) + throw IllegalArgumentException("64 bytes signing key expected, but it was " + + publicSigningKey.size + " bytes long.") + if (publicEncryptionKey.size != 64 && publicEncryptionKey.size != 65) + throw IllegalArgumentException("64 bytes encryption key expected, but it was " + + publicEncryptionKey.size + " bytes long.") + + when (version.toInt()) { + 2 -> return V2Pubkey.Builder() + .stream(stream) + .publicSigningKey(publicSigningKey) + .publicEncryptionKey(publicEncryptionKey) + .behaviorBitfield(behaviourBitfield) + .build() + 3 -> return V3Pubkey.Builder() + .stream(stream) + .publicSigningKey(publicSigningKey) + .publicEncryptionKey(publicEncryptionKey) + .behaviorBitfield(behaviourBitfield) + .nonceTrialsPerByte(nonceTrialsPerByte) + .extraBytes(extraBytes) + .build() + 4 -> return V4Pubkey( + V3Pubkey.Builder() + .stream(stream) + .publicSigningKey(publicSigningKey) + .publicEncryptionKey(publicEncryptionKey) + .behaviorBitfield(behaviourBitfield) + .nonceTrialsPerByte(nonceTrialsPerByte) + .extraBytes(extraBytes) + .build() + ) + else -> throw IllegalArgumentException("Unexpected pubkey version " + version) + } + } + + @JvmStatic fun createIdentityFromPrivateKey(address: String, + privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, + behaviourBitfield: Int): BitmessageAddress { + val temp = BitmessageAddress(address) + val privateKey = PrivateKey(privateSigningKey, privateEncryptionKey, + createPubkey(temp.version, temp.stream, + cryptography().createPublicKey(privateSigningKey), + cryptography().createPublicKey(privateEncryptionKey), + nonceTrialsPerByte, extraBytes, behaviourBitfield)) + val result = BitmessageAddress(privateKey) + if (result.address != address) { + throw IllegalArgumentException("Address not matching private key. Address: " + address + + "; Address derived from private key: " + result.address) + } + return result + } + + @JvmStatic fun generatePrivateAddress(shorter: Boolean, + stream: Long, + vararg features: Pubkey.Feature): BitmessageAddress { + return BitmessageAddress(PrivateKey(shorter, stream, 1000, 1000, *features)) + } + + @JvmStatic fun getObjectPayload(objectType: Long, + version: Long, + streamNumber: Long, + stream: InputStream, + length: Int): ObjectPayload { + val type = ObjectType.fromNumber(objectType) + if (type != null) { + when (type) { + ObjectType.GET_PUBKEY -> return parseGetPubkey(version, streamNumber, stream, length) + ObjectType.PUBKEY -> return parsePubkey(version, streamNumber, stream, length) + MSG -> return parseMsg(version, streamNumber, stream, length) + ObjectType.BROADCAST -> return parseBroadcast(version, streamNumber, stream, length) + else -> LOG.error("This should not happen, someone broke something in the code!") + } + } + // fallback: just store the message - we don't really care what it is + LOG.trace("Unexpected object type: " + objectType) + return GenericPayload.read(version, streamNumber, stream, length) + } + + @JvmStatic private fun parseGetPubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + return GetPubkey.read(stream, streamNumber, length, version) + } + + @JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? { + when (version.toInt()) { + 2 -> return V2Pubkey.read(`is`, stream) + 3 -> return V3Pubkey.read(`is`, stream) + 4 -> return V4Pubkey.read(`is`, stream, length, encrypted) + } + LOG.debug("Unexpected pubkey version $version, handling as generic payload object") + return null + } + + @JvmStatic private fun parsePubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + val pubkey = readPubkey(version, streamNumber, stream, length, true) + return pubkey ?: GenericPayload.read(version, streamNumber, stream, length) + } + + @JvmStatic private fun parseMsg(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + return Msg.read(stream, streamNumber, length) + } + + @JvmStatic private fun parseBroadcast(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + when (version.toInt()) { + 4 -> return V4Broadcast.read(stream, streamNumber, length) + 5 -> return V5Broadcast.read(stream, streamNumber, length) + else -> { + LOG.debug("Encountered unknown broadcast version " + version) + return GenericPayload.read(version, streamNumber, stream, length) + } + } + } + + @JvmStatic fun getBroadcast(plaintext: Plaintext): Broadcast { + val sendingAddress = plaintext.from + if (sendingAddress.version < 4) { + return V4Broadcast(sendingAddress, plaintext) + } else { + return V5Broadcast(sendingAddress, plaintext) + } + } + + @JvmStatic fun createAck(from: BitmessageAddress, ackData: ByteArray?, ttl: Long): ObjectMessage? { + val ack = GenericPayload( + 3, from.stream, + ackData ?: return null + ) + return ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now + ttl).build() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java deleted file mode 100644 index 378f658..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2015 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.factory; - -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.utils.AccessCounter; -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Creates protocol v3 network messages from {@link InputStream InputStreams} - */ -class V3MessageFactory { - private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class); - - public static NetworkMessage read(InputStream in) throws IOException { - findMagic(in); - String command = getCommand(in); - int length = (int) Decode.uint32(in); - if (length > 1600003) { - throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected."); - } - byte[] checksum = Decode.bytes(in, 4); - - byte[] payloadBytes = Decode.bytes(in, length); - - if (testChecksum(checksum, payloadBytes)) { - MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length); - if (payload != null) - return new NetworkMessage(payload); - else - return null; - } else { - throw new IOException("Checksum failed for message '" + command + "'"); - } - } - - static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException { - switch (command) { - case "version": - return parseVersion(stream); - case "verack": - return new VerAck(); - case "addr": - return parseAddr(stream); - case "inv": - return parseInv(stream); - case "getdata": - return parseGetData(stream); - case "object": - return readObject(stream, length); - case "custom": - return readCustom(stream, length); - default: - LOG.debug("Unknown command: " + command); - return null; - } - } - - private static MessagePayload readCustom(InputStream in, int length) throws IOException { - return CustomMessage.read(in, length); - } - - public static ObjectMessage readObject(InputStream in, int length) throws IOException { - AccessCounter counter = new AccessCounter(); - byte nonce[] = Decode.bytes(in, 8, counter); - long expiresTime = Decode.int64(in, counter); - long objectType = Decode.uint32(in, counter); - long version = Decode.varInt(in, counter); - long stream = Decode.varInt(in, counter); - - byte[] data = Decode.bytes(in, length - counter.length()); - ObjectPayload payload; - try { - ByteArrayInputStream dataStream = new ByteArrayInputStream(data); - payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length); - } catch (Exception e) { - if (LOG.isTraceEnabled()) { - LOG.trace("Could not parse object payload - using generic payload instead", e); - LOG.trace(Strings.hex(data).toString()); - } - payload = new GenericPayload(version, stream, data); - } - - return new ObjectMessage.Builder() - .nonce(nonce) - .expiresTime(expiresTime) - .objectType(objectType) - .stream(stream) - .payload(payload) - .build(); - } - - private static GetData parseGetData(InputStream stream) throws IOException { - long count = Decode.varInt(stream); - GetData.Builder builder = new GetData.Builder(); - for (int i = 0; i < count; i++) { - builder.addInventoryVector(parseInventoryVector(stream)); - } - return builder.build(); - } - - private static Inv parseInv(InputStream stream) throws IOException { - long count = Decode.varInt(stream); - Inv.Builder builder = new Inv.Builder(); - for (int i = 0; i < count; i++) { - builder.addInventoryVector(parseInventoryVector(stream)); - } - return builder.build(); - } - - private static Addr parseAddr(InputStream stream) throws IOException { - long count = Decode.varInt(stream); - Addr.Builder builder = new Addr.Builder(); - for (int i = 0; i < count; i++) { - builder.addAddress(parseAddress(stream, false)); - } - return builder.build(); - } - - private static Version parseVersion(InputStream stream) throws IOException { - int version = Decode.int32(stream); - long services = Decode.int64(stream); - long timestamp = Decode.int64(stream); - NetworkAddress addrRecv = parseAddress(stream, true); - NetworkAddress addrFrom = parseAddress(stream, true); - long nonce = Decode.int64(stream); - String userAgent = Decode.varString(stream); - long[] streamNumbers = Decode.varIntList(stream); - - return new Version.Builder() - .version(version) - .services(services) - .timestamp(timestamp) - .addrRecv(addrRecv).addrFrom(addrFrom) - .nonce(nonce) - .userAgent(userAgent) - .streams(streamNumbers).build(); - } - - private static InventoryVector parseInventoryVector(InputStream stream) throws IOException { - return InventoryVector.fromHash(Decode.bytes(stream, 32)); - } - - private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException { - long time; - long streamNumber; - if (!light) { - time = Decode.int64(stream); - streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct - } else { - time = 0; - streamNumber = 0; - } - long services = Decode.int64(stream); - byte[] ipv6 = Decode.bytes(stream, 16); - int port = Decode.uint16(stream); - return new NetworkAddress.Builder() - .time(time) - .stream(streamNumber) - .services(services) - .ipv6(ipv6) - .port(port) - .build(); - } - - private static boolean testChecksum(byte[] checksum, byte[] payload) { - byte[] payloadChecksum = cryptography().sha512(payload); - for (int i = 0; i < checksum.length; i++) { - if (checksum[i] != payloadChecksum[i]) { - return false; - } - } - return true; - } - - private static String getCommand(InputStream stream) throws IOException { - byte[] bytes = new byte[12]; - int end = bytes.length; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = (byte) stream.read(); - if (end == bytes.length) { - if (bytes[i] == 0) end = i; - } else { - if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command"); - } - } - return new String(bytes, 0, end, "ASCII"); - } - - private static void findMagic(InputStream in) throws IOException { - int pos = 0; - for (int i = 0; i < 1620000; i++) { - byte b = (byte) in.read(); - if (b == MAGIC_BYTES[pos]) { - if (pos + 1 == MAGIC_BYTES.length) { - return; - } - } else if (pos > 0 && b == MAGIC_BYTES[0]) { - pos = 1; - } else { - pos = 0; - } - pos++; - } - throw new NodeException("Failed to find MAGIC bytes in stream"); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt new file mode 100644 index 0000000..bc62662 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt @@ -0,0 +1,230 @@ +/* + * Copyright 2017 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.factory + +import ch.dissem.bitmessage.entity.* +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.ObjectPayload +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.AccessCounter +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.Strings +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.util.* + +/** + * Creates protocol v3 network messages from [InputStreams][InputStream] + */ +object V3MessageFactory { + private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java) + + @JvmStatic + fun read(`in`: InputStream): NetworkMessage? { + findMagic(`in`) + val command = getCommand(`in`) + val length = Decode.uint32(`in`).toInt() + if (length > 1600003) { + throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.") + } + val checksum = Decode.bytes(`in`, 4) + + val payloadBytes = Decode.bytes(`in`, length) + + if (testChecksum(checksum, payloadBytes)) { + val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length) + if (payload != null) + return NetworkMessage(payload) + else + return null + } else { + throw IOException("Checksum failed for message '$command'") + } + } + + @JvmStatic + fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? { + when (command) { + "version" -> return parseVersion(stream) + "verack" -> return VerAck() + "addr" -> return parseAddr(stream) + "inv" -> return parseInv(stream) + "getdata" -> return parseGetData(stream) + "object" -> return readObject(stream, length) + "custom" -> return readCustom(stream, length) + else -> { + LOG.debug("Unknown command: " + command) + return null + } + } + } + + private fun readCustom(`in`: InputStream, length: Int): MessagePayload { + return CustomMessage.read(`in`, length) + } + + @JvmStatic + fun readObject(`in`: InputStream, length: Int): ObjectMessage { + val counter = AccessCounter() + val nonce = Decode.bytes(`in`, 8, counter) + val expiresTime = Decode.int64(`in`, counter) + val objectType = Decode.uint32(`in`, counter) + val version = Decode.varInt(`in`, counter) + val stream = Decode.varInt(`in`, counter) + + val data = Decode.bytes(`in`, length - counter.length()) + var payload: ObjectPayload + try { + val dataStream = ByteArrayInputStream(data) + payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size) + } catch (e: Exception) { + if (LOG.isTraceEnabled) { + LOG.trace("Could not parse object payload - using generic payload instead", e) + LOG.trace(Strings.hex(data).toString()) + } + payload = GenericPayload(version, stream, data) + } + + return ObjectMessage.Builder() + .nonce(nonce) + .expiresTime(expiresTime) + .objectType(objectType) + .stream(stream) + .payload(payload) + .build() + } + + private fun parseGetData(stream: InputStream): GetData { + val count = Decode.varInt(stream) + val inventoryVectors = LinkedList<InventoryVector>() + for (i in 0..count - 1) { + inventoryVectors.add(parseInventoryVector(stream)) + } + return GetData(inventoryVectors) + } + + private fun parseInv(stream: InputStream): Inv { + val count = Decode.varInt(stream) + val inventoryVectors = LinkedList<InventoryVector>() + for (i in 0..count - 1) { + inventoryVectors.add(parseInventoryVector(stream)) + } + return Inv(inventoryVectors) + } + + private fun parseAddr(stream: InputStream): Addr { + val count = Decode.varInt(stream) + val networkAddresses = LinkedList<NetworkAddress>() + for (i in 0..count - 1) { + networkAddresses.add(parseAddress(stream, false)) + } + return Addr(networkAddresses) + } + + private fun parseVersion(stream: InputStream): Version { + val version = Decode.int32(stream) + val services = Decode.int64(stream) + val timestamp = Decode.int64(stream) + val addrRecv = parseAddress(stream, true) + val addrFrom = parseAddress(stream, true) + val nonce = Decode.int64(stream) + val userAgent = Decode.varString(stream) + val streamNumbers = Decode.varIntList(stream) + + return Version.Builder() + .version(version) + .services(services) + .timestamp(timestamp) + .addrRecv(addrRecv).addrFrom(addrFrom) + .nonce(nonce) + .userAgent(userAgent) + .streams(*streamNumbers).build() + } + + private fun parseInventoryVector(stream: InputStream): InventoryVector { + return InventoryVector(Decode.bytes(stream, 32)) + } + + private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress { + val time: Long + val streamNumber: Long + if (!light) { + time = Decode.int64(stream) + streamNumber = Decode.uint32(stream) // This isn't consistent, not sure if this is correct + } else { + time = 0 + streamNumber = 0 + } + val services = Decode.int64(stream) + val ipv6 = Decode.bytes(stream, 16) + val port = Decode.uint16(stream) + return NetworkAddress.Builder() + .time(time) + .stream(streamNumber) + .services(services) + .ipv6(ipv6) + .port(port) + .build() + } + + private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean { + val payloadChecksum = cryptography().sha512(payload) + for (i in checksum.indices) { + if (checksum[i] != payloadChecksum[i]) { + return false + } + } + return true + } + + private fun getCommand(stream: InputStream): String { + val bytes = ByteArray(12) + var end = bytes.size + for (i in bytes.indices) { + bytes[i] = stream.read().toByte() + if (end == bytes.size) { + if (bytes[i].toInt() == 0) end = i + } else { + if (bytes[i].toInt() != 0) throw IOException("'\\u0000' padding expected for command") + } + } + return String(bytes, 0, end, Charsets.US_ASCII) + } + + private fun findMagic(`in`: InputStream) { + var pos = 0 + for (i in 0..1619999) { + val b = `in`.read().toByte() + if (b == NetworkMessage.MAGIC_BYTES[pos]) { + if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) { + return + } + } else if (pos > 0 && b == NetworkMessage.MAGIC_BYTES[0]) { + pos = 1 + } else { + pos = 0 + } + pos++ + } + throw NodeException("Failed to find MAGIC bytes in stream") + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java deleted file mode 100644 index 89677cd..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.factory; - -import ch.dissem.bitmessage.entity.MessagePayload; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; -import static ch.dissem.bitmessage.factory.BufferPool.bufferPool; -import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message. - */ -public class V3MessageReader { - private ByteBuffer headerBuffer; - private ByteBuffer dataBuffer; - - private ReaderState state = ReaderState.MAGIC; - private String command; - private int length; - private byte[] checksum; - - private List<NetworkMessage> messages = new LinkedList<>(); - - public ByteBuffer getActiveBuffer() { - if (state != null && state != ReaderState.DATA) { - if (headerBuffer == null) { - headerBuffer = bufferPool.allocateHeaderBuffer(); - } - } - return state == ReaderState.DATA ? dataBuffer : headerBuffer; - } - - public void update() { - if (state != ReaderState.DATA) { - getActiveBuffer(); - headerBuffer.flip(); - } - switch (state) { - case MAGIC: - if (!findMagicBytes(headerBuffer)) { - headerBuffer.compact(); - return; - } - state = ReaderState.HEADER; - case HEADER: - if (headerBuffer.remaining() < 20) { - headerBuffer.compact(); - headerBuffer.limit(20); - return; - } - command = getCommand(headerBuffer); - length = (int) Decode.uint32(headerBuffer); - if (length > MAX_PAYLOAD_SIZE) { - throw new NodeException("Payload of " + length + " bytes received, no more than " + - MAX_PAYLOAD_SIZE + " was expected."); - } - checksum = new byte[4]; - headerBuffer.get(checksum); - state = ReaderState.DATA; - bufferPool.deallocate(headerBuffer); - headerBuffer = null; - dataBuffer = bufferPool.allocate(length); - dataBuffer.clear(); - dataBuffer.limit(length); - case DATA: - if (dataBuffer.position() < length) { - return; - } else { - dataBuffer.flip(); - } - if (!testChecksum(dataBuffer)) { - state = ReaderState.MAGIC; - throw new NodeException("Checksum failed for message '" + command + "'"); - } - try { - MessagePayload payload = V3MessageFactory.getPayload( - command, - new ByteArrayInputStream(dataBuffer.array(), - dataBuffer.arrayOffset() + dataBuffer.position(), length), - length); - if (payload != null) { - messages.add(new NetworkMessage(payload)); - } - } catch (IOException e) { - throw new NodeException(e.getMessage()); - } finally { - state = ReaderState.MAGIC; - bufferPool.deallocate(dataBuffer); - dataBuffer = null; - dataBuffer = null; - } - } - } - - public List<NetworkMessage> getMessages() { - return messages; - } - - private boolean findMagicBytes(ByteBuffer buffer) { - int i = 0; - while (buffer.hasRemaining()) { - if (i == 0) { - buffer.mark(); - } - if (buffer.get() == MAGIC_BYTES[i]) { - i++; - if (i == MAGIC_BYTES.length) { - return true; - } - } else { - i = 0; - } - } - if (i > 0) { - buffer.reset(); - } - return false; - } - - private static String getCommand(ByteBuffer buffer) { - int start = buffer.position(); - int l = 0; - while (l < 12 && buffer.get() != 0) l++; - int i = l + 1; - while (i < 12) { - if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command"); - i++; - } - try { - return new String(buffer.array(), start, l, "ASCII"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } - - private boolean testChecksum(ByteBuffer buffer) { - byte[] payloadChecksum = cryptography().sha512(buffer.array(), - buffer.arrayOffset() + buffer.position(), length); - for (int i = 0; i < checksum.length; i++) { - if (checksum[i] != payloadChecksum[i]) { - return false; - } - } - return true; - } - - /** - * De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its - * connection is severed. - */ - public void cleanup() { - state = null; - if (headerBuffer != null) { - bufferPool.deallocate(headerBuffer); - } - if (dataBuffer != null) { - bufferPool.deallocate(dataBuffer); - } - } - - private enum ReaderState {MAGIC, HEADER, DATA} -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt new file mode 100644 index 0000000..694e8c0 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt @@ -0,0 +1,190 @@ +/* + * Copyright 2017 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.factory + +import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE +import ch.dissem.bitmessage.entity.NetworkMessage +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayInputStream +import java.io.IOException +import java.nio.ByteBuffer +import java.util.* + +/** + * Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message. + */ +class V3MessageReader { + private var headerBuffer: ByteBuffer? = null + private var dataBuffer: ByteBuffer? = null + + private var state: ReaderState? = ReaderState.MAGIC + private var command: String? = null + private var length: Int = 0 + private val checksum = ByteArray(4) + + private val messages = LinkedList<NetworkMessage>() + + val activeBuffer: ByteBuffer + get() { + if (state != null && state != ReaderState.DATA) { + if (headerBuffer == null) { + headerBuffer = BufferPool.allocateHeaderBuffer() + } + } + return if (state == ReaderState.DATA) + dataBuffer ?: throw IllegalStateException("data buffer is null") + else + headerBuffer ?: throw IllegalStateException("header buffer is null") + } + + fun update() { + if (state != ReaderState.DATA) { + activeBuffer + headerBuffer!!.flip() + } + when (state) { + V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null")) + V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null")) + V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null")) + } + } + + private fun magic(headerBuffer: ByteBuffer) { + if (!findMagicBytes(headerBuffer)) { + headerBuffer.compact() + return + } else { + state = ReaderState.HEADER + header(headerBuffer) + } + } + + private fun header(headerBuffer: ByteBuffer) { + if (headerBuffer.remaining() < 20) { + headerBuffer.compact() + headerBuffer.limit(20) + return + } + command = getCommand(headerBuffer) + length = Decode.uint32(headerBuffer).toInt() + if (length > MAX_PAYLOAD_SIZE) { + throw NodeException("Payload of " + length + " bytes received, no more than " + + MAX_PAYLOAD_SIZE + " was expected.") + } + headerBuffer.get(checksum) + state = ReaderState.DATA + this.headerBuffer = null + BufferPool.deallocate(headerBuffer) + val dataBuffer = BufferPool.allocate(length) + this.dataBuffer = dataBuffer + data(dataBuffer) + } + + private fun data(dataBuffer: ByteBuffer) { + dataBuffer.clear() + dataBuffer.limit(length) + if (dataBuffer.position() < length) { + return + } else { + dataBuffer.flip() + } + if (!testChecksum(dataBuffer)) { + state = ReaderState.MAGIC + this.dataBuffer = null + BufferPool.deallocate(dataBuffer) + throw NodeException("Checksum failed for message '$command'") + } + try { + V3MessageFactory.getPayload( + command ?: throw IllegalStateException("command is null"), + ByteArrayInputStream(dataBuffer.array(), + dataBuffer.arrayOffset() + dataBuffer.position(), length), + length + )?.let { messages.add(NetworkMessage(it)) } + } catch (e: IOException) { + throw NodeException(e.message) + } finally { + state = ReaderState.MAGIC + this.dataBuffer = null + BufferPool.deallocate(dataBuffer) + } + } + + fun getMessages(): List<NetworkMessage> { + return messages + } + + private fun findMagicBytes(buffer: ByteBuffer): Boolean { + var i = 0 + while (buffer.hasRemaining()) { + if (i == 0) { + buffer.mark() + } + if (buffer.get() == NetworkMessage.MAGIC_BYTES[i]) { + i++ + if (i == NetworkMessage.MAGIC_BYTES.size) { + return true + } + } else { + i = 0 + } + } + if (i > 0) { + buffer.reset() + } + return false + } + + private fun getCommand(buffer: ByteBuffer): String { + val start = buffer.position() + var l = 0 + while (l < 12 && buffer.get().toInt() != 0) l++ + var i = l + 1 + while (i < 12) { + if (buffer.get().toInt() != 0) throw NodeException("'\\u0000' padding expected for command") + i++ + } + return String(buffer.array(), start, l, Charsets.US_ASCII) + } + + private fun testChecksum(buffer: ByteBuffer): Boolean { + val payloadChecksum = cryptography().sha512(buffer.array(), + buffer.arrayOffset() + buffer.position(), length) + for (i in checksum.indices) { + if (checksum[i] != payloadChecksum[i]) { + return false + } + } + return true + } + + /** + * De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its + * connection is severed. + */ + fun cleanup() { + state = null + headerBuffer?.let { BufferPool.deallocate(it) } + dataBuffer?.let { BufferPool.deallocate(it) } + } + + private enum class ReaderState { + MAGIC, HEADER, DATA + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java deleted file mode 100644 index 304c20d..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2015 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.ports; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.math.BigInteger; -import java.security.*; - -import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; -import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; -import static ch.dissem.bitmessage.utils.Numbers.max; - -/** - * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. - */ -public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder { - protected static final Logger LOG = LoggerFactory.getLogger(Cryptography.class); - private static final SecureRandom RANDOM = new SecureRandom(); - private static final BigInteger TWO = BigInteger.valueOf(2); - private static final BigInteger TWO_POW_64 = TWO.pow(64); - private static final BigInteger TWO_POW_16 = TWO.pow(16); - - protected static final String ALGORITHM_ECDSA = "ECDSA"; - protected static final String ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"; - protected static final String ALGORITHM_EVP_SHA256 = "SHA256withECDSA"; - - protected final Provider provider; - private InternalContext context; - - protected AbstractCryptography(Provider provider) { - this.provider = provider; - } - - @Override - public void setContext(InternalContext context) { - this.context = context; - } - - public byte[] sha512(byte[] data, int offset, int length) { - MessageDigest mda = md("SHA-512"); - mda.update(data, offset, length); - return mda.digest(); - } - - public byte[] sha512(byte[]... data) { - return hash("SHA-512", data); - } - - public byte[] doubleSha512(byte[]... data) { - MessageDigest mda = md("SHA-512"); - for (byte[] d : data) { - mda.update(d); - } - return mda.digest(mda.digest()); - } - - public byte[] doubleSha512(byte[] data, int length) { - MessageDigest mda = md("SHA-512"); - mda.update(data, 0, length); - return mda.digest(mda.digest()); - } - - public byte[] ripemd160(byte[]... data) { - return hash("RIPEMD160", data); - } - - public byte[] doubleSha256(byte[] data, int length) { - MessageDigest mda = md("SHA-256"); - mda.update(data, 0, length); - return mda.digest(mda.digest()); - } - - public byte[] sha1(byte[]... data) { - return hash("SHA-1", data); - } - - public byte[] randomBytes(int length) { - byte[] result = new byte[length]; - RANDOM.nextBytes(result); - return result; - } - - public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, - long extraBytes, ProofOfWorkEngine.Callback callback) { - nonceTrialsPerByte = max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE); - extraBytes = max(extraBytes, NETWORK_EXTRA_BYTES); - - byte[] initialHash = getInitialHash(object); - - byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); - - context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback); - } - - public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) - throws IOException { - byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); - byte[] value = doubleSha512(object.getNonce(), getInitialHash(object)); - if (Bytes.lt(target, value, 8)) { - throw new InsufficientProofOfWorkException(target, value); - } - } - - protected byte[] doSign(byte[] data, java.security.PrivateKey privKey) throws GeneralSecurityException { - // TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network - Signature sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider); - sig.initSign(privKey); - sig.update(data); - return sig.sign(); - } - - - protected boolean doCheckSignature(byte[] data, byte[] signature, PublicKey publicKey) throws GeneralSecurityException { - for (String algorithm : new String[]{ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256}) { - Signature sig = Signature.getInstance(algorithm, provider); - sig.initVerify(publicKey); - sig.update(data); - if (sig.verify(signature)) { - return true; - } - } - return false; - } - - @Override - public byte[] getInitialHash(ObjectMessage object) { - return sha512(object.getPayloadBytesWithoutNonce()); - } - - @Override - public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - if (nonceTrialsPerByte == 0) nonceTrialsPerByte = NETWORK_NONCE_TRIALS_PER_BYTE; - if (extraBytes == 0) extraBytes = NETWORK_EXTRA_BYTES; - - BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now()); - BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes); - BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte) - .multiply( - powLength.add( - powLength.multiply(TTL).divide(TWO_POW_16) - ) - ); - return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8); - } - - private byte[] hash(String algorithm, byte[]... data) { - MessageDigest mda = md(algorithm); - for (byte[] d : data) { - mda.update(d); - } - return mda.digest(); - } - - private MessageDigest md(String algorithm) { - try { - return MessageDigest.getInstance(algorithm, provider); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - public byte[] mac(byte[] key_m, byte[] data) { - try { - Mac mac = Mac.getInstance("HmacSHA256", provider); - mac.init(new SecretKeySpec(key_m, "HmacSHA256")); - return mac.doFinal(data); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey, - long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { - return Factory.createPubkey(version, stream, - createPublicKey(privateSigningKey), - createPublicKey(privateEncryptionKey), - nonceTrialsPerByte, extraBytes, features); - } - - public BigInteger keyToBigInt(byte[] privateKey) { - return new BigInteger(1, privateKey); - } - - public long randomNonce() { - return RANDOM.nextLong(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt new file mode 100644 index 0000000..56b3522 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.UnixTime +import ch.dissem.bitmessage.utils.max +import org.slf4j.LoggerFactory +import java.math.BigInteger +import java.security.* +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +/** + * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. + */ +abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography { + private val context by InternalContext + + @JvmField protected val ALGORITHM_ECDSA = "ECDSA" + @JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" + @JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA" + + override fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray { + val mda = md("SHA-512") + mda.update(data, offset, length) + return mda.digest() + } + + override fun sha512(vararg data: ByteArray): ByteArray { + return hash("SHA-512", *data) + } + + override fun doubleSha512(vararg data: ByteArray): ByteArray { + val mda = md("SHA-512") + for (d in data) { + mda.update(d) + } + return mda.digest(mda.digest()) + } + + override fun doubleSha512(data: ByteArray, length: Int): ByteArray { + val mda = md("SHA-512") + mda.update(data, 0, length) + return mda.digest(mda.digest()) + } + + override fun ripemd160(vararg data: ByteArray): ByteArray { + return hash("RIPEMD160", *data) + } + + override fun doubleSha256(data: ByteArray, length: Int): ByteArray { + val mda = md("SHA-256") + mda.update(data, 0, length) + return mda.digest(mda.digest()) + } + + override fun sha1(vararg data: ByteArray): ByteArray { + return hash("SHA-1", *data) + } + + override fun randomBytes(length: Int): ByteArray { + val result = ByteArray(length) + RANDOM.nextBytes(result) + return result + } + + override fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, + extraBytes: Long, callback: ProofOfWorkEngine.Callback) { + + val initialHash = getInitialHash(`object`) + + val target = getProofOfWorkTarget(`object`, + max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) + + context.proofOfWorkEngine.calculateNonce(initialHash, target, callback) + } + + @Throws(InsufficientProofOfWorkException::class) + override fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + val target = getProofOfWorkTarget(`object`, nonceTrialsPerByte, extraBytes) + val value = doubleSha512(`object`.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(`object`)) + if (Bytes.lt(target, value, 8)) { + throw InsufficientProofOfWorkException(target, value) + } + } + + @Throws(GeneralSecurityException::class) + protected fun doSign(data: ByteArray, privKey: java.security.PrivateKey): ByteArray { + // TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network + val sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider) + sig.initSign(privKey) + sig.update(data) + return sig.sign() + } + + + @Throws(GeneralSecurityException::class) + protected fun doCheckSignature(data: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean { + for (algorithm in arrayOf(ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256)) { + val sig = Signature.getInstance(algorithm, provider) + sig.initVerify(publicKey) + sig.update(data) + if (sig.verify(signature)) { + return true + } + } + return false + } + + override fun getInitialHash(`object`: ObjectMessage): ByteArray { + return sha512(`object`.payloadBytesWithoutNonce) + } + + override fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray { + @Suppress("NAME_SHADOWING") + val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte + @Suppress("NAME_SHADOWING") + val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes + + val TTL = BigInteger.valueOf(`object`.expiresTime - UnixTime.now) + val powLength = BigInteger.valueOf(`object`.payloadBytesWithoutNonce.size + extraBytes) + val denominator = BigInteger.valueOf(nonceTrialsPerByte) + .multiply( + powLength.add( + powLength.multiply(TTL).divide(TWO_POW_16) + ) + ) + return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8) + } + + private fun hash(algorithm: String, vararg data: ByteArray): ByteArray { + val mda = md(algorithm) + for (d in data) { + mda.update(d) + } + return mda.digest() + } + + private fun md(algorithm: String): MessageDigest { + try { + return MessageDigest.getInstance(algorithm, provider) + } catch (e: GeneralSecurityException) { + throw ApplicationException(e) + } + + } + + override fun mac(key_m: ByteArray, data: ByteArray): ByteArray { + try { + val mac = Mac.getInstance("HmacSHA256", provider) + mac.init(SecretKeySpec(key_m, "HmacSHA256")) + return mac.doFinal(data) + } catch (e: GeneralSecurityException) { + throw ApplicationException(e) + } + + } + + override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey { + return Factory.createPubkey(version, stream, + createPublicKey(privateSigningKey), + createPublicKey(privateEncryptionKey), + nonceTrialsPerByte, extraBytes, *features) + } + + override fun keyToBigInt(privateKey: ByteArray): BigInteger { + return BigInteger(1, privateKey) + } + + override fun randomNonce(): Long { + return RANDOM.nextLong() + } + + companion object { + protected val LOG = LoggerFactory.getLogger(Cryptography::class.java) + private val RANDOM = SecureRandom() + private val TWO = BigInteger.valueOf(2) + private val TWO_POW_64 = TWO.pow(64) + private val TWO_POW_16 = TWO.pow(16) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java deleted file mode 100644 index 6184ff9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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.ports; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Strings; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static ch.dissem.bitmessage.utils.SqlStrings.join; - -public abstract class AbstractMessageRepository implements MessageRepository, InternalContext.ContextHolder { - protected InternalContext ctx; - - @Override - public void setContext(InternalContext context) { - this.ctx = context; - } - - /** - * @deprecated use {@link #saveContactIfNecessary(BitmessageAddress)} instead. - */ - @Deprecated - protected void safeSenderIfNecessary(Plaintext message) { - if (message.getId() == null) { - saveContactIfNecessary(message.getFrom()); - } - } - - protected void saveContactIfNecessary(BitmessageAddress contact) { - if (contact != null) { - BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(contact.getAddress()); - if (savedAddress == null) { - ctx.getAddressRepository().save(contact); - } else if (savedAddress.getPubkey() == null && contact.getPubkey() != null) { - savedAddress.setPubkey(contact.getPubkey()); - ctx.getAddressRepository().save(savedAddress); - } - if (savedAddress != null) { - contact.setAlias(savedAddress.getAlias()); - } - } - } - - @Override - public Plaintext getMessage(Object id) { - if (id instanceof Long) { - return single(find("id=" + id)); - } else { - throw new IllegalArgumentException("Long expected for ID"); - } - } - - @Override - public Plaintext getMessage(InventoryVector iv) { - return single(find("iv=X'" + Strings.hex(iv.getHash()) + "'")); - } - - @Override - public Plaintext getMessage(byte[] initialHash) { - return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'")); - } - - @Override - public Plaintext getMessageForAck(byte[] ackData) { - return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'")); - } - - @Override - public List<Plaintext> findMessages(Label label) { - if (label == null) { - return find("id NOT IN (SELECT message_id FROM Message_Label)"); - } else { - return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"); - } - } - - @Override - public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { - return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'"); - } - - @Override - public List<Plaintext> findMessages(Plaintext.Status status) { - return find("status='" + status.name() + "'"); - } - - @Override - public List<Plaintext> findMessages(BitmessageAddress sender) { - return find("sender='" + sender.getAddress() + "'"); - } - - @Override - public List<Plaintext> findMessagesToResend() { - return find("status='" + Plaintext.Status.SENT.name() + "'" + - " AND next_try < " + UnixTime.now()); - } - - @Override - public List<Plaintext> findResponses(Plaintext parent) { - if (parent.getInventoryVector() == null) { - return Collections.emptyList(); - } - return find("iv IN (SELECT child FROM Message_Parent" - + " WHERE parent=X'" + Strings.hex(parent.getInventoryVector().getHash()) + "')"); - } - - @Override - public List<Plaintext> getConversation(UUID conversationId) { - return find("conversation=X'" + conversationId.toString().replace("-", "") + "'"); - } - - @Override - public List<Label> getLabels() { - return findLabels("1=1"); - } - - @Override - public List<Label> getLabels(Label.Type... types) { - return findLabels("type IN (" + join(types) + ")"); - } - - protected abstract List<Label> findLabels(String where); - - - protected <T> T single(Collection<T> collection) { - switch (collection.size()) { - case 0: - return null; - case 1: - return collection.iterator().next(); - default: - throw new ApplicationException("This shouldn't happen, found " + collection.size() + - " items, one or none was expected"); - } - } - - protected abstract List<Plaintext> find(String where); -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt new file mode 100644 index 0000000..b7329eb --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.SqlStrings.join +import ch.dissem.bitmessage.utils.Strings +import ch.dissem.bitmessage.utils.UnixTime +import java.util.* + +abstract class AbstractMessageRepository : MessageRepository { + protected var ctx by InternalContext + + protected fun saveContactIfNecessary(contact: BitmessageAddress?) { + contact?.let { + val savedAddress = ctx.addressRepository.getAddress(contact.address) + if (savedAddress == null) { + ctx.addressRepository.save(contact) + } else if (savedAddress.pubkey == null && contact.pubkey != null) { + savedAddress.pubkey = contact.pubkey + ctx.addressRepository.save(savedAddress) + } + if (savedAddress != null) { + contact.alias = savedAddress.alias + } + } + } + + override fun getMessage(id: Any): Plaintext { + if (id is Long) { + return single(find("id=" + id)) ?: throw IllegalArgumentException("There is no message with id $id") + } else { + throw IllegalArgumentException("Long expected for ID") + } + } + + override fun getMessage(iv: InventoryVector): Plaintext? { + return single(find("iv=X'" + Strings.hex(iv.hash) + "'")) + } + + override fun getMessage(initialHash: ByteArray): Plaintext? { + return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'")) + } + + override fun getMessageForAck(ackData: ByteArray): Plaintext? { + return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'")) + } + + override fun findMessages(label: Label?): List<Plaintext> { + if (label == null) { + return find("id NOT IN (SELECT message_id FROM Message_Label)") + } else { + return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")") + } + } + + override fun findMessages(status: Plaintext.Status, recipient: BitmessageAddress): List<Plaintext> { + return find("status='" + status.name + "' AND recipient='" + recipient.address + "'") + } + + override fun findMessages(status: Plaintext.Status): List<Plaintext> { + return find("status='" + status.name + "'") + } + + override fun findMessages(sender: BitmessageAddress): List<Plaintext> { + return find("sender='" + sender.address + "'") + } + + override fun findMessagesToResend(): List<Plaintext> { + return find("status='" + Plaintext.Status.SENT.name + "'" + + " AND next_try < " + UnixTime.now) + } + + override fun findResponses(parent: Plaintext): List<Plaintext> { + if (parent.inventoryVector == null) { + return emptyList() + } + return find("iv IN (SELECT child FROM Message_Parent" + + " WHERE parent=X'" + Strings.hex(parent.inventoryVector!!.hash) + "')") + } + + override fun getConversation(conversationId: UUID): List<Plaintext> { + return find("conversation=X'" + conversationId.toString().replace("-", "") + "'") + } + + override fun getLabels(): List<Label> { + return findLabels("1=1") + } + + override fun getLabels(vararg types: Label.Type): List<Label> { + return findLabels("type IN (" + join(*types) + ")") + } + + protected abstract fun findLabels(where: String): List<Label> + + + protected fun <T> single(collection: Collection<T>): T? { + when (collection.size) { + 0 -> return null + 1 -> return collection.iterator().next() + else -> throw ApplicationException("This shouldn't happen, found " + collection.size + + " items, one or none was expected") + } + } + + protected abstract fun find(where: String): List<Plaintext> +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt similarity index 63% rename from core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java rename to core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt index 5247730..811785a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,52 +14,51 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.BitmessageAddress -import java.util.List; - -public interface AddressRepository { +interface AddressRepository { /** * Returns a matching BitmessageAddress if there is one with the given ripe or tag, that * has no public key yet. If it doesn't exist or already has a public key, null is returned. - * + * @param ripeOrTag Either ripe or tag (depending of address version) of an address with - * missing public key. + * * missing public key. + * * * @return the matching address if there is one without public key, or null otherwise. */ - BitmessageAddress findContact(byte[] ripeOrTag); + fun findContact(ripeOrTag: ByteArray): BitmessageAddress? - BitmessageAddress findIdentity(byte[] ripeOrTag); + fun findIdentity(ripeOrTag: ByteArray): BitmessageAddress? /** * @return all Bitmessage addresses that belong to this user, i.e. have a private key. */ - List<BitmessageAddress> getIdentities(); + fun getIdentities(): List<BitmessageAddress> /** * @return all subscribed chans. */ - List<BitmessageAddress> getChans(); + fun getChans(): List<BitmessageAddress> - List<BitmessageAddress> getSubscriptions(); + fun getSubscriptions(): List<BitmessageAddress> - List<BitmessageAddress> getSubscriptions(long broadcastVersion); + fun getSubscriptions(broadcastVersion: Long): List<BitmessageAddress> /** * @return all Bitmessage addresses that have no private key or are chans. */ - List<BitmessageAddress> getContacts(); + fun getContacts(): List<BitmessageAddress> /** - * Implementations must not delete cryptographic keys if they're not provided by <code>address</code>. - * + * Implementations must not delete cryptographic keys if they're not provided by `address`. + * @param address to save or update */ - void save(BitmessageAddress address); + fun save(address: BitmessageAddress) - void remove(BitmessageAddress address); + fun remove(address: BitmessageAddress) - BitmessageAddress getAddress(String address); + fun getAddress(address: String): BitmessageAddress? } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt similarity index 65% rename from core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java rename to core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt index 9ea6a9d..22176bb 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,212 +11,253 @@ * 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. + * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.SecureRandom; +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import java.math.BigInteger +import java.security.MessageDigest +import java.security.SecureRandom /** - * Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom}, + * Provides some methods to help with hashing and encryption. All randoms are created using [SecureRandom], * which should be secure enough. */ -public interface Cryptography { +interface Cryptography { /** - * A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at + * A helper method to calculate SHA-512 hashes. Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in * success on the same thread. - * + * @param data to get hashed + * * * @param offset of the data to be hashed + * * * @param length of the data to be hashed + * * * @return SHA-512 hash of data within the given range */ - byte[] sha512(byte[] data, int offset, int length); + fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray /** - * A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at + * A helper method to calculate SHA-512 hashes. Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in * success on the same thread. - * + * @param data to get hashed + * * * @return SHA-512 hash of data */ - byte[] sha512(byte[]... data); + fun sha512(vararg data: ByteArray): ByteArray /** - * A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created + * A helper method to calculate doubleSHA-512 hashes. Please note that a new [MessageDigest] object is created * at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in * success on the same thread. - * + * @param data to get hashed + * * * @return SHA-512 hash of data */ - byte[] doubleSha512(byte[]... data); + fun doubleSha512(vararg data: ByteArray): ByteArray /** * A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes * to use for the hash calculation. - * <p> - * Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you - * shouldn't use this if you need to do many hash calculations in short order on the same thread. - * </p> * + * + * Please note that a new [MessageDigest] object is created at each call (to ensure thread safety), so you + * shouldn't use this if you need to do many hash calculations in short order on the same thread. + * + * @param data to get hashed + * * * @param length number of bytes to be taken into account + * * * @return SHA-512 hash of data */ - byte[] doubleSha512(byte[] data, int length); + fun doubleSha512(data: ByteArray, length: Int): ByteArray /** * A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a * concatenation of all arrays, but might perform better. - * <p> - * Please note that a new {@link MessageDigest} object is created at + * + * + * Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short * order on the same thread. - * </p> * + * @param data to get hashed + * * * @return RIPEMD-160 hash of data */ - byte[] ripemd160(byte[]... data); + fun ripemd160(vararg data: ByteArray): ByteArray /** * A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes * to use for the hash calculation. - * <p> - * Please note that a new {@link MessageDigest} object is created at + * + * + * Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short * order on the same thread. - * </p> * + * @param data to get hashed + * * * @param length number of bytes to be taken into account + * * * @return SHA-256 hash of data */ - byte[] doubleSha256(byte[] data, int length); + fun doubleSha256(data: ByteArray, length: Int): ByteArray /** * A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a * concatenation of all arrays, but might perform better. - * <p> - * Please note that a new {@link MessageDigest} object is created at + * + * + * Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short * order on the same thread. - * </p> * + * @param data to get hashed + * * * @return SHA hash of data */ - byte[] sha1(byte[]... data); + fun sha1(vararg data: ByteArray): ByteArray /** * @param length number of bytes to return + * * * @return an array of the given size containing random bytes */ - byte[] randomBytes(int length); + fun randomBytes(length: Int): ByteArray /** * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to * live. - * + * @param object to do the proof of work for + * * * @param nonceTrialsPerByte difficulty + * * * @param extraBytes bytes to add to the object size (makes it more difficult to send small messages) + * * * @param callback to handle nonce once it's calculated */ - void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, - long extraBytes, ProofOfWorkEngine.Callback callback); + fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, + extraBytes: Long, callback: ProofOfWorkEngine.Callback) /** * @param object to be checked + * * * @param nonceTrialsPerByte difficulty + * * * @param extraBytes bytes to add to the object size + * * * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages) */ - void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) - throws IOException; + @Throws(InsufficientProofOfWorkException::class) + fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) - byte[] getInitialHash(ObjectMessage object); + fun getInitialHash(`object`: ObjectMessage): ByteArray - byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes); + fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray /** * Calculates the MAC for a message (data) - * + * @param key_m the symmetric key used + * * * @param data the message data to calculate the MAC for + * * * @return the MAC */ - byte[] mac(byte[] key_m, byte[] data); + fun mac(key_m: ByteArray, data: ByteArray): ByteArray /** * @param encrypt if true, encrypts data, otherwise tries to decrypt it. + * * * @param data + * * * @param key_e + * * * @return */ - byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector); + fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray /** * Create a new public key fom given private keys. - * + * @param version of the public key / address + * * * @param stream of the address + * * * @param privateSigningKey private key used for signing + * * * @param privateEncryptionKey private key used for encryption + * * * @param nonceTrialsPerByte proof of work difficulty + * * * @param extraBytes bytes to add for the proof of work (make it harder for small messages) + * * * @param features of the address + * * * @return a public key object */ - Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey, - long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features); + fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey /** * @param privateKey private key as byte array + * * * @return a public key corresponding to the given private key */ - byte[] createPublicKey(byte[] privateKey); + fun createPublicKey(privateKey: ByteArray): ByteArray /** * @param privateKey private key as byte array + * * * @return a big integer representation (unsigned) of the given bytes */ - BigInteger keyToBigInt(byte[] privateKey); + fun keyToBigInt(privateKey: ByteArray): BigInteger /** * @param data to check + * * * @param signature the signature of the message + * * * @param pubkey the sender's public key + * * * @return true if the signature is valid, false otherwise */ - boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey); + fun isSignatureValid(data: ByteArray, signature: ByteArray, pubkey: Pubkey): Boolean /** * Calculate the signature of data, using the given private key. - * + * @param data to be signed + * * * @param privateKey to be used for signing + * * * @return the signature */ - byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey); + fun getSignature(data: ByteArray, privateKey: ch.dissem.bitmessage.entity.valueobject.PrivateKey): ByteArray /** * @return a random number of type long */ - long randomNonce(); + fun randomNonce(): Long - byte[] multiply(byte[] k, byte[] r); + fun multiply(k: ByteArray, r: ByteArray): ByteArray - byte[] createPoint(byte[] x, byte[] y); + fun createPoint(x: ByteArray, y: ByteArray): ByteArray } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java b/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt similarity index 66% rename from core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java rename to core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt index 4c73fe3..3f8b63b 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,14 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import org.junit.BeforeClass; +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.MessagePayload /** * @author Christian Basler */ -public class TestBase { - @BeforeClass - public static void setUpClass() { - Singleton.initialize(new BouncyCryptography()); - } +interface CustomCommandHandler { + fun handle(request: CustomMessage): MessagePayload? } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java deleted file mode 100644 index e4c2105..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.ports; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.Label; - -import static ch.dissem.bitmessage.entity.Plaintext.Status.*; - -public class DefaultLabeler implements Labeler, InternalContext.ContextHolder { - private InternalContext ctx; - - @Override - public void setLabels(Plaintext msg) { - msg.setStatus(RECEIVED); - if (msg.getType() == Plaintext.Type.BROADCAST) { - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD)); - } else { - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); - } - } - - @Override - public void markAsDraft(Plaintext msg) { - msg.setStatus(DRAFT); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.DRAFT)); - } - - @Override - public void markAsSending(Plaintext msg) { - if (msg.getTo() != null && msg.getTo().getPubkey() == null) { - msg.setStatus(PUBKEY_REQUESTED); - } else { - msg.setStatus(DOING_PROOF_OF_WORK); - } - msg.removeLabel(Label.Type.DRAFT); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX)); - } - - @Override - public void markAsSent(Plaintext msg) { - msg.setStatus(SENT); - msg.removeLabel(Label.Type.OUTBOX); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT)); - } - - @Override - public void markAsAcknowledged(Plaintext msg) { - msg.setStatus(SENT_ACKNOWLEDGED); - } - - @Override - public void markAsRead(Plaintext msg) { - msg.removeLabel(Label.Type.UNREAD); - } - - @Override - public void markAsUnread(Plaintext msg) { - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.UNREAD)); - } - - @Override - public void delete(Plaintext msg) { - msg.getLabels().clear(); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.TRASH)); - } - - @Override - public void archive(Plaintext msg) { - msg.getLabels().clear(); - } - - @Override - public void setContext(InternalContext ctx) { - this.ctx = ctx; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt new file mode 100644 index 0000000..63148b7 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.* +import ch.dissem.bitmessage.entity.valueobject.Label + +open class DefaultLabeler : Labeler { + private var ctx by InternalContext + + override fun setLabels(msg: Plaintext) { + msg.status = RECEIVED + if (msg.type === Plaintext.Type.BROADCAST) { + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD)) + } else { + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)) + } + } + + override fun markAsDraft(msg: Plaintext) { + msg.status = DRAFT + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.DRAFT)) + } + + override fun markAsSending(msg: Plaintext) { + if (msg.to != null && msg.to!!.pubkey == null) { + msg.status = PUBKEY_REQUESTED + } else { + msg.status = DOING_PROOF_OF_WORK + } + msg.removeLabel(Label.Type.DRAFT) + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.OUTBOX)) + } + + override fun markAsSent(msg: Plaintext) { + msg.status = SENT + msg.removeLabel(Label.Type.OUTBOX) + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.SENT)) + } + + override fun markAsAcknowledged(msg: Plaintext) { + msg.status = SENT_ACKNOWLEDGED + } + + override fun markAsRead(msg: Plaintext) { + msg.removeLabel(Label.Type.UNREAD) + } + + override fun markAsUnread(msg: Plaintext) { + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.UNREAD)) + } + + override fun delete(msg: Plaintext) { + msg.labels.clear() + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.TRASH)) + } + + override fun archive(msg: Plaintext) { + msg.labels.clear() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt similarity index 65% rename from core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java rename to core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt index 6af65c1..2087354 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,44 +14,42 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; - -import java.util.List; +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.valueobject.InventoryVector /** * The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing. */ -public interface Inventory { +interface Inventory { /** * Returns the IVs of all valid objects we have for the given streams */ - List<InventoryVector> getInventory(long... streams); + fun getInventory(vararg streams: Long): List<InventoryVector> /** * Returns the IVs of all objects in the offer that we don't have already. Implementations are allowed to * ignore the streams parameter, but it must be set when calling this method. */ - List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams); + fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector> - ObjectMessage getObject(InventoryVector vector); + fun getObject(vector: InventoryVector): ObjectMessage? /** * This method is mainly used to search for public keys to newly added addresses or broadcasts from new * subscriptions. */ - List<ObjectMessage> getObjects(long stream, long version, ObjectType... types); + fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> - void storeObject(ObjectMessage object); + fun storeObject(`object`: ObjectMessage) - boolean contains(ObjectMessage object); + operator fun contains(`object`: ObjectMessage): Boolean /** * Deletes all objects that expired 5 minutes ago or earlier * (so we don't accidentally request objects we just deleted) */ - void cleanup(); + fun cleanup() } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt similarity index 56% rename from core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java rename to core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt index e79bee2..35ddf16 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,44 +14,43 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.Plaintext /** * Defines and sets labels. Note that it should also update the status field of a message. - * Generally it's highly advised to override the {@link DefaultLabeler} whenever possible, + * Generally it's highly advised to override the [DefaultLabeler] whenever possible, * instead of directly implementing the interface. - * <p> + * * As the labeler gets called whenever the state of a message changes, it can also be used * as a listener. - * </p> */ -public interface Labeler { +interface Labeler { /** * Sets the labels of a newly received message. * * @param msg an unlabeled message or broadcast */ - void setLabels(Plaintext msg); + fun setLabels(msg: Plaintext) - void markAsDraft(Plaintext msg); + fun markAsDraft(msg: Plaintext) /** - * It is paramount that this methods marks the {@link Plaintext} object with status - * {@link Plaintext.Status#PUBKEY_REQUESTED} (see {@link DefaultLabeler}) + * It is paramount that this methods marks the [Plaintext] object with status + * [Plaintext.Status.PUBKEY_REQUESTED] (see [DefaultLabeler]) */ - void markAsSending(Plaintext msg); + fun markAsSending(msg: Plaintext) - void markAsSent(Plaintext msg); + fun markAsSent(msg: Plaintext) - void markAsAcknowledged(Plaintext msg); + fun markAsAcknowledged(msg: Plaintext) - void markAsRead(Plaintext msg); + fun markAsRead(msg: Plaintext) - void markAsUnread(Plaintext msg); + fun markAsUnread(msg: Plaintext) - void delete(Plaintext msg); + fun delete(msg: Plaintext) - void archive(Plaintext msg); + fun archive(msg: Plaintext) } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java deleted file mode 100644 index ba34cfe..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015 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.ports; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.Plaintext.Status; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; - -import java.util.Collection; -import java.util.List; -import java.util.UUID; - -public interface MessageRepository { - List<Label> getLabels(); - - List<Label> getLabels(Label.Type... types); - - int countUnread(Label label); - - Plaintext getMessage(Object id); - - Plaintext getMessage(InventoryVector iv); - - Plaintext getMessage(byte[] initialHash); - - Plaintext getMessageForAck(byte[] ackData); - - /** - * @param label to search for - * @return a distinct list of all conversations that have at least one message with the given label. - */ - List<UUID> findConversations(Label label); - - List<Plaintext> findMessages(Label label); - - List<Plaintext> findMessages(Status status); - - List<Plaintext> findMessages(Status status, BitmessageAddress recipient); - - List<Plaintext> findMessages(BitmessageAddress sender); - - List<Plaintext> findResponses(Plaintext parent); - - List<Plaintext> findMessagesToResend(); - - void save(Plaintext message); - - void remove(Plaintext message); - - /** - * Returns all messages with this conversation ID. The returned messages aren't sorted in any way, - * so you may prefer to use {@link ch.dissem.bitmessage.utils.ConversationService#getConversation(UUID)} - * instead. - * - * @param conversationId ID of the requested conversation - * @return all messages with the given conversation ID - */ - Collection<Plaintext> getConversation(UUID conversationId); -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt new file mode 100644 index 0000000..c1fd59a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import java.util.* + +interface MessageRepository { + fun getLabels(): List<Label> + + fun getLabels(vararg types: Label.Type): List<Label> + + fun countUnread(label: Label): Int + + fun getMessage(id: Any): Plaintext + + fun getMessage(iv: InventoryVector): Plaintext? + + fun getMessage(initialHash: ByteArray): Plaintext? + + fun getMessageForAck(ackData: ByteArray): Plaintext? + + /** + * @param label to search for + * * + * @return a distinct list of all conversations that have at least one message with the given label. + */ + fun findConversations(label: Label): List<UUID> + + fun findMessages(label: Label?): List<Plaintext> + + fun findMessages(status: Status): List<Plaintext> + + fun findMessages(status: Status, recipient: BitmessageAddress): List<Plaintext> + + fun findMessages(sender: BitmessageAddress): List<Plaintext> + + fun findResponses(parent: Plaintext): List<Plaintext> + + fun findMessagesToResend(): List<Plaintext> + + fun save(message: Plaintext) + + fun remove(message: Plaintext) + + /** + * Returns all messages with this conversation ID. The returned messages aren't sorted in any way, + * so you may prefer to use [ch.dissem.bitmessage.utils.ConversationService.getConversation] + * instead. + + * @param conversationId ID of the requested conversation + * * + * @return all messages with the given conversation ID + */ + fun getConversation(conversationId: UUID): Collection<Plaintext> +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java deleted file mode 100644 index fb45e50..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2015 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.ports; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Bytes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -import static ch.dissem.bitmessage.utils.Bytes.inc; -import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; - -/** - * A POW engine using all available CPU cores. - */ -public class MultiThreadedPOWEngine implements ProofOfWorkEngine { - private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class); - private final ExecutorService waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build()); - private final ExecutorService workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build()); - - /** - * This method will block until all pending nonce calculations are done, but not wait for its own calculation - * to finish. - * (This implementation becomes very inefficient if multiple nonce are calculated at the same time.) - * - * @param initialHash the SHA-512 hash of the object to send, sans nonce - * @param target the target, representing an unsigned long - * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make - */ - @Override - public void calculateNonce(final byte[] initialHash, final byte[] target, final Callback callback) { - waiterPool.execute(new Runnable() { - @Override - public void run() { - long startTime = System.currentTimeMillis(); - - int cores = Runtime.getRuntime().availableProcessors(); - if (cores > 255) cores = 255; - LOG.info("Doing POW using " + cores + " cores"); - List<Worker> workers = new ArrayList<>(cores); - for (int i = 0; i < cores; i++) { - Worker w = new Worker((byte) cores, i, initialHash, target); - workers.add(w); - } - List<Future<byte[]>> futures = new ArrayList<>(cores); - for (Worker w : workers) { - // Doing this in the previous loop might cause a ConcurrentModificationException in the worker - // if a worker finds a nonce while new ones are still being added. - futures.add(workerPool.submit(w)); - } - try { - while (!Thread.interrupted()) { - for (Future<byte[]> future : futures) { - if (future.isDone()) { - callback.onNonceCalculated(initialHash, future.get()); - LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds"); - for (Future<byte[]> f : futures) { - f.cancel(true); - } - return; - } - } - Thread.sleep(100); - } - LOG.error("POW waiter thread interrupted - this should not happen!"); - } catch (ExecutionException e) { - LOG.error(e.getMessage(), e); - } catch (InterruptedException e) { - LOG.error("POW waiter thread interrupted - this should not happen!", e); - } - } - }); - } - - private class Worker implements Callable<byte[]> { - private final byte numberOfCores; - private final byte[] initialHash; - private final byte[] target; - private final MessageDigest mda; - private final byte[] nonce = new byte[8]; - - Worker(byte numberOfCores, int core, byte[] initialHash, byte[] target) { - this.numberOfCores = numberOfCores; - this.initialHash = initialHash; - this.target = target; - this.nonce[7] = (byte) core; - try { - mda = MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public byte[] call() throws Exception { - do { - inc(nonce, numberOfCores); - mda.update(nonce); - mda.update(initialHash); - if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) { - return nonce; - } - } while (!Thread.interrupted()); - return null; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt new file mode 100644 index 0000000..2f2c8b8 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Bytes.inc +import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool +import org.slf4j.LoggerFactory +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.* +import java.util.concurrent.Callable +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.Future + +/** + * A POW engine using all available CPU cores. + */ +class MultiThreadedPOWEngine : ProofOfWorkEngine { + private val waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build()) + private val workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build()) + + /** + * This method will block until all pending nonce calculations are done, but not wait for its own calculation + * to finish. + * (This implementation becomes very inefficient if multiple nonce are calculated at the same time.) + + * @param initialHash the SHA-512 hash of the object to send, sans nonce + * * + * @param target the target, representing an unsigned long + * * + * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make + */ + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + waiterPool.execute({ + val startTime = System.currentTimeMillis() + + var cores = Runtime.getRuntime().availableProcessors() + if (cores > 255) cores = 255 + LOG.info("Doing POW using $cores cores") + val workers = ArrayList<Worker>(cores) + for (i in 0..cores - 1) { + val w = Worker(cores.toByte(), i, initialHash, target) + workers.add(w) + } + val futures = ArrayList<Future<ByteArray>>(cores) + // Doing this in the previous loop might cause a ConcurrentModificationException in the worker + // if a worker finds a nonce while new ones are still being added. + workers.mapTo(futures) { workerPool.submit(it) } + try { + while (!Thread.interrupted()) { + futures.firstOrNull { it.isDone }?.let { + callback.onNonceCalculated(initialHash, it.get()) + LOG.info("Nonce calculated in " + (System.currentTimeMillis() - startTime) / 1000 + " seconds") + futures.forEach { it.cancel(true) } + return@execute + } + } + LOG.error("POW waiter thread interrupted - this should not happen!") + } catch (e: ExecutionException) { + LOG.error(e.message, e) + } catch (e: InterruptedException) { + LOG.error("POW waiter thread interrupted - this should not happen!", e) + } + }) + } + + private inner class Worker internal constructor( + private val numberOfCores: Byte, core: Int, + private val initialHash: ByteArray, + private val target: ByteArray + ) : Callable<ByteArray> { + private val mda: MessageDigest + private val nonce = ByteArray(8) + + init { + this.nonce[7] = core.toByte() + try { + mda = MessageDigest.getInstance("SHA-512") + } catch (e: NoSuchAlgorithmException) { + LOG.error(e.message, e) + throw ApplicationException(e) + } + + } + + override fun call(): ByteArray? { + do { + inc(nonce, numberOfCores) + mda.update(nonce) + mda.update(initialHash) + if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) { + return nonce + } + } while (!Thread.interrupted()) + return null + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt similarity index 58% rename from core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java rename to core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt index e9c164e..72fee7b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,73 +14,73 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.utils.Property; +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Property -import java.io.IOException; -import java.net.InetAddress; -import java.util.Collection; -import java.util.concurrent.Future; +import java.io.IOException +import java.net.InetAddress +import java.util.concurrent.Future /** * Handles incoming messages */ -public interface NetworkHandler { - int NETWORK_MAGIC_NUMBER = 8; - int HEADER_SIZE = 24; - int MAX_PAYLOAD_SIZE = 1600003; - int MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE; +interface NetworkHandler { /** * Connects to the trusted host, fetches and offers new messages and disconnects afterwards. - * <p> + * + * * An implementation should disconnect if either the timeout is reached or the returned thread is interrupted. - * </p> + * */ - Future<?> synchronize(InetAddress server, int port, long timeoutInSeconds); + fun synchronize(server: InetAddress, port: Int, timeoutInSeconds: Long): Future<*> /** * Send a custom message to a specific node (that should implement handling for this message type) and returns - * the response, which in turn is expected to be a {@link CustomMessage}. - * + * the response, which in turn is expected to be a [CustomMessage]. + * @param server the node's address + * * * @param port the node's port + * * * @param request the request + * * * @return the response */ - CustomMessage send(InetAddress server, int port, CustomMessage request); + fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage /** * Start a full network node, accepting incoming connections and relaying objects. */ - void start(); + fun start() /** * Stop the full network node. */ - void stop(); + fun stop() /** * Offer new objects to up to 8 random nodes. */ - void offer(InventoryVector iv); + fun offer(iv: InventoryVector) /** * Request each of those objects from a node that knows of the requested object. - * + * @param inventoryVectors of the objects to be requested */ - void request(Collection<InventoryVector> inventoryVectors); + fun request(inventoryVectors: Collection<InventoryVector>) - Property getNetworkStatus(); + val networkStatus: Property - boolean isRunning(); + val isRunning: Boolean interface MessageListener { - void receive(ObjectMessage object) throws IOException; + @Throws(IOException::class) + fun receive(`object`: ObjectMessage) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt similarity index 69% rename from core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java rename to core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt index ad5219b..59e3567 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,21 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; - -import java.util.List; +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress /** * Stores and provides known peers. */ -public interface NodeRegistry { +interface NodeRegistry { /** * Removes all known nodes from registry. This should work around connection issues * when there are many invalid nodes in the registry. */ - void clear(); + fun clear() - List<NetworkAddress> getKnownAddresses(int limit, long... streams); + fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> - void offerAddresses(List<NetworkAddress> addresses); + fun offerAddresses(addresses: List<NetworkAddress>) } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java deleted file mode 100644 index 63d70eb..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java +++ /dev/null @@ -1,54 +0,0 @@ -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.ApplicationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.InetAddress; -import java.util.*; - -/** - * Helper class to kick start node registries. - */ -public class NodeRegistryHelper { - private static final Logger LOG = LoggerFactory.getLogger(NodeRegistryHelper.class); - - public static Map<Long, Set<NetworkAddress>> loadStableNodes() { - try (InputStream in = NodeRegistryHelper.class.getClassLoader().getResourceAsStream("nodes.txt")) { - Scanner scanner = new Scanner(in); - long stream = 0; - Map<Long, Set<NetworkAddress>> result = new HashMap<>(); - Set<NetworkAddress> streamSet = null; - while (scanner.hasNext()) { - try { - String line = scanner.nextLine().trim(); - if (line.startsWith("[stream")) { - stream = Long.parseLong(line.substring(8, line.lastIndexOf(']'))); - streamSet = new HashSet<>(); - result.put(stream, streamSet); - } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) { - int portIndex = line.lastIndexOf(':'); - InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex)); - int port = Integer.valueOf(line.substring(portIndex + 1)); - for (InetAddress inetAddress : inetAddresses) { - streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build()); - } - } - } catch (IOException e) { - LOG.warn(e.getMessage(), e); - } - } - if (LOG.isDebugEnabled()) { - for (Map.Entry<Long, Set<NetworkAddress>> e : result.entrySet()) { - LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes."); - } - } - return result; - } catch (IOException e) { - throw new ApplicationException(e); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt new file mode 100644 index 0000000..1b947dd --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import org.slf4j.LoggerFactory +import java.io.IOException +import java.net.InetAddress +import java.util.* + +/** + * Helper class to kick start node registries. + */ +object NodeRegistryHelper { + private val LOG = LoggerFactory.getLogger(NodeRegistryHelper::class.java) + + @JvmStatic + fun loadStableNodes(): Map<Long, Set<NetworkAddress>> { + javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` -> + val scanner = Scanner(`in`) + var stream: Long = 0 + val result = HashMap<Long, Set<NetworkAddress>>() + var streamSet: MutableSet<NetworkAddress>? = null + while (scanner.hasNext()) { + try { + val line = scanner.nextLine().trim { it <= ' ' } + if (line.startsWith("[stream")) { + stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']'))) + streamSet = HashSet<NetworkAddress>() + result.put(stream, streamSet) + } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) { + val portIndex = line.lastIndexOf(':') + val inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex)) + val port = Integer.valueOf(line.substring(portIndex + 1))!! + for (inetAddress in inetAddresses) { + streamSet.add(NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build()) + } + } + } catch (e: IOException) { + LOG.warn(e.message, e) + } + } + if (LOG.isDebugEnabled) { + for ((key, value) in result) { + LOG.debug("Stream " + key + ": loaded " + value.size + " bootstrap nodes.") + } + } + return result + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt similarity index 77% rename from core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java rename to core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt index fc7b4c2..ce76694 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,27 +14,29 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports /** * Does the proof of work necessary to send an object. */ -public interface ProofOfWorkEngine { +interface ProofOfWorkEngine { /** * Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long * smaller than target. - * + * @param initialHash the SHA-512 hash of the object to send, sans nonce + * * * @param target the target, representing an unsigned long + * * * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make - * sure this is only called once. + * * sure this is only called once. */ - void calculateNonce(byte[] initialHash, byte[] target, Callback callback); + fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback) interface Callback { /** * @param nonce 8 bytes nonce */ - void onNonceCalculated(byte[] initialHash, byte[] nonce); + fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java deleted file mode 100644 index b27a05d..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; - -import java.util.List; - -/** - * Objects that proof of work is currently being done for. - * - * @author Christian Basler - */ -public interface ProofOfWorkRepository { - Item getItem(byte[] initialHash); - - List<byte[]> getItems(); - - void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes); - - void putObject(Item item); - - void removeObject(byte[] initialHash); - - class Item { - public final ObjectMessage object; - public final long nonceTrialsPerByte; - public final long extraBytes; - - // Needed for ACK POW calculation - public final Long expirationTime; - public final Plaintext message; - - public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - this(object, nonceTrialsPerByte, extraBytes, 0, null); - } - - public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) { - this.object = object; - this.nonceTrialsPerByte = nonceTrialsPerByte; - this.extraBytes = extraBytes; - this.expirationTime = expirationTime; - this.message = message; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt new file mode 100644 index 0000000..fbf5f60 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext + +/** + * Objects that proof of work is currently being done for. + + * @author Christian Basler + */ +interface ProofOfWorkRepository { + fun getItem(initialHash: ByteArray): Item + + fun getItems(): List<ByteArray> + + fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) + + fun putObject(item: Item) + + fun removeObject(initialHash: ByteArray) + + data class Item @JvmOverloads constructor( + val `object`: ObjectMessage, + val nonceTrialsPerByte: Long, + val extraBytes: Long, + // Needed for ACK POW calculation + val expirationTime: Long? = 0, + val message: Plaintext? = null + ) +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java deleted file mode 100644 index a7d0d57..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015 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.ports; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Bytes; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import static ch.dissem.bitmessage.utils.Bytes.inc; - -/** - * You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one. - * <p> - * <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's - * another reason not to use this one. - * </p> - */ -public class SimplePOWEngine implements ProofOfWorkEngine { - @Override - public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) { - try { - MessageDigest mda = MessageDigest.getInstance("SHA-512"); - byte[] nonce = new byte[8]; - do { - inc(nonce); - mda.update(nonce); - mda.update(initialHash); - } while (Bytes.lt(target, mda.digest(mda.digest()), 8)); - callback.onNonceCalculated(initialHash, nonce); - } catch (NoSuchAlgorithmException e) { - throw new ApplicationException(e); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt new file mode 100644 index 0000000..3d7c6d3 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.utils.Bytes +import java.security.MessageDigest + +/** + * You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one. + * + * **Warning:** implementations probably depend on POW being asynchronous, that's + * another reason not to use this one. + */ +class SimplePOWEngine : ProofOfWorkEngine { + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + val mda = MessageDigest.getInstance("SHA-512") + val nonce = ByteArray(8) + do { + Bytes.inc(nonce) + mda.update(nonce) + mda.update(initialHash) + } while (Bytes.lt(target, mda.digest(mda.digest()), 8)) + callback.onNonceCalculated(initialHash, nonce) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java b/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt similarity index 52% rename from core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java rename to core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt index 55c23c3..9da06e8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,48 +14,50 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils /** * Intended to count the bytes read or written during (de-)serialization. */ -public class AccessCounter { - private int count; - - /** - * Increases the counter by one, if not null. - */ - public static void inc(AccessCounter counter) { - if (counter != null) counter.inc(); - } - - /** - * Increases the counter by length, if not null. - */ - public static void inc(AccessCounter counter, int length) { - if (counter != null) counter.inc(length); - } +class AccessCounter { + private var count: Int = 0 /** * Increases the counter by one. */ - private void inc() { - count++; + private fun inc() { + count++ } /** * Increases the counter by length. */ - private void inc(int length) { - count += length; + private fun inc(length: Int) { + count += length } - public int length() { - return count; + fun length(): Int { + return count } - @Override - public String toString() { - return String.valueOf(count); + override fun toString(): String { + return count.toString() + } + + companion object { + + /** + * Increases the counter by one, if not null. + */ + @JvmStatic fun inc(counter: AccessCounter?) { + counter?.inc() + } + + /** + * Increases the counter by length, if not null. + */ + @JvmStatic fun inc(counter: AccessCounter?, length: Int) { + counter?.inc(length) + } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java deleted file mode 100644 index a67e344..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2011 Google Inc. - * Copyright 2015 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.exception.AddressFormatException; -import ch.dissem.bitmessage.exception.ApplicationException; - -import java.io.UnsupportedEncodingException; - -import static java.util.Arrays.copyOfRange; - -/** - * Base58 encoder and decoder. - * - * @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily. - */ -public class Base58 { - private static final int[] INDEXES = new int[128]; - private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - - static { - for (int i = 0; i < INDEXES.length; i++) { - INDEXES[i] = -1; - } - for (int i = 0; i < ALPHABET.length; i++) { - INDEXES[ALPHABET[i]] = i; - } - } - - /** - * Encodes the given bytes in base58. No checksum is appended. - * - * @param data to encode - * @return base58 encoded input - */ - public static String encode(byte[] data) { - if (data.length == 0) { - return ""; - } - final byte[] bytes = copyOfRange(data, 0, data.length); - // Count leading zeroes. - int zeroCount = 0; - while (zeroCount < bytes.length && bytes[zeroCount] == 0) { - ++zeroCount; - } - // The actual encoding. - byte[] temp = new byte[bytes.length * 2]; - int j = temp.length; - - int startAt = zeroCount; - while (startAt < bytes.length) { - byte mod = divmod58(bytes, startAt); - if (bytes[startAt] == 0) { - ++startAt; - } - temp[--j] = (byte) ALPHABET[mod]; - } - - // Strip extra '1' if there are some after decoding. - while (j < temp.length && temp[j] == ALPHABET[0]) { - ++j; - } - // Add as many leading '1' as there were leading zeros. - while (--zeroCount >= 0) { - temp[--j] = (byte) ALPHABET[0]; - } - - byte[] output = copyOfRange(temp, j, temp.length); - try { - return new String(output, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); // Cannot happen. - } - } - - public static byte[] decode(String input) throws AddressFormatException { - if (input.length() == 0) { - return new byte[0]; - } - byte[] input58 = new byte[input.length()]; - // Transform the String to a base58 byte sequence - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - - int digit58 = -1; - if (c < 128) { - digit58 = INDEXES[c]; - } - if (digit58 < 0) { - throw new AddressFormatException("Illegal character " + c + " at " + i); - } - - input58[i] = (byte) digit58; - } - // Count leading zeroes - int zeroCount = 0; - while (zeroCount < input58.length && input58[zeroCount] == 0) { - ++zeroCount; - } - // The encoding - byte[] temp = new byte[input.length()]; - int j = temp.length; - - int startAt = zeroCount; - while (startAt < input58.length) { - byte mod = divmod256(input58, startAt); - if (input58[startAt] == 0) { - ++startAt; - } - - temp[--j] = mod; - } - // Do no add extra leading zeroes, move j to first non null byte. - while (j < temp.length && temp[j] == 0) { - ++j; - } - return copyOfRange(temp, j - zeroCount, temp.length); - } - - // - // number -> number / 58, returns number % 58 - // - private static byte divmod58(byte[] number, int startAt) { - int remainder = 0; - for (int i = startAt; i < number.length; i++) { - int digit256 = (int) number[i] & 0xFF; - int temp = remainder * 256 + digit256; - - number[i] = (byte) (temp / 58); - - remainder = temp % 58; - } - - return (byte) remainder; - } - - // - // number -> number / 256, returns number % 256 - // - private static byte divmod256(byte[] number58, int startAt) { - int remainder = 0; - for (int i = startAt; i < number58.length; i++) { - int digit58 = (int) number58[i] & 0xFF; - int temp = remainder * 58 + digit58; - - number58[i] = (byte) (temp / 256); - - remainder = temp % 256; - } - - return (byte) remainder; - } -} \ No newline at end of file diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt new file mode 100644 index 0000000..7f98f93 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt @@ -0,0 +1,161 @@ +/* + * Copyright 2017 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.exception.AddressFormatException +import java.util.Arrays.copyOfRange + +/** + * Base58 encoder and decoder. + + * @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily. + */ +object Base58 { + private val INDEXES = IntArray(128) + private val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray() + + init { + for (i in INDEXES.indices) { + INDEXES[i] = -1 + } + for (i in ALPHABET.indices) { + INDEXES[ALPHABET[i].toInt()] = i + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + + * @param data to encode + * * + * @return base58 encoded input + */ + @JvmStatic fun encode(data: ByteArray): String { + if (data.isEmpty()) { + return "" + } + val bytes = copyOfRange(data, 0, data.size) + // Count leading zeroes. + var zeroCount = 0 + while (zeroCount < bytes.size && bytes[zeroCount].toInt() == 0) { + ++zeroCount + } + // The actual encoding. + val temp = ByteArray(bytes.size * 2) + var j = temp.size + + var startAt = zeroCount + while (startAt < bytes.size) { + val mod = divmod58(bytes, startAt) + if (bytes[startAt].toInt() == 0) { + ++startAt + } + temp[--j] = ALPHABET[mod.toInt()].toByte() + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.size && temp[j] == ALPHABET[0].toByte()) { + ++j + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = ALPHABET[0].toByte() + } + + val output = copyOfRange(temp, j, temp.size) + return String(output, Charsets.US_ASCII) + } + + @Throws(AddressFormatException::class) + @JvmStatic fun decode(input: String): ByteArray { + if (input.isEmpty()) { + return ByteArray(0) + } + val input58 = ByteArray(input.length) + // Transform the String to a base58 byte sequence + for (i in 0..input.length - 1) { + val c = input[i] + + var digit58 = -1 + if (c.toInt() < 128) { + digit58 = INDEXES[c.toInt()] + } + if (digit58 < 0) { + throw AddressFormatException("Illegal character $c at $i") + } + + input58[i] = digit58.toByte() + } + // Count leading zeroes + var zeroCount = 0 + while (zeroCount < input58.size && input58[zeroCount].toInt() == 0) { + ++zeroCount + } + // The encoding + val temp = ByteArray(input.length) + var j = temp.size + + var startAt = zeroCount + while (startAt < input58.size) { + val mod = divmod256(input58, startAt) + if (input58[startAt].toInt() == 0) { + ++startAt + } + + temp[--j] = mod + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.size && temp[j].toInt() == 0) { + ++j + } + return copyOfRange(temp, j - zeroCount, temp.size) + } + + // + // number -> number / 58, returns number % 58 + // + private fun divmod58(number: ByteArray, startAt: Int): Byte { + var remainder = 0 + for (i in startAt..number.size - 1) { + val digit256 = number[i].toInt() and 0xFF + val temp = remainder * 256 + digit256 + + number[i] = (temp / 58).toByte() + + remainder = temp % 58 + } + + return remainder.toByte() + } + + // + // number -> number / 256, returns number % 256 + // + private fun divmod256(number58: ByteArray, startAt: Int): Byte { + var remainder = 0 + for (i in startAt..number58.size - 1) { + val digit58 = number58[i].toInt() and 0xFF + val temp = remainder * 58 + digit58 + + number58[i] = (temp / 256).toByte() + + remainder = temp % 256 + } + + return remainder.toByte() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java index 986c288..15e174c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ package ch.dissem.bitmessage.utils; * This is one part due to the fact that Java doesn't support unsigned numbers, and another * part so we don't have to convert between byte arrays and numbers in time critical * situations. + * <p> + * Note: This class can't yet be ported to Kotlin, as with Kotlin byte + byte = int, which + * would be rather inefficient in our case. + * </p> */ public class Bytes { public static final byte BYTE_0x80 = (byte) 0x80; diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java b/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt similarity index 52% rename from core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java rename to core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt index 775a5f3..14dea31 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,35 +14,32 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils /** * Waits for a value within a callback method to be set. */ -public class CallbackWaiter<T> { - private final long startTime = System.currentTimeMillis(); - private volatile boolean isSet; - private T value; - private long time; +class CallbackWaiter<T> { + private val startTime = System.currentTimeMillis() + @Volatile private var isSet: Boolean = false + private var _value: T? = null + var time: Long = 0 + private set - public void setValue(T value) { - synchronized (this) { - this.time = System.currentTimeMillis() - startTime; - this.value = value; - this.isSet = true; + fun setValue(value: T?) { + synchronized(this) { + this.time = System.currentTimeMillis() - startTime + this._value = value + this.isSet = true } } - public T waitForValue() throws InterruptedException { + fun waitForValue(): T? { while (!isSet) { - Thread.sleep(100); + Thread.sleep(100) } - synchronized (this) { - return value; + synchronized(this) { + return _value } } - - public long getTime() { - return time; - } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java deleted file mode 100644 index 7eda028..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015 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 java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; - -public class Collections { - private final static Random RANDOM = new Random(); - - /** - * @param count the number of elements to return (if possible) - * @param collection the collection to take samples from - * @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The - * result is by no means securely random, but should be random enough so not the same objects get selected over - * and over again. - */ - public static <T> List<T> selectRandom(int count, Collection<T> collection) { - ArrayList<T> result = new ArrayList<>(count); - if (collection.size() <= count) { - result.addAll(collection); - } else { - double collectionRest = collection.size(); - double resultRest = count; - int skipMax = (int) Math.ceil(collectionRest / resultRest); - int skip = RANDOM.nextInt(skipMax); - for (T item : collection) { - collectionRest--; - if (skip > 0) { - skip--; - } else { - result.add(item); - resultRest--; - if (resultRest == 0) { - break; - } - skipMax = (int) Math.ceil(collectionRest / resultRest); - skip = RANDOM.nextInt(skipMax); - } - } - } - return result; - } - - public static <T> T selectRandom(Collection<T> collection) { - int index = RANDOM.nextInt(collection.size()); - for (T item : collection) { - if (index == 0) { - return item; - } - index--; - } - throw new IllegalArgumentException("Empty collection? Size: " + collection.size()); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt new file mode 100644 index 0000000..47626d2 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2017 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 java.util.* + +object Collections { + private val RANDOM = Random() + + /** + * @param count the number of elements to return (if possible) + * * + * @param collection the collection to take samples from + * * + * @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The + * * result is by no means securely random, but should be random enough so not the same objects get selected over + * * and over again. + */ + @JvmStatic fun <T> selectRandom(count: Int, collection: Collection<T>): List<T> { + val result = ArrayList<T>(count) + if (collection.size <= count) { + result.addAll(collection) + } else { + var collectionRest = collection.size.toDouble() + var resultRest = count.toDouble() + var skipMax = Math.ceil(collectionRest / resultRest).toInt() + var skip = RANDOM.nextInt(skipMax) + for (item in collection) { + collectionRest-- + if (skip > 0) { + skip-- + } else { + result.add(item) + resultRest-- + if (resultRest == 0.0) { + break + } + skipMax = Math.ceil(collectionRest / resultRest).toInt() + skip = RANDOM.nextInt(skipMax) + } + } + } + return result + } + + @JvmStatic fun <T> selectRandom(collection: Collection<T>): T { + var index = RANDOM.nextInt(collection.size) + for (item in collection) { + if (index == 0) { + return item + } + index-- + } + throw IllegalArgumentException("Empty collection? Size: " + collection.size) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java deleted file mode 100644 index f261a08..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2017 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.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.ports.MessageRepository; - -import java.util.*; -import java.util.Collections; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static java.util.regex.Pattern.CASE_INSENSITIVE; - -/** - * Service that helps with conversations. - */ -public class ConversationService { - private final MessageRepository messageRepository; - - private final Pattern SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE); - - public ConversationService(MessageRepository messageRepository) { - this.messageRepository = messageRepository; - } - - /** - * Retrieve the whole conversation from one single message. If the message isn't part - * of a conversation, a singleton list containing the given message is returned. Otherwise - * it's the same as {@link #getConversation(UUID)} - * - * @param message - * @return a list of messages that belong to the same conversation. - */ - public List<Plaintext> getConversation(Plaintext message) { - if (message.getConversationId() == null) { - return Collections.singletonList(message); - } - return getConversation(message.getConversationId()); - } - - private LinkedList<Plaintext> sorted(Collection<Plaintext> collection) { - LinkedList<Plaintext> result = new LinkedList<>(collection); - Collections.sort(result, new Comparator<Plaintext>() { - @Override - public int compare(Plaintext o1, Plaintext o2) { - //noinspection NumberEquality - if both are null (if both are the same, it's a bonus) - if (o1.getReceived() == o2.getReceived()) { - return 0; - } - if (o1.getReceived() == null) { - return -1; - } - if (o2.getReceived() == null) { - return 1; - } - return -o1.getReceived().compareTo(o2.getReceived()); - } - }); - return result; - } - - public List<Plaintext> getConversation(UUID conversationId) { - LinkedList<Plaintext> messages = sorted(messageRepository.getConversation(conversationId)); - Map<InventoryVector, Plaintext> map = new HashMap<>(messages.size()); - for (Plaintext message : messages) { - if (message.getInventoryVector() != null) { - map.put(message.getInventoryVector(), message); - } - } - - LinkedList<Plaintext> result = new LinkedList<>(); - while (!messages.isEmpty()) { - Plaintext last = messages.poll(); - int pos = lastParentPosition(last, result); - result.add(pos, last); - addAncestors(last, result, messages, map); - } - return result; - } - - public String getSubject(List<Plaintext> conversation) { - if (conversation.isEmpty()) { - return null; - } - // TODO: this has room for improvement - String subject = conversation.get(0).getSubject(); - Matcher matcher = SUBJECT_PREFIX.matcher(subject); - if (matcher.find()) { - return subject.substring(matcher.end()).trim(); - } - return subject.trim(); - } - - private int lastParentPosition(Plaintext child, LinkedList<Plaintext> messages) { - Iterator<Plaintext> plaintextIterator = messages.descendingIterator(); - int i = 0; - while (plaintextIterator.hasNext()) { - Plaintext next = plaintextIterator.next(); - if (isParent(next, child)) { - break; - } - i++; - } - return messages.size() - i; - } - - private boolean isParent(Plaintext item, Plaintext child) { - for (InventoryVector parentId : child.getParents()) { - if (parentId.equals(item.getInventoryVector())) { - return true; - } - } - return false; - } - - private void addAncestors(Plaintext message, LinkedList<Plaintext> result, LinkedList<Plaintext> messages, Map<InventoryVector, Plaintext> map) { - for (InventoryVector parentKey : message.getParents()) { - Plaintext parent = map.remove(parentKey); - if (parent != null) { - messages.remove(parent); - result.addFirst(parent); - addAncestors(parent, result, messages, map); - } - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt new file mode 100644 index 0000000..392a1cf --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2017 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.Plaintext +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.ports.MessageRepository +import java.util.* +import java.util.Collections +import java.util.regex.Pattern +import java.util.regex.Pattern.CASE_INSENSITIVE + +/** + * Service that helps with conversations. + */ +class ConversationService(private val messageRepository: MessageRepository) { + + private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE) + + /** + * Retrieve the whole conversation from one single message. If the message isn't part + * of a conversation, a singleton list containing the given message is returned. Otherwise + * it's the same as [.getConversation] + + * @param message + * * + * @return a list of messages that belong to the same conversation. + */ + fun getConversation(message: Plaintext): List<Plaintext> { + return getConversation(message.conversationId) + } + + private fun sorted(collection: Collection<Plaintext>): LinkedList<Plaintext> { + val result = LinkedList(collection) + Collections.sort(result, Comparator<Plaintext> { o1, o2 -> + return@Comparator when { + o1.received === o2.received -> 0 + o1.received == null -> -1 + o2.received == null -> 1 + else -> -o1.received.compareTo(o2.received) + } + }) + return result + } + + fun getConversation(conversationId: UUID): List<Plaintext> { + val messages = sorted(messageRepository.getConversation(conversationId)) + val map = HashMap<InventoryVector, Plaintext>(messages.size) + for (message in messages) { + message.inventoryVector?.let { + map.put(it, message) + } + } + + val result = LinkedList<Plaintext>() + while (!messages.isEmpty()) { + val last = messages.poll() + val pos = lastParentPosition(last, result) + result.add(pos, last) + addAncestors(last, result, messages, map) + } + return result + } + + fun getSubject(conversation: List<Plaintext>): String? { + if (conversation.isEmpty()) { + return null + } + // TODO: this has room for improvement + val subject = conversation[0].subject + val matcher = SUBJECT_PREFIX.matcher(subject!!) + + return if (matcher.find()) { + subject.substring(matcher.end()) + } else { + subject + }.trim { it <= ' ' } + } + + private fun lastParentPosition(child: Plaintext, messages: LinkedList<Plaintext>): Int { + val plaintextIterator = messages.descendingIterator() + var i = 0 + while (plaintextIterator.hasNext()) { + val next = plaintextIterator.next() + if (isParent(next, child)) { + break + } + i++ + } + return messages.size - i + } + + private fun isParent(item: Plaintext, child: Plaintext): Boolean { + return child.parents.firstOrNull { it == item.inventoryVector } != null + } + + private fun addAncestors(message: Plaintext, result: LinkedList<Plaintext>, messages: LinkedList<Plaintext>, map: MutableMap<InventoryVector, Plaintext>) { + for (parentKey in message.parents) { + map.remove(parentKey)?.let { + messages.remove(it) + result.addFirst(it) + addAncestors(it, result, messages, map) + } + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java deleted file mode 100644 index e2090b9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Map; - -public class DebugUtils { - private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class); - - public static void saveToFile(ObjectMessage objectMessage) { - try { - File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); - f.createNewFile(); - objectMessage.write(new FileOutputStream(f)); - } catch (IOException e) { - LOG.debug(e.getMessage(), e); - } - } - - public static <K> void inc(Map<K, Integer> map, K key) { - if (map.containsKey(key)) { - map.put(key, map.get(key) + 1); - } else { - map.put(key, 1); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt new file mode 100644 index 0000000..31b2e65 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2017 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 org.slf4j.LoggerFactory +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +object DebugUtils { + private val LOG = LoggerFactory.getLogger(DebugUtils::class.java) + + @JvmStatic fun saveToFile(objectMessage: ObjectMessage) { + try { + val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv") + f.createNewFile() + objectMessage.write(FileOutputStream(f)) + } catch (e: IOException) { + LOG.debug(e.message, e) + } + + } + + @JvmStatic fun <K> inc(map: MutableMap<K, Int>, key: K) { + val value = map.get(key); + if (value == null) { + map.put(key, 1) + } else { + map.put(key, value + 1) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java deleted file mode 100644 index 8d05e7e..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2015 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 java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -import static ch.dissem.bitmessage.utils.AccessCounter.inc; - -/** - * This class handles decoding simple types from byte stream, according to - * https://bitmessage.org/wiki/Protocol_specification#Common_structures - */ -public class Decode { - public static byte[] shortVarBytes(InputStream in, AccessCounter counter) throws IOException { - int length = uint16(in, counter); - return bytes(in, length, counter); - } - - public static byte[] varBytes(InputStream in) throws IOException { - return varBytes(in, null); - } - - public static byte[] varBytes(InputStream in, AccessCounter counter) throws IOException { - int length = (int) varInt(in, counter); - return bytes(in, length, counter); - } - - public static byte[] bytes(InputStream in, int count) throws IOException { - return bytes(in, count, null); - } - - public static byte[] bytes(InputStream in, int count, AccessCounter counter) throws IOException { - byte[] result = new byte[count]; - int off = 0; - while (off < count) { - int read = in.read(result, off, count - off); - if (read < 0) { - throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off); - } - off += read; - } - inc(counter, count); - return result; - } - - public static long[] varIntList(InputStream in) throws IOException { - int length = (int) varInt(in); - long[] result = new long[length]; - - for (int i = 0; i < length; i++) { - result[i] = varInt(in); - } - return result; - } - - public static long varInt(InputStream in) throws IOException { - return varInt(in, null); - } - - public static long varInt(InputStream in, AccessCounter counter) throws IOException { - int first = in.read(); - inc(counter); - switch (first) { - case 0xfd: - return uint16(in, counter); - case 0xfe: - return uint32(in, counter); - case 0xff: - return int64(in, counter); - default: - return first; - } - } - - public static int uint8(InputStream in) throws IOException { - return in.read(); - } - - public static int uint16(InputStream in) throws IOException { - return uint16(in, null); - } - - public static int uint16(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 2); - return in.read() << 8 | in.read(); - } - - public static long uint32(InputStream in) throws IOException { - return uint32(in, null); - } - - public static long uint32(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 4); - return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); - } - - public static long uint32(ByteBuffer in) { - return u(in.get()) << 24 | u(in.get()) << 16 | u(in.get()) << 8 | u(in.get()); - } - - public static int int32(InputStream in) throws IOException { - return int32(in, null); - } - - public static int int32(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 4); - return ByteBuffer.wrap(bytes(in, 4)).getInt(); - } - - public static long int64(InputStream in) throws IOException { - return int64(in, null); - } - - public static long int64(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 8); - return ByteBuffer.wrap(bytes(in, 8)).getLong(); - } - - public static String varString(InputStream in) throws IOException { - return varString(in, null); - } - - public static String varString(InputStream in, AccessCounter counter) throws IOException { - int length = (int) varInt(in, counter); - // technically, it says the length in characters, but I think this one might be correct - // otherwise it will get complicated, as we'll need to read UTF-8 char by char... - return new String(bytes(in, length, counter), "utf-8"); - } - - /** - * Returns the given byte as if it were unsigned. - */ - private static int u(byte b) { - return b & 0xFF; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt new file mode 100644 index 0000000..3eb192e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2017 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 java.io.IOException +import java.io.InputStream +import java.nio.ByteBuffer + +/** + * This class handles decoding simple types from byte stream, according to + * https://bitmessage.org/wiki/Protocol_specification#Common_structures + */ +object Decode { + @JvmStatic fun shortVarBytes(`in`: InputStream, counter: AccessCounter): ByteArray { + val length = uint16(`in`, counter) + return bytes(`in`, length, counter) + } + + @JvmStatic @JvmOverloads fun varBytes(`in`: InputStream, counter: AccessCounter? = null): ByteArray { + val length = varInt(`in`, counter).toInt() + return bytes(`in`, length, counter) + } + + @JvmStatic @JvmOverloads fun bytes(`in`: InputStream, count: Int, counter: AccessCounter? = null): ByteArray { + val result = ByteArray(count) + var off = 0 + while (off < count) { + val read = `in`.read(result, off, count - off) + if (read < 0) { + throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off") + } + off += read + } + AccessCounter.inc(counter, count) + return result + } + + @JvmStatic fun varIntList(`in`: InputStream): LongArray { + val length = varInt(`in`).toInt() + val result = LongArray(length) + + for (i in 0..length - 1) { + result[i] = varInt(`in`) + } + return result + } + + @JvmStatic @JvmOverloads fun varInt(`in`: InputStream, counter: AccessCounter? = null): Long { + val first = `in`.read() + AccessCounter.inc(counter) + when (first) { + 0xfd -> return uint16(`in`, counter).toLong() + 0xfe -> return uint32(`in`, counter) + 0xff -> return int64(`in`, counter) + else -> return first.toLong() + } + } + + @JvmStatic fun uint8(`in`: InputStream): Int { + return `in`.read() + } + + @JvmStatic @JvmOverloads fun uint16(`in`: InputStream, counter: AccessCounter? = null): Int { + AccessCounter.inc(counter, 2) + return `in`.read() shl 8 or `in`.read() + } + + @JvmStatic @JvmOverloads fun uint32(`in`: InputStream, counter: AccessCounter? = null): Long { + AccessCounter.inc(counter, 4) + return (`in`.read() shl 24 or (`in`.read() shl 16) or (`in`.read() shl 8) or `in`.read()).toLong() + } + + @JvmStatic fun uint32(`in`: ByteBuffer): Long { + return (u(`in`.get()) shl 24 or (u(`in`.get()) shl 16) or (u(`in`.get()) shl 8) or u(`in`.get())).toLong() + } + + @JvmStatic @JvmOverloads fun int32(`in`: InputStream, counter: AccessCounter? = null): Int { + AccessCounter.inc(counter, 4) + return ByteBuffer.wrap(bytes(`in`, 4)).int + } + + @JvmStatic @JvmOverloads fun int64(`in`: InputStream, counter: AccessCounter? = null): Long { + AccessCounter.inc(counter, 8) + return ByteBuffer.wrap(bytes(`in`, 8)).long + } + + @JvmStatic @JvmOverloads fun varString(`in`: InputStream, counter: AccessCounter? = null): String { + val length = varInt(`in`, counter).toInt() + // technically, it says the length in characters, but I think this one might be correct + // otherwise it will get complicated, as we'll need to read UTF-8 char by char... + return String(bytes(`in`, length, counter)) + } + + /** + * Returns the given byte as if it were unsigned. + */ + @JvmStatic private fun u(b: Byte): Int { + return b.toInt() and 0xFF + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java deleted file mode 100644 index a60c027..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2015 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.Streamable; -import ch.dissem.bitmessage.exception.ApplicationException; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.Buffer; -import java.nio.ByteBuffer; - -import static ch.dissem.bitmessage.utils.AccessCounter.inc; - -/** - * This class handles encoding simple types from byte stream, according to - * https://bitmessage.org/wiki/Protocol_specification#Common_structures - */ -public class Encode { - public static void varIntList(long[] values, OutputStream stream) throws IOException { - varInt(values.length, stream); - for (long value : values) { - varInt(value, stream); - } - } - - public static void varIntList(long[] values, ByteBuffer buffer) { - varInt(values.length, buffer); - for (long value : values) { - varInt(value, buffer); - } - } - - public static void varInt(long value, OutputStream stream) throws IOException { - varInt(value, stream, null); - } - - public static void varInt(long value, ByteBuffer buffer) { - if (value < 0) { - // This is due to the fact that Java doesn't really support unsigned values. - // Please be aware that this might be an error due to a smaller negative value being cast to long. - // Normally, negative values shouldn't occur within the protocol, and longs large enough for being - // recognized as negatives aren't realistic. - buffer.put((byte) 0xff); - buffer.putLong(value); - } else if (value < 0xfd) { - buffer.put((byte) value); - } else if (value <= 0xffffL) { - buffer.put((byte) 0xfd); - buffer.putShort((short) value); - } else if (value <= 0xffffffffL) { - buffer.put((byte) 0xfe); - buffer.putInt((int) value); - } else { - buffer.put((byte) 0xff); - buffer.putLong(value); - } - } - - public static byte[] varInt(long value) { - ByteBuffer buffer = ByteBuffer.allocate(9); - varInt(value, buffer); - buffer.flip(); - return Bytes.truncate(buffer.array(), buffer.limit()); - } - - public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(9); - varInt(value, buffer); - buffer.flip(); - stream.write(buffer.array(), 0, buffer.limit()); - inc(counter, buffer.limit()); - } - - public static void int8(long value, OutputStream stream) throws IOException { - int8(value, stream, null); - } - - public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write((int) value); - inc(counter); - } - - public static void int16(long value, OutputStream stream) throws IOException { - int16(value, stream, null); - } - - public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write(ByteBuffer.allocate(2).putShort((short) value).array()); - inc(counter, 2); - } - - public static void int16(long value, ByteBuffer buffer) { - buffer.putShort((short) value); - } - - public static void int32(long value, OutputStream stream) throws IOException { - int32(value, stream, null); - } - - public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write(ByteBuffer.allocate(4).putInt((int) value).array()); - inc(counter, 4); - } - - public static void int32(long value, ByteBuffer buffer) { - buffer.putInt((int) value); - } - - public static void int64(long value, OutputStream stream) throws IOException { - int64(value, stream, null); - } - - public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write(ByteBuffer.allocate(8).putLong(value).array()); - inc(counter, 8); - } - - public static void int64(long value, ByteBuffer buffer) { - buffer.putLong(value); - } - - public static void varString(String value, OutputStream out) throws IOException { - byte[] bytes = value.getBytes("utf-8"); - // Technically, it says the length in characters, but I think this one might be correct. - // It doesn't really matter, as only ASCII characters are being used. - // see also Decode#varString() - varInt(bytes.length, out); - out.write(bytes); - } - - public static void varString(String value, ByteBuffer buffer) { - try { - byte[] bytes = value.getBytes("utf-8"); - // Technically, it says the length in characters, but I think this one might be correct. - // It doesn't really matter, as only ASCII characters are being used. - // see also Decode#varString() - buffer.put(varInt(bytes.length)); - buffer.put(bytes); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } - - public static void varBytes(byte[] data, OutputStream out) throws IOException { - varInt(data.length, out); - out.write(data); - } - - public static void varBytes(byte[] data, ByteBuffer buffer) { - varInt(data.length, buffer); - buffer.put(data); - } - - /** - * Serializes a {@link Streamable} object and returns the byte array. - * - * @param streamable the object to be serialized - * @return an array of bytes representing the given streamable object. - */ - public static byte[] bytes(Streamable streamable) { - if (streamable == null) return null; - - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - streamable.write(stream); - } catch (IOException e) { - throw new ApplicationException(e); - } - return stream.toByteArray(); - } - - /** - * @param streamable the object to be serialized - * @param padding the result will be padded such that its length is a multiple of <em>padding</em> - * @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding. - */ - public static byte[] bytes(Streamable streamable, int padding) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - streamable.write(stream); - } catch (IOException e) { - throw new ApplicationException(e); - } - int offset = padding - stream.size() % padding; - int length = stream.size() + offset; - byte[] result = new byte[length]; - stream.write(result, offset, stream.size()); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt new file mode 100644 index 0000000..1904c66 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2017 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.Streamable +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * This class handles encoding simple types from byte stream, according to + * https://bitmessage.org/wiki/Protocol_specification#Common_structures + */ +object Encode { + @JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { + varInt(values.size.toLong(), stream) + for (value in values) { + varInt(value, stream) + } + } + + @JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) { + varInt(values.size.toLong(), buffer) + for (value in values) { + varInt(value, buffer) + } + } + + @JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { + if (value < 0) { + // This is due to the fact that Java doesn't really support unsigned values. + // Please be aware that this might be an error due to a smaller negative value being cast to long. + // Normally, negative values shouldn't occur within the protocol, and longs large enough for being + // recognized as negatives aren't realistic. + buffer.put(0xff.toByte()) + buffer.putLong(value) + } else if (value < 0xfd) { + buffer.put(value.toByte()) + } else if (value <= 0xffffL) { + buffer.put(0xfd.toByte()) + buffer.putShort(value.toShort()) + } else if (value <= 0xffffffffL) { + buffer.put(0xfe.toByte()) + buffer.putInt(value.toInt()) + } else { + buffer.put(0xff.toByte()) + buffer.putLong(value) + } + } + + @JvmStatic fun varInt(value: Long): ByteArray { + val buffer = ByteBuffer.allocate(9) + varInt(value, buffer) + buffer.flip() + return Bytes.truncate(buffer.array(), buffer.limit()) + } + + @JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + val buffer = ByteBuffer.allocate(9) + varInt(value, buffer) + buffer.flip() + stream.write(buffer.array(), 0, buffer.limit()) + AccessCounter.inc(counter, buffer.limit()) + } + + @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(value.toInt()) + AccessCounter.inc(counter) + } + + @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array()) + AccessCounter.inc(counter, 2) + } + + @JvmStatic fun int16(value: Long, buffer: ByteBuffer) { + buffer.putShort(value.toShort()) + } + + @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array()) + AccessCounter.inc(counter, 4) + } + + @JvmStatic fun int32(value: Long, buffer: ByteBuffer) { + buffer.putInt(value.toInt()) + } + + @JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(8).putLong(value).array()) + AccessCounter.inc(counter, 8) + } + + @JvmStatic fun int64(value: Long, buffer: ByteBuffer) { + buffer.putLong(value) + } + + @JvmStatic fun varString(value: String, out: OutputStream) { + val bytes = value.toByteArray(charset("utf-8")) + // Technically, it says the length in characters, but I think this one might be correct. + // It doesn't really matter, as only ASCII characters are being used. + // see also Decode#varString() + varInt(bytes.size.toLong(), out) + out.write(bytes) + } + + @JvmStatic fun varString(value: String, buffer: ByteBuffer) { + val bytes = value.toByteArray() + // Technically, it says the length in characters, but I think this one might be correct. + // It doesn't really matter, as only ASCII characters are being used. + // see also Decode#varString() + buffer.put(varInt(bytes.size.toLong())) + buffer.put(bytes) + } + + @JvmStatic fun varBytes(data: ByteArray, out: OutputStream) { + varInt(data.size.toLong(), out) + out.write(data) + } + + @JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) { + varInt(data.size.toLong(), buffer) + buffer.put(data) + } + + /** + * Serializes a [Streamable] object and returns the byte array. + * @param streamable the object to be serialized + * @return an array of bytes representing the given streamable object. + */ + @JvmStatic fun bytes(streamable: Streamable): ByteArray { + val stream = ByteArrayOutputStream() + streamable.write(stream) + return stream.toByteArray() + } + + /** + * @param streamable the object to be serialized + * @param padding the result will be padded such that its length is a multiple of *padding* + * @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding. + */ + @JvmStatic fun bytes(streamable: Streamable, padding: Int): ByteArray { + val stream = ByteArrayOutputStream() + streamable.write(stream) + val offset = padding - stream.size() % padding + val length = stream.size() + offset + val result = ByteArray(length) + stream.write(result, offset, stream.size()) + return result + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java deleted file mode 100644 index 9d0c078..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java +++ /dev/null @@ -1,10 +0,0 @@ -package ch.dissem.bitmessage.utils; - -/** - * @author Christian Basler - */ -public class Numbers { - public static long max(long a, long b) { - return a > b ? a : b; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt similarity index 73% rename from core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt index c9e3efd..445cbd1 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,10 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +@file:JvmName("Numbers") -public class DecryptionFailedException extends Exception { - private static final long serialVersionUID = 3241116253113872731L; +package ch.dissem.bitmessage.utils + +fun max(a: Long, b: Long): Long { + return if (a > b) a else b } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Points.java b/core/src/main/java/ch/dissem/bitmessage/utils/Points.kt similarity index 52% rename from core/src/main/java/ch/dissem/bitmessage/utils/Points.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Points.kt index 937fe20..7f07188 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Points.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Points.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,23 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; - -import java.util.Arrays; +package ch.dissem.bitmessage.utils /** - * Created by chris on 20.07.15. + * Helper object to get a point from a public key on a elliptic curve. */ -public class Points { - public static byte[] getX(byte[] P) { - return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1); +object Points { + /** + * returns X component of the point represented by public key P + */ + @JvmStatic fun getX(P: ByteArray): ByteArray { + return P.sliceArray(1..(P.size - 1) / 2) } - public static byte[] getY(byte[] P) { - return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length); + /** + * returns Y component of the point represented by public key P + */ + @JvmStatic fun getY(P: ByteArray): ByteArray { + return P.sliceArray((P.size - 1) / 2 + 1..P.size - 1) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.java b/core/src/main/java/ch/dissem/bitmessage/utils/Property.java deleted file mode 100644 index b823eb5..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Property.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015 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 java.util.Arrays; -import java.util.Objects; - -/** - * Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now - * used to contain different status information. It is by default displayed in some JSON inspired human readable - * notation, but you might only want to rely on the 'human readable' part. - * <p> - * If you need a real JSON representation, please add a method <code>toJson()</code>. - * </p> - */ -public class Property { - private String name; - private Object value; - private Property[] properties; - - public Property(String name, Object value, Property... properties) { - this.name = name; - this.value = value; - this.properties = properties; - } - - public String getName() { - return name; - } - - public Object getValue() { - return value; - } - - /** - * Returns the property if available or <code>null</code> otherwise. - * Subproperties can be requested by submitting the sequence of properties. - */ - public Property getProperty(String... name) { - if (name == null || name.length == 0) return null; - - for (Property p : properties) { - if (Objects.equals(name[0], p.name)) { - if (name.length == 1) - return p; - else - return p.getProperty(Arrays.copyOfRange(name, 1, name.length)); - } - } - return null; - } - - public Property[] getProperties() { - return properties; - } - - @Override - public String toString() { - return toString(""); - } - - private String toString(String indentation) { - StringBuilder result = new StringBuilder(); - result.append(indentation).append(name).append(": "); - if (value != null || properties.length == 0) { - result.append(value); - } - if (properties.length > 0) { - result.append("{\n"); - for (Property property : properties) { - result.append(property.toString(indentation + " ")).append('\n'); - } - result.append(indentation).append("}"); - } - return result.toString(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt new file mode 100644 index 0000000..bea1621 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2017 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 + +/** + * Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now + * used to contain different status information. It is by default displayed in some JSON inspired human readable + * notation, but you might only want to rely on the 'human readable' part. + * + * + * If you need a real JSON representation, please add a method `toJson()`. + * + */ +class Property private constructor(val name: String, val value: Any? = null, val properties: Array<Property> = emptyArray()) { + + constructor(name: String, value: Any) : this(name = name, value = value, properties = emptyArray()) + constructor(name: String, vararg properties: Property) : this(name, null, Array(properties.size, { i -> properties[i] })) + + /** + * Returns the property if available or `null` otherwise. + * Subproperties can be requested by submitting the sequence of properties. + */ + fun getProperty(vararg name: String): Property? { + properties + .filter { name[0] == it.name } + .forEach { + if (name.size == 1) + return it + else + return it.getProperty(*name.sliceArray(1..name.size - 1)) + } + return null + } + + override fun toString(): String { + return toString("") + } + + private fun toString(indentation: String): String { + val result = StringBuilder() + result.append(indentation).append(name).append(": ") + if (value != null || properties.isEmpty()) { + result.append(value) + } + if (properties.isNotEmpty()) { + result.append("{\n") + for (property in properties) { + result.append(property.toString(indentation + " ")).append('\n') + } + result.append(indentation).append("}") + } + return result.toString() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt similarity index 58% rename from core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt index 2eeaa97..bd7e4d7 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,23 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import ch.dissem.bitmessage.ports.Cryptography; +import ch.dissem.bitmessage.ports.Cryptography +import kotlin.properties.Delegates /** * @author Christian Basler */ -public class Singleton { - private static Cryptography cryptography; +object Singleton { + private var cryptography by Delegates.notNull<Cryptography>() - public static void initialize(Cryptography cryptography) { - synchronized (Singleton.class) { - Singleton.cryptography = cryptography; - } + @Synchronized + @JvmStatic fun initialize(cryptography: Cryptography) { + Singleton.cryptography = cryptography } - public static Cryptography cryptography() { - return cryptography; + @JvmStatic fun cryptography(): Cryptography { + return cryptography } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java deleted file mode 100644 index 82d06e3..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.payload.ObjectType; - -import static ch.dissem.bitmessage.utils.Strings.hex; - -public class SqlStrings { - public static StringBuilder join(long... objects) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(objects[i]); - } - return streamList; - } - - public static StringBuilder join(byte[]... objects) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(hex(objects[i])); - } - return streamList; - } - - public static StringBuilder join(ObjectType... types) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < types.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(types[i].getNumber()); - } - return streamList; - } - - public static StringBuilder join(Enum... types) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < types.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append('\'').append(types[i].name()).append('\''); - } - return streamList; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt new file mode 100644 index 0000000..d0efdf0 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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.payload.ObjectType + +object SqlStrings { + @JvmStatic fun join(vararg objects: Long): String { + return objects.joinToString() + } + + @JvmStatic fun join(vararg objects: ByteArray): String { + return objects.map { Strings.hex(it) }.joinToString() + } + + @JvmStatic fun join(vararg types: ObjectType): String { + return types.map { it.number }.joinToString() + } + + @JvmStatic fun join(vararg types: Enum<*>): String { + return types.map { '\'' + it.name + '\'' }.joinToString() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt similarity index 50% rename from core/src/main/java/ch/dissem/bitmessage/utils/Strings.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt index 96383f7..98fd16e 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,31 +14,27 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils /** * Some utilities to handle strings. * TODO: Probably this should be split in a GUI related and an SQL related utility class. */ -public class Strings { - public static StringBuilder join(Object... objects) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(objects[i]); - } - return streamList; +object Strings { + @JvmStatic fun join(vararg objects: Any): String { + return objects.joinToString() } - public static StringBuilder hex(byte[] bytes) { - StringBuilder hex = new StringBuilder(bytes.length + 2); - for (byte b : bytes) { - hex.append(String.format("%02x", b)); - } - return hex; + @JvmStatic fun hex(bytes: ByteArray): String { + return bytes.map { String.format("%02x", it) }.joinToString(separator = "") } - public static String str(Object o) { - return o == null ? null : o.toString(); + @JvmStatic fun str(o: Any?): String? { + return o?.toString() + } + + @JvmName("strNonNull") + @JvmStatic fun str(o: Any): String { + return o.toString() } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.java b/core/src/main/java/ch/dissem/bitmessage/utils/TTL.java deleted file mode 100644 index c4fab9c..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.java +++ /dev/null @@ -1,44 +0,0 @@ -package ch.dissem.bitmessage.utils; - -import static ch.dissem.bitmessage.utils.UnixTime.DAY; - -/** - * Stores times to live in seconds for different object types. Usually this shouldn't be messed with, but for tests - * it might be a good idea to reduce it to a minimum, and on mobile clients you might want to optimize it as well. - * - * @author Christian Basler - */ -public class TTL { - private static long msg = 2 * DAY; - private static long getpubkey = 2 * DAY; - private static long pubkey = 28 * DAY; - - public static long msg() { - return msg; - } - - public static void msg(long msg) { - TTL.msg = validate(msg); - } - - public static long getpubkey() { - return getpubkey; - } - - public static void getpubkey(long getpubkey) { - TTL.getpubkey = validate(getpubkey); - } - - public static long pubkey() { - return pubkey; - } - - public static void pubkey(long pubkey) { - TTL.pubkey = validate(pubkey); - } - - private static long validate(long ttl) { - if (ttl < 0 || ttl > 28 * DAY) throw new IllegalArgumentException("TTL must be between 0 seconds and 28 days"); - return ttl; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt b/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt new file mode 100644 index 0000000..6fe883a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 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 + +/** + * Stores times to live in seconds for different object types. Usually this shouldn't be messed with, but for tests + * it might be a good idea to reduce it to a minimum, and on mobile clients you might want to optimize it as well. + + * @author Christian Basler + */ +object TTL { + @JvmStatic var msg = 2 * UnixTime.DAY + @JvmName("msg") get + @JvmName("msg") set(msg) { + field = validate(msg) + } + + @JvmStatic var getpubkey = 2 * UnixTime.DAY + @JvmName("getpubkey") get + @JvmName("getpubkey") set(getpubkey) { + field = validate(getpubkey) + } + + @JvmStatic var pubkey = 28 * UnixTime.DAY + @JvmName("pubkey") get + @JvmName("pubkey") set(pubkey) { + field = validate(pubkey) + } + + private fun validate(ttl: Long): Long { + if (ttl < 0 || ttl > 28 * UnixTime.DAY) + throw IllegalArgumentException("TTL must be between 0 seconds and 28 days") + return ttl + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java deleted file mode 100644 index 36cf5b9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -public class ThreadFactoryBuilder { - private final String namePrefix; - private int prio = Thread.NORM_PRIORITY; - private boolean daemon = false; - - private ThreadFactoryBuilder(String pool) { - this.namePrefix = pool + "-thread-"; - } - - - public static ThreadFactoryBuilder pool(String name) { - return new ThreadFactoryBuilder(name); - } - - public ThreadFactoryBuilder lowPrio() { - prio = Thread.MIN_PRIORITY; - return this; - } - - public ThreadFactoryBuilder daemon() { - daemon = true; - return this; - } - - public ThreadFactory build() { - SecurityManager s = System.getSecurityManager(); - final ThreadGroup group = (s != null) ? s.getThreadGroup() : - Thread.currentThread().getThreadGroup(); - - return new ThreadFactory() { - private final AtomicInteger threadNumber = new AtomicInteger(1); - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, - namePrefix + threadNumber.getAndIncrement(), - 0); - t.setPriority(prio); - t.setDaemon(daemon); - return t; - } - }; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt new file mode 100644 index 0000000..98bfa55 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2017 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 java.util.concurrent.ThreadFactory +import java.util.concurrent.atomic.AtomicInteger + +class ThreadFactoryBuilder private constructor(pool: String) { + private val namePrefix: String = pool + "-thread-" + private var prio = Thread.NORM_PRIORITY + private var daemon = false + + fun lowPrio(): ThreadFactoryBuilder { + prio = Thread.MIN_PRIORITY + return this + } + + fun daemon(): ThreadFactoryBuilder { + daemon = true + return this + } + + fun build(): ThreadFactory { + val s = System.getSecurityManager() + val group = if (s != null) + s.threadGroup + else + Thread.currentThread().threadGroup + + return object : ThreadFactory { + private val threadNumber = AtomicInteger(1) + + override fun newThread(r: Runnable): Thread { + val t = Thread(group, r, + namePrefix + threadNumber.getAndIncrement(), + 0) + t.priority = prio + t.isDaemon = daemon + return t + } + } + } + + companion object { + @JvmStatic fun pool(name: String): ThreadFactoryBuilder { + return ThreadFactoryBuilder(name) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java deleted file mode 100644 index 0d0d991..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015 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; - -/** - * A simple utility class that simplifies using the second based time used in Bitmessage. - */ -public class UnixTime { - /** - * Length of a minute in seconds, intended for use with {@link #now(long)}. - */ - public static final int MINUTE = 60; - /** - * Length of an hour in seconds, intended for use with {@link #now(long)}. - */ - public static final long HOUR = 60 * MINUTE; - /** - * Length of a day in seconds, intended for use with {@link #now(long)}. - */ - public static final long DAY = 24 * HOUR; - - /** - * @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000) - */ - public static long now() { - return System.currentTimeMillis() / 1000; - } - - /** - * Same as {@link #now()} + shiftSeconds, but might be more readable. - * - * @param shiftSeconds number of seconds from now we're interested in - * @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago - */ - public static long now(long shiftSeconds) { - return (System.currentTimeMillis() / 1000) + shiftSeconds; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt new file mode 100644 index 0000000..d21bc2e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2017 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 + +/** + * A simple utility class that simplifies using the second based time used in Bitmessage. + */ +object UnixTime { + /** + * Length of a minute in seconds, intended for use with [.now]. + */ + @JvmField val MINUTE = 60L + /** + * Length of an hour in seconds, intended for use with [.now]. + */ + @JvmField val HOUR = 60L * MINUTE + /** + * Length of a day in seconds, intended for use with [.now]. + */ + @JvmField val DAY = 24L * HOUR + + /** + * @return the time in second based Unix time ([System.currentTimeMillis]/1000) + */ + @JvmStatic val now: Long + @JvmName("now") get() { + return System.currentTimeMillis() / 1000L + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java deleted file mode 100644 index 0702866..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * 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.testutils.TestInventory; -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.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; - private TestInventory testInventory; - - @Before - public void setUp() throws Exception { - Singleton.initialize(null); - listener = mock(BitmessageContext.Listener.class); - Singleton.initialize(new BouncyCryptography()); - testInventory = new TestInventory(); - ctx = new BitmessageContext.Builder() - .addressRepo(mock(AddressRepository.class)) - .cryptography(cryptography()) - .inventory(spy(testInventory)) - .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(InventoryVector.fromHash(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(InventoryVector.fromHash(cryptography().getInitialHash(item.object)), item); - } - - @Override - public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - items.put(InventoryVector.fromHash(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"); - when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(contact); - ctx.addContact(contact); - - verify(ctx.addresses(), timeout(1000).atLeastOnce()).save(contact); - verify(ctx.internals().getProofOfWorkEngine(), timeout(1000)) - .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 { - testInventory.init( - "V1Msg.payload", - "V2GetPubkey.payload", - "V2Pubkey.payload", - "V3GetPubkey.payload", - "V3Pubkey.payload", - "V4Broadcast.payload", - "V4GetPubkey.payload", - "V4Pubkey.payload", - "V5Broadcast.payload" - ); - BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); - - when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(contact); - - 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 { - testInventory.init( - "V1Msg.payload", - "V2GetPubkey.payload", - "V2Pubkey.payload", - "V3GetPubkey.payload", - "V3Pubkey.payload", - "V4Broadcast.payload", - "V4GetPubkey.payload", - "V4Pubkey.payload", - "V5Broadcast.payload" - ); - BitmessageAddress contact = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - 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(any(BitmessageAddress.class)); - 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"); - - testInventory.init( - "V4Broadcast.payload", - "V5Broadcast.payload" - ); - - 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.resendUnacknowledgedMessages(); - verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext)); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt new file mode 100644 index 0000000..024b66a --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt @@ -0,0 +1,319 @@ +/* + * Copyright 2017 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.DefaultLabeler +import ch.dissem.bitmessage.ports.ProofOfWorkEngine +import ch.dissem.bitmessage.ports.ProofOfWorkRepository +import ch.dissem.bitmessage.testutils.TestInventory +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TTL +import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.notNullValue +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.util.* + +/** + * @author Christian Basler + */ +class BitmessageContextTest { + private lateinit var ctx: BitmessageContext + private var listener: BitmessageContext.Listener = mock() + private val inventory = spy(TestInventory()) + private val testPowRepo = spy(object : ProofOfWorkRepository { + internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap() + internal var added = 0 + internal var removed = 0 + + override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item { + return items[InventoryVector(initialHash)]!! + } + + override fun getItems(): List<ByteArray> { + val result = LinkedList<ByteArray>() + for ((hash) in items.keys) { + result.add(hash) + } + return result + } + + override fun putObject(item: ProofOfWorkRepository.Item) { + items.put(InventoryVector(cryptography().getInitialHash(item.`object`)), item) + added++ + } + + override fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + items.put(InventoryVector(cryptography().getInitialHash(`object`)), ProofOfWorkRepository.Item(`object`, nonceTrialsPerByte, extraBytes)) + added++ + } + + override fun removeObject(initialHash: ByteArray) { + if (items.remove(InventoryVector(initialHash)) != null) { + removed++ + } + } + + fun reset() { + items.clear() + added = 0 + removed = 0 + } + }) + + @Before + fun setUp() { + Singleton.initialize(BouncyCryptography()) + ctx = BitmessageContext.Builder() + .addressRepo(mock()) + .cryptography(cryptography()) + .inventory(inventory) + .listener(listener) + .messageRepo(mock()) + .networkHandler(mock()) + .nodeRegistry(mock()) + .labeler(spy(DefaultLabeler())) + .powRepo(testPowRepo) + .proofOfWorkEngine(spy(object : ProofOfWorkEngine { + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + callback.onNonceCalculated(initialHash, ByteArray(8)) + } + })) + .build() + TTL.msg = 2 * MINUTE + testPowRepo.reset() + } + + @Test + fun `ensure contact is saved and pubkey requested`() { + val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact) + + ctx.addContact(contact) + + verify(ctx.addresses(), timeout(1000).atLeastOnce()).save(contact) + verify(ctx.internals().proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure pubkey is not requested if it exists`() { + val (_, _, payload) = TestUtils.loadObjectMessage(2, "V2Pubkey.payload") + val pubkey = payload as Pubkey + val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + contact.pubkey = pubkey + + ctx.addContact(contact) + + verify(ctx.addresses(), times(1)).save(contact) + verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure V2Pubkey is not requested if it exists in inventory`() { + inventory.init( + "V1Msg.payload", + "V2GetPubkey.payload", + "V2Pubkey.payload", + "V3GetPubkey.payload", + "V3Pubkey.payload", + "V4Broadcast.payload", + "V4GetPubkey.payload", + "V4Pubkey.payload", + "V5Broadcast.payload" + ) + val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + + whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact) + + ctx.addContact(contact) + + verify(ctx.addresses(), atLeastOnce()).save(contact) + verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure V4Pubkey is not requested if it exists in inventory`() { + inventory.init( + "V1Msg.payload", + "V2GetPubkey.payload", + "V2Pubkey.payload", + "V3GetPubkey.payload", + "V3Pubkey.payload", + "V4Broadcast.payload", + "V4GetPubkey.payload", + "V4Pubkey.payload", + "V5Broadcast.payload" + ) + val contact = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + val stored = BitmessageAddress(contact.address) + stored.alias = "Test" + whenever(ctx.addresses().getAddress(contact.address)).thenReturn(stored) + + ctx.addContact(contact) + + verify(ctx.addresses(), atLeastOnce()).save(any()) + verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure subscription is added and existing broadcasts retrieved`() { + val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + + inventory.init( + "V4Broadcast.payload", + "V5Broadcast.payload" + ) + + whenever(ctx.addresses().getSubscriptions(any())).thenReturn(listOf(address)) + ctx.addSubscribtion(address) + + verify(ctx.addresses(), atLeastOnce()).save(address) + assertThat(address.isSubscribed, `is`(true)) + verify(ctx.internals().inventory).getObjects(eq(address.stream), any(), any()) + verify(listener).receive(any()) + } + + @Test + fun `ensure identity is created`() { + assertThat(ctx.createIdentity(false), notNullValue()) + } + + @Test + fun `ensure message is sent`() { + ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), + "Subject", "Message") + assertEquals(2, testPowRepo.added) + verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce()) + .putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) + } + + @Test + fun `ensure pubkey is requested if it is missing`() { + ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), + BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), + "Subject", "Message") + verify(testPowRepo, timeout(10000).atLeastOnce()) + .putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L)) + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) + } + + @Test(expected = IllegalArgumentException::class) + fun `ensure sender must be identity`() { + ctx.send(BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), + BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), + "Subject", "Message") + } + + @Test + fun `ensure broadcast is sent`() { + ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), + "Subject", "Message") + verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce()) + .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) + verify(ctx.internals().proofOfWorkEngine) + .calculateNonce(any(), any(), any()) + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) + } + + @Test(expected = IllegalArgumentException::class) + fun `ensure sender without private key throws exception`() { + val msg = Plaintext.Builder(Type.BROADCAST) + .from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .message("Subject", "Message") + .build() + ctx.send(msg) + } + + @Test + fun `ensure chan is joined`() { + val chanAddress = "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r" + val chan = ctx.joinChan("general", chanAddress) + assertNotNull(chan) + assertEquals(chan.address, chanAddress) + assertTrue(chan.isChan) + } + + @Test + fun `ensure deterministic addresses are created`() { + val expected_size = 8 + val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false) + assertEquals(expected_size.toLong(), addresses.size.toLong()) + val expected = HashSet<String>(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 (a in addresses) { + assertTrue(expected.contains(a.address)) + expected.remove(a.address) + } + } + + @Test + fun `ensure short deterministic addresses are created`() { + val expected_size = 1 + val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true) + assertEquals(expected_size.toLong(), addresses.size.toLong()) + val expected = HashSet<String>(expected_size) + expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78") + for (a in addresses) { + assertTrue(expected.contains(a.address)) + expected.remove(a.address) + } + } + + @Test + fun `ensure chan is created`() { + val chan = ctx.createChan("test") + assertNotNull(chan) + assertEquals(chan.version, Pubkey.LATEST_VERSION) + assertTrue(chan.isChan) + } + + @Test + fun `ensure unacknowledged message is resent`() { + val plaintext = Plaintext.Builder(Type.MSG) + .ttl(1) + .message("subject", "message") + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .build() + assertTrue(plaintext.to!!.has(Pubkey.Feature.DOES_ACK)) + whenever(ctx.messages().findMessagesToResend()).thenReturn(listOf(plaintext)) + whenever(ctx.messages().getMessage(any<ByteArray>())).thenReturn(plaintext) + ctx.resendUnacknowledgedMessages() + verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java deleted file mode 100644 index 21f9506..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015 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.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.V4Broadcast; -import ch.dissem.bitmessage.entity.payload.V5Broadcast; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class DecryptionTest extends TestBase { - @Test - public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - TestUtils.loadPubkey(address); - ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload"); - V4Broadcast broadcast = (V4Broadcast) objectMessage.getPayload(); - broadcast.decrypt(address); - assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject()); - assertTrue(objectMessage.isSignatureValid(address.getPubkey())); - } - - @Test - public void ensureV5BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - TestUtils.loadPubkey(address); - ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload"); - V5Broadcast broadcast = (V5Broadcast) objectMessage.getPayload(); - broadcast.decrypt(address); - assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject()); - assertTrue(objectMessage.isSignatureValid(address.getPubkey())); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt new file mode 100644 index 0000000..dd9bc95 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2017 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.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.V4Broadcast +import ch.dissem.bitmessage.entity.payload.V5Broadcast +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class DecryptionTest : TestBase() { + @Test + fun `ensure V4Broadcast is decrypted correctly`() { + val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + TestUtils.loadPubkey(address) + val objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload") + val broadcast = objectMessage.payload as V4Broadcast + broadcast.decrypt(address) + assertEquals("Test-Broadcast", broadcast.plaintext?.subject) + assertTrue(objectMessage.isSignatureValid(address.pubkey!!)) + } + + @Test + fun `ensure V5Broadcast is decrypted correctly`() { + val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + TestUtils.loadPubkey(address) + val objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload") + val broadcast = objectMessage.payload as V5Broadcast + broadcast.decrypt(address) + assertEquals("Test-Broadcast", broadcast.plaintext?.subject) + assertTrue(objectMessage.isSignatureValid(address.pubkey!!)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java deleted file mode 100644 index 32a20e4..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.Broadcast; -import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Collections; - -import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED; -import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.MessageMatchers.plaintext; -import static org.mockito.Mockito.*; - -/** - * @author Christian Basler - */ -public class DefaultMessageListenerTest extends TestBase { - @Mock - private AddressRepository addressRepo; - @Mock - private MessageRepository messageRepo; - @Mock - private Inventory inventory; - @Mock - private NetworkHandler networkHandler; - - private InternalContext ctx; - private DefaultMessageListener listener; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - ctx = mock(InternalContext.class); - Singleton.initialize(new BouncyCryptography()); - when(ctx.getAddressRepository()).thenReturn(addressRepo); - 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(mock(Labeler.class), mock(BitmessageContext.Listener.class)); - when(ctx.getNetworkListener()).thenReturn(listener); - listener.setContext(ctx); - } - - @Test - public void ensurePubkeyIsSentOnRequest() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - when(addressRepo.findIdentity(any(byte[].class))) - .thenReturn(identity); - listener.receive(new ObjectMessage.Builder() - .stream(2) - .payload(new GetPubkey(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) - .build()); - verify(ctx).sendPubkey(eq(identity), eq(2L)); - } - - @Test - public void ensureIncomingPubkeyIsAddedToContact() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress contact = new BitmessageAddress(identity.getAddress()); - when(addressRepo.findContact(any(byte[].class))) - .thenReturn(contact); - when(messageRepo.findMessages(eq(PUBKEY_REQUESTED), eq(contact))) - .thenReturn(Collections.singletonList( - new Plaintext.Builder(MSG).from(identity).to(contact).message("S", "T").build() - )); - - ObjectMessage objectMessage = new ObjectMessage.Builder() - .stream(2) - .payload(identity.getPubkey()) - .build(); - objectMessage.sign(identity.getPrivateKey()); - objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.getPublicDecryptionKey())); - listener.receive(objectMessage); - - verify(addressRepo).save(any(BitmessageAddress.class)); - } - - @Test - public void ensureIncomingMessageIsSaved() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress contact = new BitmessageAddress(identity.getAddress()); - contact.setPubkey(identity.getPubkey()); - - when(addressRepo.getIdentities()).thenReturn(Collections.singletonList(identity)); - - ObjectMessage objectMessage = new ObjectMessage.Builder() - .stream(2) - .payload(new Msg(new Plaintext.Builder(MSG) - .from(identity) - .to(contact) - .message("S", "T") - .build())) - .nonce(new byte[8]) - .build(); - objectMessage.sign(identity.getPrivateKey()); - objectMessage.encrypt(identity.getPubkey()); - - listener.receive(objectMessage); - - verify(messageRepo, atLeastOnce()).save(plaintext(MSG)); - } - - @Test - public void ensureIncomingBroadcastIsSaved() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - - when(addressRepo.getSubscriptions(anyLong())).thenReturn(Collections.singletonList(identity)); - - Broadcast broadcast = Factory.getBroadcast(new Plaintext.Builder(BROADCAST) - .from(identity) - .message("S", "T") - .build()); - ObjectMessage objectMessage = new ObjectMessage.Builder() - .stream(2) - .payload(broadcast) - .nonce(new byte[8]) - .build(); - objectMessage.sign(identity.getPrivateKey()); - broadcast.encrypt(); - - listener.receive(objectMessage); - - verify(messageRepo, atLeastOnce()).save(plaintext(BROADCAST)); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt new file mode 100644 index 0000000..dc30105 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2017 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.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.GetPubkey +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import com.nhaarman.mockito_kotlin.* +import org.junit.Before +import org.junit.Test + +/** + * @author Christian Basler + */ +class DefaultMessageListenerTest : TestBase() { + private lateinit var listener: DefaultMessageListener + + private val ctx = TestUtils.mockedInternalContext( + cryptography = Singleton.cryptography() + ) + + @Before + fun setUp() { + listener = ctx.networkListener as DefaultMessageListener + } + + @Test + fun `ensure pubkey is sent on request`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + whenever(ctx.addressRepository.findIdentity(any())).thenReturn(identity) + listener.receive(ObjectMessage.Builder() + .stream(2) + .payload(GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) + .build()) + verify(ctx.proofOfWorkRepository).putObject(argThat { type == ObjectType.PUBKEY.number }, any(), any()) + } + + @Test + fun `ensure incoming pubkey is added to contact`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val contact = BitmessageAddress(identity.address) + whenever(ctx.addressRepository.findContact(isA())).thenReturn(contact) + whenever(ctx.messageRepository.findMessages(eq(PUBKEY_REQUESTED), eq(contact))) + .thenReturn(listOf(Plaintext.Builder(MSG).from(identity).to(contact).message("S", "T").build())) + + val objectMessage = ObjectMessage.Builder() + .stream(2) + .payload(identity.pubkey!!) + .build() + objectMessage.sign(identity.privateKey!!) + objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.publicDecryptionKey)) + listener.receive(objectMessage) + + verify(ctx.addressRepository).save(eq(contact)) + } + + @Test + fun `ensure incoming message is saved`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val contact = BitmessageAddress(identity.address) + contact.pubkey = identity.pubkey + + whenever(ctx.addressRepository.getIdentities()).thenReturn(listOf(identity)) + + val objectMessage = ObjectMessage.Builder() + .stream(2) + .payload(Msg(Plaintext.Builder(MSG) + .from(identity) + .to(contact) + .message("S", "T") + .build())) + .nonce(ByteArray(8)) + .build() + objectMessage.sign(identity.privateKey!!) + objectMessage.encrypt(identity.pubkey!!) + + listener.receive(objectMessage) + + verify(ctx.messageRepository, atLeastOnce()).save(argThat { type == MSG }) + } + + @Test + fun `ensure incoming broadcast is saved`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + + whenever(ctx.addressRepository.getSubscriptions(any())).thenReturn(listOf(identity)) + + val broadcast = Factory.getBroadcast(Plaintext.Builder(BROADCAST) + .from(identity) + .message("S", "T") + .build()) + val objectMessage = ObjectMessage.Builder() + .stream(2) + .payload(broadcast) + .nonce(ByteArray(8)) + .build() + objectMessage.sign(identity.privateKey!!) + broadcast.encrypt() + + listener.receive(objectMessage) + + verify(ctx.messageRepository, atLeastOnce()).save(argThat { type == BROADCAST }) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java deleted file mode 100644 index 0a6ee25..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 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.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.CryptoBox; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.IOException; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class EncryptionTest extends TestBase { - @Test - public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException { - GenericPayload before = new GenericPayload(0, 1, cryptography().randomBytes(100)); - - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey()); - - GenericPayload after = GenericPayload.read(0, 1, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 100); - - assertEquals(before, after); - } - - @Test - public void ensureMessageCanBeDecrypted() throws IOException, DecryptionFailedException { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); - BitmessageAddress identity = new BitmessageAddress(privateKey); - assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.getAddress()); - - ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload"); - Msg msg = (Msg) object.getPayload(); - msg.decrypt(privateKey.getPrivateEncryptionKey()); - Plaintext plaintext = msg.getPlaintext(); - assertNotNull(plaintext); - assertEquals("Test", plaintext.getSubject()); - assertEquals("Hallo, das ist ein Test von der v4-Adresse", plaintext.getText()); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt new file mode 100644 index 0000000..657b468 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2017 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.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.CryptoBox +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +class EncryptionTest : TestBase() { + @Test + fun `ensure decrypted data is same as before encryption`() { + val before = GenericPayload(0, 1, cryptography().randomBytes(100)) + + val privateKey = PrivateKey(false, 1, 1000, 1000) + val cryptoBox = CryptoBox(before, privateKey.pubkey.encryptionKey) + + val after = GenericPayload.read(0, 1, cryptoBox.decrypt(privateKey.privateEncryptionKey), 100) + + assertEquals(before, after) + } + + @Test + fun `ensure message can be decrypted`() { + val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) + val identity = BitmessageAddress(privateKey) + assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.address) + + val (_, _, payload) = TestUtils.loadObjectMessage(3, "V1Msg.payload") + val msg = payload as Msg + msg.decrypt(privateKey.privateEncryptionKey) + assertNotNull(msg.plaintext) + assertEquals("Test", msg.plaintext?.subject) + assertEquals("Hallo, das ist ein Test von der v4-Adresse", msg.plaintext?.text) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java deleted file mode 100644 index 039e7ad..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.Msg; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.*; - -/** - * @author Christian Basler - */ -public class ProofOfWorkServiceTest { - private ProofOfWorkService proofOfWorkService; - - private Cryptography cryptography; - @Mock - private InternalContext ctx; - @Mock - private ProofOfWorkRepository proofOfWorkRepo; - @Mock - private Inventory inventory; - @Mock - private NetworkHandler networkHandler; - @Mock - private MessageRepository messageRepo; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - cryptography = spy(new BouncyCryptography()); - Singleton.initialize(cryptography); - - ctx = mock(InternalContext.class); - when(ctx.getProofOfWorkRepository()).thenReturn(proofOfWorkRepo); - when(ctx.getInventory()).thenReturn(inventory); - when(ctx.getNetworkHandler()).thenReturn(networkHandler); - when(ctx.getMessageRepository()).thenReturn(messageRepo); - when(ctx.getLabeler()).thenReturn(mock(Labeler.class)); - when(ctx.getNetworkListener()).thenReturn(mock(NetworkHandler.MessageListener.class)); - - proofOfWorkService = new ProofOfWorkService(); - proofOfWorkService.setContext(ctx); - } - - @Test - public void ensureMissingProofOfWorkIsDone() { - when(proofOfWorkRepo.getItems()).thenReturn(Arrays.asList(new byte[64])); - when(proofOfWorkRepo.getItem(any(byte[].class))).thenReturn(new ProofOfWorkRepository.Item(null, 1001, 1002)); - doNothing().when(cryptography).doProofOfWork(any(ObjectMessage.class), anyLong(), anyLong(), any(ProofOfWorkEngine.Callback.class)); - - proofOfWorkService.doMissingProofOfWork(10); - - verify(cryptography, timeout(1000)).doProofOfWork((ObjectMessage) isNull(), eq(1001L), eq(1002L), - any(ProofOfWorkEngine.Callback.class)); - } - - @Test - public void ensureCalculatedNonceIsStored() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress address = TestUtils.loadContact(); - Plaintext plaintext = new Plaintext.Builder(MSG).from(identity).to(address).message("", "").build(); - ObjectMessage object = new ObjectMessage.Builder() - .payload(new Msg(plaintext)) - .build(); - object.sign(identity.getPrivateKey()); - object.encrypt(address.getPubkey()); - byte[] initialHash = new byte[64]; - byte[] nonce = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; - - when(proofOfWorkRepo.getItem(initialHash)).thenReturn(new ProofOfWorkRepository.Item(object, 1001, 1002)); - when(messageRepo.getMessage(initialHash)).thenReturn(plaintext); - - proofOfWorkService.onNonceCalculated(initialHash, nonce); - - verify(proofOfWorkRepo).removeObject(eq(initialHash)); - verify(inventory).storeObject(eq(object)); - verify(networkHandler).offer(eq(object.getInventoryVector())); - assertThat(plaintext.getInventoryVector(), equalTo(object.getInventoryVector())); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt new file mode 100644 index 0000000..23a31a9 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2017 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.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.ports.Cryptography +import ch.dissem.bitmessage.ports.ProofOfWorkRepository +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestUtils +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.CoreMatchers.equalTo +import org.junit.Assert.assertThat +import org.junit.Before +import org.junit.Test +import java.util.* +import kotlin.properties.Delegates + +/** + * @author Christian Basler + */ +class ProofOfWorkServiceTest { + private var cryptography by Delegates.notNull<Cryptography>() + private var ctx by Delegates.notNull<InternalContext>() + + private var obj by Delegates.notNull<ObjectMessage>() + + @Before + fun setUp() { + cryptography = spy(BouncyCryptography()) + Singleton.initialize(cryptography) + + ctx = TestUtils.mockedInternalContext( + cryptography = cryptography + ) + + obj = ObjectMessage( + expiresTime = 0, + stream = 1, + payload = GenericPayload(1, 1, kotlin.ByteArray(0)), + type = 42, + version = 42 + ) + } + + @Test + fun `ensure missing proof of work is done`() { + whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64))) + whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002)) + doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any()) + + ctx.proofOfWorkService.doMissingProofOfWork(10) + + verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any()) + } + + @Test + fun `ensure calculated nonce is stored`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val address = TestUtils.loadContact() + val plaintext = Plaintext.Builder(MSG).from(identity).to(address).message("", "").build() + val `object` = ObjectMessage( + expiresTime = 0, + stream = 1, + payload = Msg(plaintext) + ) + `object`.sign(identity.privateKey!!) + `object`.encrypt(address.pubkey!!) + val initialHash = ByteArray(64) + val nonce = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8) + + whenever(ctx.proofOfWorkRepository.getItem(initialHash)).thenReturn(ProofOfWorkRepository.Item(`object`, 1001, 1002)) + whenever(ctx.messageRepository.getMessage(initialHash)).thenReturn(plaintext) + + ctx.proofOfWorkService.onNonceCalculated(initialHash, nonce) + + verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash)) + verify(ctx.inventory).storeObject(eq(`object`)) + verify(ctx.networkHandler).offer(eq(`object`.inventoryVector)) + assertThat(plaintext.inventoryVector, equalTo(`object`.inventoryVector)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java deleted file mode 100644 index 3566a5f..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015 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.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.*; - -public class SignatureTest extends TestBase { - @Test - public void ensureValidationWorks() throws IOException { - ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - assertTrue(object.isSignatureValid(pubkey)); - } - - @Test - public void ensureSigningWorks() throws IOException { - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - - ObjectMessage objectMessage = new ObjectMessage.Builder() - .objectType(ObjectType.PUBKEY) - .stream(1) - .payload(privateKey.getPubkey()) - .build(); - objectMessage.sign(privateKey); - - assertTrue(objectMessage.isSignatureValid(privateKey.getPubkey())); - } - - @Test - public void ensureMessageIsProperlySigned() throws IOException, DecryptionFailedException { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - - ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload"); - Msg msg = (Msg) object.getPayload(); - msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); - Plaintext plaintext = msg.getPlaintext(); - assertEquals(TestUtils.loadContact().getPubkey(), plaintext.getFrom().getPubkey()); - assertNotNull(plaintext); - assertTrue(object.isSignatureValid(plaintext.getFrom().getPubkey())); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt new file mode 100644 index 0000000..5fe9f7a --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2017 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.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Assert.* +import org.junit.Test + +class SignatureTest : TestBase() { + @Test + fun `ensure validation works`() { + val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") + val pubkey = `object`.payload as Pubkey + assertTrue(`object`.isSignatureValid(pubkey)) + } + + @Test + fun `ensure signing works`() { + val privateKey = PrivateKey(false, 1, 1000, 1000) + + val objectMessage = ObjectMessage.Builder() + .objectType(ObjectType.PUBKEY) + .stream(1) + .payload(privateKey.pubkey) + .build() + objectMessage.sign(privateKey) + + assertTrue(objectMessage.isSignatureValid(privateKey.pubkey)) + } + + @Test + fun `ensure message is properly signed`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + + val `object` = TestUtils.loadObjectMessage(3, "V1Msg.payload") + val msg = `object`.payload as Msg + msg.decrypt(identity.privateKey!!.privateEncryptionKey) + assertNotNull(msg.plaintext) + assertEquals(TestUtils.loadContact().pubkey, msg.plaintext!!.from.pubkey) + assertTrue(`object`.isSignatureValid(msg.plaintext!!.from.pubkey!!)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java deleted file mode 100644 index fced84f..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.*; -import org.junit.Test; - -import java.io.IOException; -import java.util.Arrays; - -import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; -import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.*; - -public class BitmessageAddressTest extends TestBase { - @Test - public void ensureFeatureFlagIsCalculatedCorrectly() { - assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK)); - assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION)); - assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION)); - } - - @Test - public void ensureBase58DecodesCorrectly() { - assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D", - Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")); - } - - @Test - public void ensureAddressStaysSame() { - String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"; - assertEquals(address, new BitmessageAddress(address).toString()); - } - - @Test - public void ensureStreamAndVersionAreParsed() { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - assertEquals(1, address.getStream()); - assertEquals(3, address.getVersion()); - - address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e"); - assertEquals(1, address.getStream()); - assertEquals(4, address.getVersion()); - } - - @Test - public void ensureIdentityCanBeCreated() { - BitmessageAddress address = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); - assertNotNull(address.getPubkey()); - assertTrue(address.has(DOES_ACK)); - } - - @Test - public void ensureV2PubkeyCanBeImported() throws IOException { - ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - BitmessageAddress address = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); - try { - address.setPubkey(pubkey); - } catch (Exception e) { - fail(e.getMessage()); - } - } - - @Test - public void ensureV3PubkeyCanBeImported() throws IOException { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe()); - - ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - assertTrue(object.isSignatureValid(pubkey)); - try { - address.setPubkey(pubkey); - } catch (Exception e) { - fail(e.getMessage()); - } - - assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.getRipe()); - assertTrue(address.has(DOES_ACK)); - } - - @Test - public void ensureV4PubkeyCanBeImported() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); - object.decrypt(address.getPublicDecryptionKey()); - V4Pubkey pubkey = (V4Pubkey) object.getPayload(); - assertTrue(object.isSignatureValid(pubkey)); - try { - address.setPubkey(pubkey); - } catch (Exception e) { - fail(e.getMessage()); - } - assertTrue(address.has(DOES_ACK)); - } - - @Test - public void ensureV3IdentityCanBeImported() throws IOException { - String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"; - assertEquals(3, new BitmessageAddress(address_string).getVersion()); - assertEquals(1, new BitmessageAddress(address_string).getStream()); - - byte[] privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); - byte[] privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck"); - - System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n"); - - BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, - cryptography().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); - assertEquals(address_string, address.getAddress()); - } - - @Test - public void ensureV4IdentityCanBeImported() throws IOException { - assertEquals(4, new BitmessageAddress("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke").getVersion()); - byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU"); - byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz"); - BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, - cryptography().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000))); - assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress()); - } - - private void assertHexEquals(String hex, byte[] bytes) { - assertEquals(hex.toLowerCase(), Strings.hex(bytes).toString().toLowerCase()); - } - - private byte[] getSecret(String walletImportFormat) throws IOException { - byte[] bytes = Base58.decode(walletImportFormat); - if (bytes[0] != (byte) 0x80) - throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]); - if (bytes.length != 37) - throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long"); - - byte[] hash = cryptography().doubleSha256(bytes, 33); - for (int i = 0; i < 4; i++) { - if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat); - } - return Arrays.copyOfRange(bytes, 1, 33); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt new file mode 100644 index 0000000..1177ea2 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt @@ -0,0 +1,159 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.* +import org.junit.Assert +import org.junit.Assert.* +import org.junit.Test +import java.io.IOException +import java.util.* + +class BitmessageAddressTest : TestBase() { + @Test + fun `ensure feature flag is calculated correctly`() { + Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK).toLong()) + assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION).toLong()) + assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION).toLong()) + } + + @Test + fun `ensure base58 decodes correctly`() { + assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D", + Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")) + } + + @Test + fun `ensure address stays same`() { + val address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ" + assertEquals(address, BitmessageAddress(address).toString()) + } + + @Test + fun `ensure stream and version are parsed`() { + var address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + assertEquals(1, address.stream) + assertEquals(3, address.version) + + address = BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e") + assertEquals(1, address.stream) + assertEquals(4, address.version) + } + + @Test + fun `ensure identity can be created`() { + val address = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)) + assertNotNull(address.pubkey) + assertTrue(address.has(DOES_ACK)) + } + + @Test + fun `ensure V2Pubkey can be imported`() { + val (_, _, payload) = TestUtils.loadObjectMessage(2, "V2Pubkey.payload") + val pubkey = payload as Pubkey + val address = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + try { + address.pubkey = pubkey + } catch (e: Exception) { + fail(e.message) + } + + } + + @Test + fun `ensure V3Pubkey can be imported`() { + val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe) + + val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") + val pubkey = `object`.payload as Pubkey + assertTrue(`object`.isSignatureValid(pubkey)) + try { + address.pubkey = pubkey + } catch (e: Exception) { + fail(e.message) + } + + assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe) + assertTrue(address.has(DOES_ACK)) + } + + @Test + fun `ensure V4Pubkey can be imported`() { + val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + val `object` = TestUtils.loadObjectMessage(4, "V4Pubkey.payload") + `object`.decrypt(address.publicDecryptionKey) + val pubkey = `object`.payload as V4Pubkey + assertTrue(`object`.isSignatureValid(pubkey)) + try { + address.pubkey = pubkey + } catch (e: Exception) { + fail(e.message) + } + + assertTrue(address.has(DOES_ACK)) + } + + @Test + fun `ensure V3 identity can be imported`() { + val address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn" + assertEquals(3, BitmessageAddress(address_string).version) + assertEquals(1, BitmessageAddress(address_string).stream) + + val privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9") + val privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck") + + println("\n\n" + Strings.hex(privsigningkey) + "\n\n") + + val address = BitmessageAddress(PrivateKey(privsigningkey, privencryptionkey, + Singleton.cryptography().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))) + assertEquals(address_string, address.address) + } + + @Test + fun `ensure V4 identity can be imported`() { + assertEquals(4, BitmessageAddress("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke").version) + val privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU") + val privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz") + val address = BitmessageAddress(PrivateKey(privsigningkey, privencryptionkey, + Singleton.cryptography().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000))) + assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.address) + } + + private fun assertHexEquals(hex: String, bytes: ByteArray) { + assertEquals(hex.toLowerCase(), Strings.hex(bytes).toLowerCase()) + } + + private fun getSecret(walletImportFormat: String): ByteArray { + val bytes = Base58.decode(walletImportFormat) + if (bytes[0] != 0x80.toByte()) + throw IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]) + if (bytes.size != 37) + throw IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.size + " long") + + val hash = Singleton.cryptography().doubleSha256(bytes, 33) + for (i in 0..3) { + if (hash[i] != bytes[33 + i]) throw IOException("Hash check failed for secret " + walletImportFormat) + } + return Arrays.copyOfRange(bytes, 1, 33) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java deleted file mode 100644 index c417df1..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.extended.Attachment; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.entity.valueobject.extended.Vote; -import ch.dissem.bitmessage.factory.ExtendedEncodingFactory; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.UnsupportedEncodingException; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; - -/** - * @author Christian Basler - */ -public class ExtendedEncodingTest { - private ExtendedEncodingFactory extendedEncodingFactory = ExtendedEncodingFactory.getInstance(); - - @Test - public void ensureSimpleMessageIsDecoded() { - ExtendedEncoding extended = extendedEncodingFactory.unzip(Bytes.fromHex("78da9d59dd8e1bb715ee359fa097c4b65b498876babb76d0760bd970368eedb68e8daed3c0700c81d25012b333c3e99063ad1c0430d0a728d002bd289077caa59fa4df39244723693705b2f0da339c730ebff3c3f343ffe33f3f94da39b5d4ff9dd97cf3e32f9d10524afe4b3a2b57762dfd4acb453bbf967355c967b2755a1a2f1f0afe791eb85f2a7c374e2ab950ce8fe5dc96b59a7b3933956a36d2e9c6a8c2bc57ded84a2e6c532a10b9d678352b342d48674a53a846e6ca2be9adfcd3d58b2f33f96a05a190748d3d64ddd87726d74e5ebedcf81504417a6eaaa563018d56f4225595cb75633c3df7d191e04c3cab9c574521c4af656d6a69c2ab2cdd927639a959b0102f372f3742ecae6ef7afdb0668c2a229eb4297baf2ac5b2689932d4576f2809f89af01d3ae61d9af57ba921bdbd2f78f1ffee999269a2837ce3766d692943113555ae7648984f16fc6b5aa9057becd8d95d038ca95579fff5902497ccdf00064ad97fac6ebcab1bcd69139eec64dfcc9aa4d5b39e90abb2e3699105f60a3f8e13cfbdd583e37f3c63abbf009cfe5279fc84bb8db14ba61476ca929221a8d58c03e399471b660fd76c5decb3ebd456c54f3fcf4ec53125fb695f11bf93837016e231fdfd40ddcdb2d91cd679a4cca569bb5a6c8b72690a5cddb4267e22945b42532f1a2d2276e653d0798fc8d6c2b7a10e22b78859e66ac0e3da5b80a14613d3ce34b96a2671b20795bd68e390aab723ce14f615408543e1bdecc4c411aade12cf9ad034222afcdfc9a500a96472b248a313882bd80954f0a73ada59d7dabe73e8b30babde23b766a6c79482ec483070fc8f7b6f109362fc5e78cf51bbe391bcbf3b1bcf7762406dfdcfce1de3737a767f83dc7efbdc10e7db4c8703a121d93101154c2128911f2b043d3a80da912bccf6b85a19c81d80f07273083c4b788d10b71eb86b3436014e67a4ac2265fa8c2e99118464823215ee344c1d92d8242156bb5a1b482e8a1e496b8e4b5deac6d038266d9d2d1c8e4954628e88633563547e273aed514d485e20413222970db9ae370a6717460e8bf221fb1f8dccee968839abc6fb1d4445ae4862b8fbc55d2c72e9e1080fc0832cea81f3ffccbed51e9e6e3877f67f299df9ab82dbc81b5a2a3a3ff6da565e09543eccd6bb38da7141642951036b65dae90d2c14319a7d4f04b3e82067b61c2dc389271f93392f3ec8510b3762127e9753812a4a541d6928daa967a78767a3abaa07a02ba8c12b31eee065b2033a3d18865654eebebe1e9284511cc30e9bc9f2c330461d82912e5b46162e0eda0a2a97cf71d393d98f7b79da1a55dc879eb3cd40a5567536b216054327b81ea575be70c1528b8997822eb011372ee53dd68e6ab907314e556f63648b437a5ceba87ceac69e5c0ce0211b5688b696e504027f23b56e6c8e44717f26c1c5ee670a9d7b472b84165d7c3d1587c2f44ae173247facdf5347d1d223c8237a459c8d960da7d994e0764427c0f9fe9072f0070b80542aa4ed2de1c2937c5c2d1dbb13c3a7e7d5c1ee7af8e9f5e1c3fbf38beca8e17472396d668df362c3dc0d2d54fc0328e4b1ece1bad8f0f018cb610a3e0ef7635b990af9a568fe52060c33b0411ec05ef762bceef0f808a1039c911bb51db731210ea85c2019ceca93512d40030cd542d15cc3b39c863bd3dc6f1f04e57d65e4ff63c37daa605ce993d5264cda2985160a2d86af34e53d620817fe4fc13296b651ab7475f221593ad2955f56a6714032921b12d28339ebc53454b85116232f1980a2be70f8aff9f776a6257421851a779edee021597b87cf042886eb67b0c9f2ccbe41d21c46c19ff3de25a49f1401b928de5642207f9602ba01708c95dd0f715c887f7cfc781d7869c3e44d64a6c8d32e81c88ec71d3d86678f455758dd358b16617f2b83992c792f104a64e0d68cf8e19121a8e77b5ab4e4279ff7c17a44240f5541b428bd12e4146593b4265b9b729a9b62a8485a46d0fcf1630f9ef60dbb17c739651a9cdeea36920ba9896f7cf0db16f0f4cfc377074a9fcae5332ee2c35490fa31ea6c936d9d3f117e251fe8e4220dfd65658b2f28d45ffff8873b52abc6e2ad4f2771cae284e8d0a1d78578353418d1533cac2f4726dea314f1d53b6c2748547d05374f16aa9eab4166aaaa37986eb428935bfb64cc725a3f206f5238e62a14ed3b108b59b7671085542596052d0719c72a92d0d5e4b6709c430e9987a64b3ac2c771ec687bdd1b6f8b877c4945c4e9c555bce48634464980d1c95046e916a74adc97e4864ba19d378e8570a63869aaf1203d19359c9208c603771f067641d182eb5ebc9e73402c2a23557f2dcc0d498027802794cf28109389cee6053ea0a5d1491f1c4a7196a94c7218139954cccadb3daa6bd455bcd8913ade55d4d0d67973492e961ea2cc672a9fd144d229e6315aa487527bbe06bb23def0f4309dc6b8b025baf92fd0acd660127036890cf1ca4d1127eafc8921d2d9efb1b8687b80ffd4401931eda2178466267b72af4a7c13d34a224dba53884d50273c7c6cd636828fb2d5ffadc2122570eb7c4a1efdbc7874eafcafb5440cb6028a98a2f2dd645ec9c29cae3a01cbb34d4e882bcbc71e36efecaad7661b026d781af356e256f17415307e6a238839e85e3411fe265020d9714d07771a3da55aa84b11a45dd7e824061fdff37e73d501a32f919861ec3b5b3c220da87749e754f52e5396731c38520c80cbc69df467768c86d20e13d03e3cc841ac41303b69ae9b9a2ab876badebdbc6513e3bb1c784cbd9d28599217f91f8b572a153e0f8a4ca5dda86ce9eaaa28eb0c765db00922f9005d62b4ca17b372cd2b5351d38a8add71dbeae2e48a73dcf575b8fc66b271e89b96a3b06032c61a0496d0c1245b2d390ac00d1e99e81669ad7e1f645ce57740af98286340c9d088d72c032252c132a2169a62321a11ad0fedce5817e8221acf58b93dfe3df1e65aa1c77f7327794c637b381ab554973ec402f9783b7e34344a35b87e1ae3c266403c68566e0cd9e482152ab25c42b1e5ebbde6b1c26e2bd8e27d6bfd4ccfd0cb56e6ba166839bcdfbf79bc1e8a7f419897e1f32213e8acc49e2dd7a33064708de1842eca9d82584413e4ee694583876f742f2343bcf6e4854837e96b2c1c1f77b72a5d028385b62bb2a1d1c04778826b4c5cc971084c44a0179d705031d468e332e43d82093bd7b8aee82a223d7377561e606c78a15a062b65690b77386b3eda4d05506b606e38ecd782aa34842a16f75f245eb5f2c3ee7b2b93d4b57ded6cf525794edbe12fa2e1184d689eecb1aebeddc16d25654bed9da6a46f791bd8b14f1e41239a27705f4e412394b514258d325e9b2b16bce9b4561e79c86d27d17df08975a55aed7d5513310521a7e0bba157c72194c99a26339cf9096e9b619c6e02db6dc05ecdb355f99d8bbd011e22ff462822bb609eaefada61b214ee73054d0252465beb7228ec22c57dc6f516e2499bba076afab62a2dce26298bbb74ff1d8a98aaef27ae6e311b57fabcc0a203952a740beefdf849351d7e9266e119a3452012d4bcd85650f57a4df02732d3ab2642ff9a80a775aeb70afb74716d38749d3dec1349a891f5ccb8b3ffee2ac1b2b5326eb5d6c73d7e0a43c917c5548ff35f1f0e1ff00b5629026")); - assertThat(extended, instanceOf(ExtendedEncoding.class)); - assertThat(extended.getContent(), instanceOf(Message.class)); - assertThat(((Message) extended.getContent()).getSubject(), is("Extended encoding on Windows works - but how ??")); - assertThat(((Message) extended.getContent()).getBody(), notNullValue()); - assertThat(((Message) extended.getContent()).getBody().length(), is(6233)); - } - - - @Test - public void ensureSimpleMessageIsEncoded() { - ExtendedEncoding in = new Message.Builder() - .subject("Test sübject") - .body("test bödy") - .build(); - - assertThat(in.zip(), notNullValue()); - - ExtendedEncoding out = extendedEncodingFactory.unzip(in.zip()); - assertThat(out, is(in)); - } - - @Test - public void ensureCompleteMessageIsEncodedAndDecoded() throws UnsupportedEncodingException { - ExtendedEncoding in = new Message.Builder() - .addParent(TestUtils.randomInventoryVector()) - .addParent(TestUtils.randomInventoryVector()) - .subject("Test sübject") - .body("test bödy") - .addFile( - new Attachment.Builder() - .name("test.txt") - .type("text/plain") - .data("test".getBytes("UTF-8")) - .attachment() - .build() - ) - .build(); - - assertThat(in.zip(), notNullValue()); - - ExtendedEncoding out = extendedEncodingFactory.unzip(in.zip()); - assertThat(out, is(in)); - } - - @Test - public void ensureVoteIsEncodedAndDecoded() { - ExtendedEncoding in = new Vote.Builder() - .msgId(TestUtils.randomInventoryVector()) - .vote("+1") - .build(); - - assertThat(in.zip(), notNullValue()); - - ExtendedEncoding out = extendedEncodingFactory.unzip(in.zip()); - assertThat(out, is(in)); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt new file mode 100644 index 0000000..bc4e8d6 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.extended.Attachment +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.entity.valueobject.extended.Vote +import ch.dissem.bitmessage.factory.ExtendedEncodingFactory +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.TestUtils +import org.hamcrest.Matchers.* +import org.junit.Assert.assertThat +import org.junit.Test + +/** + * @author Christian Basler + */ +class ExtendedEncodingTest { + @Test + fun `ensure simple message is decoded`() { + val extended = ExtendedEncodingFactory.unzip(Bytes.fromHex("78da9d59dd8e1bb715ee359fa097c4b65b498876babb76d0760bd970368eedb68e8daed3c0700c81d25012b333c3e99063ad1c0430d0a728d002bd289077caa59fa4df39244723693705b2f0da339c730ebff3c3f343ffe33f3f94da39b5d4ff9dd97cf3e32f9d10524afe4b3a2b57762dfd4acb453bbf967355c967b2755a1a2f1f0afe791eb85f2a7c374e2ab950ce8fe5dc96b59a7b3933956a36d2e9c6a8c2bc57ded84a2e6c532a10b9d678352b342d48674a53a846e6ca2be9adfcd3d58b2f33f96a05a190748d3d64ddd87726d74e5ebedcf81504417a6eaaa563018d56f4225595cb75633c3df7d191e04c3cab9c574521c4af656d6a69c2ab2cdd927639a959b0102f372f3742ecae6ef7afdb0668c2a229eb4297baf2ac5b2689932d4576f2809f89af01d3ae61d9af57ba921bdbd2f78f1ffee999269a2837ce3766d692943113555ae7648984f16fc6b5aa9057becd8d95d038ca95579fff5902497ccdf00064ad97fac6ebcab1bcd69139eec64dfcc9aa4d5b39e90abb2e3699105f60a3f8e13cfbdd583e37f3c63abbf009cfe5279fc84bb8db14ba61476ca929221a8d58c03e399471b660fd76c5decb3ebd456c54f3fcf4ec53125fb695f11bf93837016e231fdfd40ddcdb2d91cd679a4cca569bb5a6c8b72690a5cddb4267e22945b42532f1a2d2276e653d0798fc8d6c2b7a10e22b78859e66ac0e3da5b80a14613d3ce34b96a2671b20795bd68e390aab723ce14f615408543e1bdecc4c411aade12cf9ad034222afcdfc9a500a96472b248a313882bd80954f0a73ada59d7dabe73e8b30babde23b766a6c79482ec483070fc8f7b6f109362fc5e78cf51bbe391bcbf3b1bcf7762406dfdcfce1de3737a767f83dc7efbdc10e7db4c8703a121d93101154c2128911f2b043d3a80da912bccf6b85a19c81d80f07273083c4b788d10b71eb86b3436014e67a4ac2265fa8c2e99118464823215ee344c1d92d8242156bb5a1b482e8a1e496b8e4b5deac6d038266d9d2d1c8e4954628e88633563547e273aed514d485e20413222970db9ae370a6717460e8bf221fb1f8dccee968839abc6fb1d4445ae4862b8fbc55d2c72e9e1080fc0832cea81f3ffccbed51e9e6e3877f67f299df9ab82dbc81b5a2a3a3ff6da565e09543eccd6bb38da7141642951036b65dae90d2c14319a7d4f04b3e82067b61c2dc389271f93392f3ec8510b3762127e9753812a4a541d6928daa967a78767a3abaa07a02ba8c12b31eee065b2033a3d18865654eebebe1e9284511cc30e9bc9f2c330461d82912e5b46162e0eda0a2a97cf71d393d98f7b79da1a55dc879eb3cd40a5567536b216054327b81ea575be70c1528b8997822eb011372ee53dd68e6ab907314e556f63648b437a5ceba87ceac69e5c0ce0211b5688b696e504027f23b56e6c8e44717f26c1c5ee670a9d7b472b84165d7c3d1587c2f44ae173247facdf5347d1d223c8237a459c8d960da7d994e0764427c0f9fe9072f0070b80542aa4ed2de1c2937c5c2d1dbb13c3a7e7d5c1ee7af8e9f5e1c3fbf38beca8e17472396d668df362c3dc0d2d54fc0328e4b1ece1bad8f0f018cb610a3e0ef7635b990af9a568fe52060c33b0411ec05ef762bceef0f808a1039c911bb51db731210ea85c2019ceca93512d40030cd542d15cc3b39c863bd3dc6f1f04e57d65e4ff63c37daa605ce993d5264cda2985160a2d86af34e53d620817fe4fc13296b651ab7475f221593ad2955f56a6714032921b12d28339ebc53454b85116232f1980a2be70f8aff9f776a6257421851a779edee021597b87cf042886eb67b0c9f2ccbe41d21c46c19ff3de25a49f1401b928de5642207f9602ba01708c95dd0f715c887f7cfc781d7869c3e44d64a6c8d32e81c88ec71d3d86678f455758dd358b16617f2b83992c792f104a64e0d68cf8e19121a8e77b5ab4e4279ff7c17a44240f5541b428bd12e4146593b4265b9b729a9b62a8485a46d0fcf1630f9ef60dbb17c739651a9cdeea36920ba9896f7cf0db16f0f4cfc377074a9fcae5332ee2c35490fa31ea6c936d9d3f117e251fe8e4220dfd65658b2f28d45ffff8873b52abc6e2ad4f2771cae284e8d0a1d78578353418d1533cac2f4726dea314f1d53b6c2748547d05374f16aa9eab4166aaaa37986eb428935bfb64cc725a3f206f5238e62a14ed3b108b59b7671085542596052d0719c72a92d0d5e4b6709c430e9987a64b3ac2c771ec687bdd1b6f8b877c4945c4e9c555bce48634464980d1c95046e916a74adc97e4864ba19d378e8570a63869aaf1203d19359c9208c603771f067641d182eb5ebc9e73402c2a23557f2dcc0d498027802794cf28109389cee6053ea0a5d1491f1c4a7196a94c7218139954cccadb3daa6bd455bcd8913ade55d4d0d67973492e961ea2cc672a9fd144d229e6315aa487527bbe06bb23def0f4309dc6b8b025baf92fd0acd660127036890cf1ca4d1127eafc8921d2d9efb1b8687b80ffd4401931eda2178466267b72af4a7c13d34a224dba53884d50273c7c6cd636828fb2d5ffadc2122570eb7c4a1efdbc7874eafcafb5440cb6028a98a2f2dd645ec9c29cae3a01cbb34d4e882bcbc71e36efecaad7661b026d781af356e256f17415307e6a238839e85e3411fe265020d9714d07771a3da55aa84b11a45dd7e824061fdff37e73d501a32f919861ec3b5b3c220da87749e754f52e5396731c38520c80cbc69df467768c86d20e13d03e3cc841ac41303b69ae9b9a2ab876badebdbc6513e3bb1c784cbd9d28599217f91f8b572a153e0f8a4ca5dda86ce9eaaa28eb0c765db00922f9005d62b4ca17b372cd2b5351d38a8add71dbeae2e48a73dcf575b8fc66b271e89b96a3b06032c61a0496d0c1245b2d390ac00d1e99e81669ad7e1f645ce57740af98286340c9d088d72c032252c132a2169a62321a11ad0fedce5817e8221acf58b93dfe3df1e65aa1c77f7327794c637b381ab554973ec402f9783b7e34344a35b87e1ae3c266403c68566e0cd9e482152ab25c42b1e5ebbde6b1c26e2bd8e27d6bfd4ccfd0cb56e6ba166839bcdfbf79bc1e8a7f419897e1f32213e8acc49e2dd7a33064708de1842eca9d82584413e4ee694583876f742f2343bcf6e4854837e96b2c1c1f77b72a5d028385b62bb2a1d1c04778826b4c5cc971084c44a0179d705031d468e332e43d82093bd7b8aee82a223d7377561e606c78a15a062b65690b77386b3eda4d05506b606e38ecd782aa34842a16f75f245eb5f2c3ee7b2b93d4b57ded6cf525794edbe12fa2e1184d689eecb1aebeddc16d25654bed9da6a46f791bd8b14f1e41239a27705f4e412394b514258d325e9b2b16bce9b4561e79c86d27d17df08975a55aed7d5513310521a7e0bba157c72194c99a26339cf9096e9b619c6e02db6dc05ecdb355f99d8bbd011e22ff462822bb609eaefada61b214ee73054d0252465beb7228ec22c57dc6f516e2499bba076afab62a2dce26298bbb74ff1d8a98aaef27ae6e311b57fabcc0a203952a740beefdf849351d7e9266e119a3452012d4bcd85650f57a4df02732d3ab2642ff9a80a775aeb70afb74716d38749d3dec1349a891f5ccb8b3ffee2ac1b2b5326eb5d6c73d7e0a43c917c5548ff35f1f0e1ff00b5629026")) + assertThat<ExtendedEncoding>(extended, instanceOf(ExtendedEncoding::class.java)) + assertThat(extended!!.content, instanceOf<Any>(Message::class.java)) + assertThat((extended.content as Message).subject, `is`("Extended encoding on Windows works - but how ??")) + assertThat((extended.content as Message).body, notNullValue()) + assertThat((extended.content as Message).body.length, `is`(6233)) + } + + + @Test + fun `ensure simple message is encoded`() { + val `in` = Message.Builder() + .subject("Test sübject") + .body("test bödy") + .build() + + assertThat(`in`.zip(), notNullValue()) + + val out = ExtendedEncodingFactory.unzip(`in`.zip()) + assertThat<ExtendedEncoding>(out, `is`(`in`)) + } + + @Test + fun `ensure complete message is encoded and decoded`() { + val `in` = Message.Builder() + .addParent(TestUtils.randomInventoryVector()) + .addParent(TestUtils.randomInventoryVector()) + .subject("Test sübject") + .body("test bödy") + .addFile( + Attachment.Builder() + .name("test.txt") + .type("text/plain") + .data("test".toByteArray(charset("UTF-8"))) + .attachment() + .build() + ) + .build() + + assertThat(`in`.zip(), notNullValue()) + + val out = ExtendedEncodingFactory.unzip(`in`.zip()) + assertThat<ExtendedEncoding>(out, `is`(`in`)) + } + + @Test + fun `ensure vote is encoded and decoded`() { + val `in` = Vote.Builder() + .msgId(TestUtils.randomInventoryVector()) + .vote("+1") + .build() + + assertThat(`in`.zip(), notNullValue()) + + val out = ExtendedEncodingFactory.unzip(`in`.zip()) + assertThat<ExtendedEncoding>(out, `is`(`in`)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java deleted file mode 100644 index 3e96b77..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2015 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.entity; - -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.*; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; - -public class SerializationTest extends TestBase { - @Test - public void ensureGetPubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V2GetPubkey.payload", 2, GetPubkey.class); - doTest("V3GetPubkey.payload", 2, GetPubkey.class); - doTest("V4GetPubkey.payload", 2, GetPubkey.class); - } - - @Test - public void ensureV2PubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V2Pubkey.payload", 2, V2Pubkey.class); - } - - @Test - public void ensureV3PubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V3Pubkey.payload", 3, V3Pubkey.class); - } - - @Test - public void ensureV4PubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V4Pubkey.payload", 4, V4Pubkey.class); - } - - @Test - public void ensureV1MsgIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V1Msg.payload", 1, Msg.class); - } - - @Test - public void ensureV4BroadcastIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V4Broadcast.payload", 4, V4Broadcast.class); - } - - @Test - public void ensureV5BroadcastIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V5Broadcast.payload", 5, V5Broadcast.class); - } - - @Test - public void ensureUnknownDataIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V1MsgStrangeData.payload", 1, GenericPayload.class); - } - - @Test - public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws Exception { - Plaintext expected = new Plaintext.Builder(MSG) - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .message("Subject", "Message") - .ackData("ackMessage".getBytes()) - .signature(new byte[0]) - .build(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - expected.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext actual = Plaintext.read(MSG, in); - - // Received is automatically set on deserialization, so we'll need to set it to null - Field received = Plaintext.class.getDeclaredField("received"); - received.setAccessible(true); - received.set(actual, null); - - assertThat(expected, is(actual)); - } - - @Test - public void ensurePlaintextWithExtendedEncodingIsSerializedAndDeserializedCorrectly() throws Exception { - Plaintext expected = new Plaintext.Builder(MSG) - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .message(new Message.Builder() - .subject("Subject") - .body("Message") - .build()) - .ackData("ackMessage".getBytes()) - .signature(new byte[0]) - .build(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - expected.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext actual = Plaintext.read(MSG, in); - - // Received is automatically set on deserialization, so we'll need to set it to null - Field received = Plaintext.class.getDeclaredField("received"); - received.setAccessible(true); - received.set(actual, null); - - assertEquals(expected, actual); - } - - @Test - public void ensurePlaintextWithAckMessageIsSerializedAndDeserializedCorrectly() throws Exception { - Plaintext expected = new Plaintext.Builder(MSG) - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .message("Subject", "Message") - .ackData("ackMessage".getBytes()) - .signature(new byte[0]) - .build(); - ObjectMessage ackMessage1 = expected.getAckMessage(); - assertNotNull(ackMessage1); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - expected.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext actual = Plaintext.read(MSG, in); - - // Received is automatically set on deserialization, so we'll need to set it to null - Field received = Plaintext.class.getDeclaredField("received"); - received.setAccessible(true); - received.set(actual, null); - - assertEquals(expected, actual); - assertEquals(ackMessage1, actual.getAckMessage()); - } - - @Test - public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception { - ArrayList<InventoryVector> ivs = new ArrayList<>(50000); - for (int i = 0; i < 50000; i++) { - ivs.add(TestUtils.randomInventoryVector()); - } - - Inv inv = new Inv.Builder().inventory(ivs).build(); - NetworkMessage before = new NetworkMessage(inv); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - before.write(out); - - NetworkMessage after = Factory.getNetworkMessage(3, new ByteArrayInputStream(out.toByteArray())); - assertNotNull(after); - Inv invAfter = (Inv) after.getPayload(); - assertEquals(ivs, invAfter.getInventory()); - } - - private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException { - byte[] data = TestUtils.getBytes(resourceName); - InputStream in = new ByteArrayInputStream(data); - ObjectMessage object = Factory.getObjectMessage(version, in, data.length); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - assertNotNull(object); - object.write(out); - assertArrayEquals(data, out.toByteArray()); - assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName()); - } - - @Test - public void ensureSystemSerializationWorks() throws Exception { - Plaintext plaintext = new Plaintext.Builder(MSG) - .from(TestUtils.loadContact()) - .to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .labels(Collections.singletonList(new Label("Test", Label.Type.INBOX, 0))) - .message("Test", "Test Test.\nTest") - .build(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(out); - oos.writeObject(plaintext); - - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - ObjectInputStream ois = new ObjectInputStream(in); - assertEquals(plaintext, ois.readObject()); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt new file mode 100644 index 0000000..a9fbfea --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2017 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.entity + +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.hamcrest.Matchers.`is` +import org.junit.Assert.* +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.util.* + +class SerializationTest : TestBase() { + @Test + fun `ensure GetPubkey is deserialized and serialized correctly`() { + doTest("V2GetPubkey.payload", 2, GetPubkey::class.java) + doTest("V3GetPubkey.payload", 2, GetPubkey::class.java) + doTest("V4GetPubkey.payload", 2, GetPubkey::class.java) + } + + @Test + fun `ensure V2Pubkey is deserialized and serialized correctly`() { + doTest("V2Pubkey.payload", 2, V2Pubkey::class.java) + } + + @Test + fun `ensure V3Pubkey is deserialized and serialized correctly`() { + doTest("V3Pubkey.payload", 3, V3Pubkey::class.java) + } + + @Test + fun `ensure V4Pubkey is deserialized and serialized correctly`() { + doTest("V4Pubkey.payload", 4, V4Pubkey::class.java) + } + + @Test + fun `ensure V1 msg is deserialized and serialized correctly`() { + doTest("V1Msg.payload", 1, Msg::class.java) + } + + @Test + fun `ensure V4Broadcast is deserialized and serialized correctly`() { + doTest("V4Broadcast.payload", 4, V4Broadcast::class.java) + } + + @Test + fun `ensure V5Broadcast is deserialized and serialized correctly`() { + doTest("V5Broadcast.payload", 5, V5Broadcast::class.java) + } + + @Test + fun `ensure unknown data is deserialized and serialized correctly`() { + doTest("V1MsgStrangeData.payload", 1, GenericPayload::class.java) + } + + @Test + fun `ensure plaintext is serialized and deserialized correctly`() { + val expected = Plaintext.Builder(MSG) + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .message("Subject", "Message") + .ackData("ackMessage".toByteArray()) + .signature(ByteArray(0)) + .build() + val out = ByteArrayOutputStream() + expected.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + val actual = Plaintext.read(MSG, `in`) + + // Received is automatically set on deserialization, so we'll need to set it to null + val received = Plaintext::class.java.getDeclaredField("received") + received.isAccessible = true + received.set(actual, null) + + assertThat(expected, `is`(actual)) + } + + @Test + fun `ensure plaintext with extended encoding is serialized and deserialized correctly`() { + val expected = Plaintext.Builder(MSG) + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .message(Message.Builder() + .subject("Subject") + .body("Message") + .build()) + .ackData("ackMessage".toByteArray()) + .signature(ByteArray(0)) + .build() + val out = ByteArrayOutputStream() + expected.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + val actual = Plaintext.read(MSG, `in`) + + // Received is automatically set on deserialization, so we'll need to set it to null + val received = Plaintext::class.java.getDeclaredField("received") + received.isAccessible = true + received.set(actual, null) + + assertEquals(expected, actual) + } + + @Test + fun `ensure plaintext with ack message is serialized and deserialized correctly`() { + val expected = Plaintext.Builder(MSG) + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .message("Subject", "Message") + .ackData("ackMessage".toByteArray()) + .signature(ByteArray(0)) + .build() + val ackMessage1 = expected.ackMessage + assertNotNull(ackMessage1) + + val out = ByteArrayOutputStream() + expected.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + val actual = Plaintext.read(MSG, `in`) + + // Received is automatically set on deserialization, so we'll need to set it to null + val received = Plaintext::class.java.getDeclaredField("received") + received.isAccessible = true + received.set(actual, null) + + assertEquals(expected, actual) + assertEquals(ackMessage1, actual.ackMessage) + } + + @Test + fun `ensure network message is serialized and deserialized correctly`() { + val ivs = ArrayList<InventoryVector>(50000) + for (i in 0..49999) { + ivs.add(TestUtils.randomInventoryVector()) + } + + val inv = Inv(ivs) + val before = NetworkMessage(inv) + val out = ByteArrayOutputStream() + before.write(out) + + val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray())) + assertNotNull(after) + val invAfter = after!!.payload as Inv + assertEquals(ivs, invAfter.inventory) + } + + private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) { + val data = TestUtils.getBytes(resourceName) + val `in` = ByteArrayInputStream(data) + val `object` = Factory.getObjectMessage(version, `in`, data.size) + val out = ByteArrayOutputStream() + assertNotNull(`object`) + `object`!!.write(out) + assertArrayEquals(data, out.toByteArray()) + assertEquals(expectedPayloadType.canonicalName, `object`.payload.javaClass.canonicalName) + } + + @Test + fun `ensure system serialization works`() { + val plaintext = Plaintext.Builder(MSG) + .from(TestUtils.loadContact()) + .to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .labels(listOf(Label("Test", Label.Type.INBOX, 0))) + .message("Test", "Test Test.\nTest") + .build() + val out = ByteArrayOutputStream() + val oos = ObjectOutputStream(out) + oos.writeObject(plaintext) + + val `in` = ByteArrayInputStream(out.toByteArray()) + val ois = ObjectInputStream(`in`) + assertEquals(plaintext, ois.readObject()) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java deleted file mode 100644 index c2efb1f..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015 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.ports; - -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.CallbackWaiter; -import ch.dissem.bitmessage.utils.TestBase; -import org.junit.Test; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.assertTrue; - -public class ProofOfWorkEngineTest extends TestBase { - @Test(timeout = 90_000) - public void testSimplePOWEngine() throws InterruptedException { - testPOW(new SimplePOWEngine()); - } - - @Test(timeout = 90_000) - public void testThreadedPOWEngine() throws InterruptedException { - testPOW(new MultiThreadedPOWEngine()); - } - - private void testPOW(ProofOfWorkEngine engine) throws InterruptedException { - byte[] initialHash = cryptography().sha512(new byte[]{1, 3, 6, 4}); - byte[] target = {0, 0, 0, -1, -1, -1, -1, -1}; - - final CallbackWaiter<byte[]> waiter1 = new CallbackWaiter<>(); - engine.calculateNonce(initialHash, target, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter1.setValue(nonce); - } - }); - byte[] nonce = waiter1.waitForValue(); - System.out.println("Calculating nonce took " + waiter1.getTime() + "ms"); - assertTrue(Bytes.lt(cryptography().doubleSha512(nonce, initialHash), target, 8)); - - // Let's add a second (shorter) run to find possible multi threading issues - byte[] initialHash2 = cryptography().sha512(new byte[]{1, 3, 6, 5}); - byte[] target2 = {0, 0, -1, -1, -1, -1, -1, -1}; - - final CallbackWaiter<byte[]> waiter2 = new CallbackWaiter<>(); - engine.calculateNonce(initialHash2, target2, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter2.setValue(nonce); - } - }); - byte[] nonce2 = waiter2.waitForValue(); - System.out.println("Calculating nonce took " + waiter2.getTime() + "ms"); - assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)); - assertTrue("Second nonce must be quicker to find", waiter1.getTime() > waiter2.getTime()); - } - -} diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt new file mode 100644 index 0000000..4ce5ec6 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2017 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.ports + +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.CallbackWaiter +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TestBase +import org.junit.Assert.assertTrue +import org.junit.Test + +class ProofOfWorkEngineTest : TestBase() { + @Test(timeout = 90000) + fun `test SimplePOWEngine`() { + testPOW(SimplePOWEngine()) + } + + @Test(timeout = 90000) + fun `test MultiThreadedPOWEngine`() { + testPOW(MultiThreadedPOWEngine()) + } + + private fun testPOW(engine: ProofOfWorkEngine) { + val initialHash = cryptography().sha512(byteArrayOf(1, 3, 6, 4)) + val target = byteArrayOf(0, 0, 0, -1, -1, -1, -1, -1) + + val waiter1 = CallbackWaiter<ByteArray>() + engine.calculateNonce(initialHash, target, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + waiter1.setValue(nonce) + } + }) + val nonce = waiter1.waitForValue()!! + println("Calculating nonce took " + waiter1.time + "ms") + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce, initialHash), target, 8)) + + // Let's add a second (shorter) run to find possible multi threading issues + val initialHash2 = cryptography().sha512(byteArrayOf(1, 3, 6, 5)) + val target2 = byteArrayOf(0, 0, -1, -1, -1, -1, -1, -1) + + val waiter2 = CallbackWaiter<ByteArray>() + engine.calculateNonce(initialHash2, target2, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + waiter2.setValue(nonce) + } + }) + val nonce2 = waiter2.waitForValue()!! + println("Calculating nonce took " + waiter2.time + "ms") + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)) + assertTrue("Second nonce must be quicker to find", waiter1.time > waiter2.time) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java deleted file mode 100644 index 1f8cfd3..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015 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.testutils; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.ports.Inventory; -import ch.dissem.bitmessage.utils.TestUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TestInventory implements Inventory { - private final Map<InventoryVector, ObjectMessage> inventory; - - public TestInventory() { - this.inventory = new HashMap<>(); - } - - @Override - public List<InventoryVector> getInventory(long... streams) { - return new ArrayList<>(inventory.keySet()); - } - - @Override - public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) { - return offer; - } - - @Override - public ObjectMessage getObject(InventoryVector vector) { - return inventory.get(vector); - } - - @Override - public List<ObjectMessage> getObjects(long stream, long version, ObjectType... types) { - return new ArrayList<>(inventory.values()); - } - - @Override - public void storeObject(ObjectMessage object) { - inventory.put(object.getInventoryVector(), object); - } - - @Override - public boolean contains(ObjectMessage object) { - return inventory.containsKey(object.getInventoryVector()); - } - - @Override - public void cleanup() { - - } - - public void init(String... resources) throws IOException { - inventory.clear(); - for (String resource : resources) { - int version = Integer.parseInt(resource.substring(1, 2)); - ObjectMessage obj = TestUtils.loadObjectMessage(version, resource); - inventory.put(obj.getInventoryVector(), obj); - } - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt new file mode 100644 index 0000000..f147151 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 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.testutils + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.ports.Inventory +import ch.dissem.bitmessage.utils.TestUtils +import java.util.* + +class TestInventory : Inventory { + private val inventory = HashMap<InventoryVector, ObjectMessage>() + + override fun getInventory(vararg streams: Long): List<InventoryVector> { + return ArrayList(inventory.keys) + } + + override fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector> { + return offer + } + + override fun getObject(vector: InventoryVector): ObjectMessage? { + return inventory[vector] + } + + override fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> { + return ArrayList(inventory.values) + } + + override fun storeObject(`object`: ObjectMessage) { + inventory.put(`object`.inventoryVector, `object`) + } + + override fun contains(`object`: ObjectMessage): Boolean { + return inventory.containsKey(`object`.inventoryVector) + } + + override fun cleanup() { + + } + + fun init(vararg resources: String) { + inventory.clear() + for (resource in resources) { + val version = Integer.parseInt(resource.substring(1, 2)) + val obj = TestUtils.loadObjectMessage(version, resource) + inventory.put(obj.inventoryVector, obj) + } + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java deleted file mode 100644 index 1af8d37..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015 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 org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Random; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -public class BytesTest { - public static final Random rnd = new Random(); - - @Test - public void ensureExpandsCorrectly() { - byte[] source = {1}; - byte[] expected = {0, 1}; - assertArrayEquals(expected, Bytes.expand(source, 2)); - } - - @Test - public void ensureIncrementCarryWorks() throws IOException { - byte[] bytes = {0, -1}; - Bytes.inc(bytes); - assertArrayEquals(TestUtils.int16(256), bytes); - } - - @Test - public void testIncrementByValue() throws IOException { - for (int v = 0; v < 256; v++) { - for (int i = 1; i < 256; i++) { - byte[] bytes = {0, (byte) v}; - Bytes.inc(bytes, (byte) i); - assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes); - } - } - } - - /** - * This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored. - */ - @Test - @Ignore - public void testLowerThanSingleByte() { - byte[] a = new byte[1]; - byte[] b = new byte[1]; - for (int i = 0; i < 255; i++) { - for (int j = 0; j < 255; j++) { - System.out.println("a = " + i + "\tb = " + j); - a[0] = (byte) i; - b[0] = (byte) j; - assertEquals(i < j, Bytes.lt(a, b)); - } - } - } - - @Test - public void testLowerThan() { - for (int i = 0; i < 1000; i++) { - BigInteger a = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - BigInteger b = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - System.out.println("a = " + a.toString(16) + "\tb = " + b.toString(16)); - assertEquals(a.compareTo(b) == -1, Bytes.lt(a.toByteArray(), b.toByteArray())); - } - } - - @Test - public void testLowerThanBounded() { - for (int i = 0; i < 1000; i++) { - BigInteger a = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - BigInteger b = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - System.out.println("a = " + a.toString(16) + "\tb = " + b.toString(16)); - assertEquals(a.compareTo(b) == -1, Bytes.lt( - Bytes.expand(a.toByteArray(), 100), - Bytes.expand(b.toByteArray(), 100), - 100)); - } - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt new file mode 100644 index 0000000..cabd0b9 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2017 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 org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Ignore +import org.junit.Test +import java.math.BigInteger +import java.util.* + +class BytesTest { + val rnd = Random() + + @Test + fun `ensure expands correctly`() { + val source = byteArrayOf(1) + val expected = byteArrayOf(0, 1) + assertArrayEquals(expected, Bytes.expand(source, 2)) + } + + @Test + fun `ensure increment carry works`() { + val bytes = byteArrayOf(0, -1) + Bytes.inc(bytes) + assertArrayEquals(TestUtils.int16(256), bytes) + } + + @Test + fun `test increment by value`() { + for (v in 0..255) { + for (i in 1..255) { + val bytes = byteArrayOf(0, v.toByte()) + Bytes.inc(bytes, i.toByte()) + assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes) + } + } + } + + /** + * This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored. + */ + @Test + @Ignore + fun `test lower than single byte`() { + val a = ByteArray(1) + val b = ByteArray(1) + for (i in 0..254) { + for (j in 0..254) { + println("a = $i\tb = $j") + a[0] = i.toByte() + b[0] = j.toByte() + assertEquals(i < j, Bytes.lt(a, b)) + } + } + } + + @Test + fun `test lower than`() { + for (i in 0..999) { + val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + println("a = " + a.toString(16) + "\tb = " + b.toString(16)) + assertEquals(a.compareTo(b) == -1, Bytes.lt(a.toByteArray(), b.toByteArray())) + } + } + + @Test + fun `test lower than bounded`() { + for (i in 0..999) { + val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + println("a = " + a.toString(16) + "\tb = " + b.toString(16)) + assertEquals(a.compareTo(b) == -1, Bytes.lt( + Bytes.expand(a.toByteArray(), 100), + Bytes.expand(b.toByteArray(), 100), + 100)) + } + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java deleted file mode 100644 index 91e42b5..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015 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 org.junit.Test; - -import java.util.LinkedList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class CollectionsTest { - @Test - public void ensureSelectRandomReturnsMaximumPossibleItems() throws Exception { - List<Integer> list = new LinkedList<>(); - for (int i = 0; i < 10; i++) { - list.add(i); - } - assertEquals(9, Collections.selectRandom(9, list).size()); - } -} \ No newline at end of file diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt similarity index 60% rename from core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java rename to core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt index ef54165..dcfad1f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,19 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import org.junit.Test; +import org.junit.Test -import static org.junit.Assert.assertEquals; +import java.util.LinkedList -public class StringsTest { +import org.junit.Assert.assertEquals + +class CollectionsTest { @Test - public void testHexString() { - assertEquals("48656c6c6f21", Strings.hex("Hello!".getBytes()).toString()); - assertEquals("0001", Strings.hex(new byte[]{0, 1}).toString()); + fun `ensure select random returns maximum possible items`() { + val list = LinkedList<Int>() + list += 0..9 + assertEquals(9, Collections.selectRandom(9, list).size.toLong()) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java deleted file mode 100644 index 6543232..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2017 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.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.ports.MessageRepository; -import org.junit.Test; - -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.TestUtils.RANDOM; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ConversationServiceTest { - private BitmessageAddress alice = new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - private BitmessageAddress bob = new BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"); - - private MessageRepository messageRepository = mock(MessageRepository.class); - private ConversationService conversationService = new ConversationService(messageRepository); - - static { - Singleton.initialize(new BouncyCryptography()); - } - - @Test - public void ensureConversationIsSortedProperly() { - List<Plaintext> expected = getConversation(); - - when(conversationService.getConversation(any(UUID.class))).thenReturn(expected); - List<Plaintext> actual = conversationService.getConversation(UUID.randomUUID()); - assertThat(actual, is(expected)); - } - - private List<Plaintext> getConversation() { - List<Plaintext> result = new LinkedList<>(); - - Plaintext older = plaintext(alice, bob, - new Message.Builder() - .subject("hey there") - .body("does it work?") - .build(), - Plaintext.Status.SENT); - result.add(older); - - Plaintext root = plaintext(alice, bob, - new Message.Builder() - .subject("new test") - .body("There's a new test in town!") - .build(), - Plaintext.Status.SENT); - result.add(root); - - result.add( - plaintext(bob, alice, - new Message.Builder() - .subject("Re: new test (1a)") - .body("Nice!") - .addParent(root) - .build(), - Plaintext.Status.RECEIVED) - ); - - Plaintext latest = plaintext(bob, alice, - new Message.Builder() - .subject("Re: new test (2b)") - .body("PS: it did work!") - .addParent(root) - .addParent(older) - .build(), - Plaintext.Status.RECEIVED); - result.add(latest); - - result.add( - plaintext(alice, bob, - new Message.Builder() - .subject("Re: new test (2)") - .body("") - .addParent(latest) - .build(), - Plaintext.Status.DRAFT) - ); - - return result; - } - - private int timer = 2; - - private Plaintext plaintext(BitmessageAddress from, BitmessageAddress to, - ExtendedEncoding content, Plaintext.Status status) { - Plaintext.Builder builder = new Plaintext.Builder(MSG) - .IV(TestUtils.randomInventoryVector()) - .from(from) - .to(to) - .message(content) - .status(status); - if (status != Plaintext.Status.DRAFT && status != Plaintext.Status.DOING_PROOF_OF_WORK) { - builder.received(5L * ++timer - RANDOM.nextInt(10)); - } - return builder.build(); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt new file mode 100644 index 0000000..97e1a2d --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2017 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.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.utils.TestUtils.RANDOM +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.Matchers.`is` +import org.junit.Assert.assertThat +import org.junit.Test +import java.util.* + +class ConversationServiceTest { + private val alice = BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + private val bob = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj") + + private val messageRepository = mock<MessageRepository>() + private val conversationService = spy(ConversationService(messageRepository)) + + companion object { + init { + Singleton.initialize(BouncyCryptography()) + } + } + + @Test + fun `ensure conversation is sorted properly`() { + MockitoKotlin.registerInstanceCreator { UUID.randomUUID() } + val expected = conversation + + doReturn(expected).whenever(conversationService).getConversation(any<UUID>()) + val actual = conversationService.getConversation(UUID.randomUUID()) + assertThat(actual, `is`(expected)) + } + + private val conversation: List<Plaintext> + get() { + val result = LinkedList<Plaintext>() + + val older = plaintext(alice, bob, + Message.Builder() + .subject("hey there") + .body("does it work?") + .build(), + Plaintext.Status.SENT) + result.add(older) + + val root = plaintext(alice, bob, + Message.Builder() + .subject("new test") + .body("There's a new test in town!") + .build(), + Plaintext.Status.SENT) + result.add(root) + + result.add( + plaintext(bob, alice, + Message.Builder() + .subject("Re: new test (1a)") + .body("Nice!") + .addParent(root) + .build(), + Plaintext.Status.RECEIVED) + ) + + val latest = plaintext(bob, alice, + Message.Builder() + .subject("Re: new test (2b)") + .body("PS: it did work!") + .addParent(root) + .addParent(older) + .build(), + Plaintext.Status.RECEIVED) + result.add(latest) + + result.add( + plaintext(alice, bob, + Message.Builder() + .subject("Re: new test (2)") + .body("") + .addParent(latest) + .build(), + Plaintext.Status.DRAFT) + ) + + return result + } + + private var timer = 2 + + private fun plaintext(from: BitmessageAddress, to: BitmessageAddress, + content: ExtendedEncoding, status: Plaintext.Status): Plaintext { + val builder = Plaintext.Builder(MSG) + .IV(TestUtils.randomInventoryVector()) + .from(from) + .to(to) + .message(content) + .status(status) + if (status !== Plaintext.Status.DRAFT && status !== Plaintext.Status.DOING_PROOF_OF_WORK) { + builder.received(5L * ++timer - RANDOM.nextInt(10)) + } + return builder.build() + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt similarity index 54% rename from core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java rename to core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt index 60d882f..f1cf244 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,27 +14,28 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import org.junit.Test; +import org.junit.Assert.assertEquals +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream -import java.io.*; - -import static org.junit.Assert.assertEquals; - -public class DecodeTest { +class DecodeTest { @Test - public void ensureDecodingWorks() throws Exception { + fun `ensure decoding works`() { // This should test all relevant cases for var_int and therefore also uint_16, uint_32 and int_64 - testCodec(0); - for (long i = 1; i > 0; i = 3 * i + 7) { - testCodec(i); + testCodec(0) + var i: Long = 1 + while (i > 0) { + testCodec(i) + i = 3 * i + 7 } } - private void testCodec(long number) throws IOException { - ByteArrayOutputStream is = new ByteArrayOutputStream(); - Encode.varInt(number, is); - assertEquals(number, Decode.varInt(new ByteArrayInputStream(is.toByteArray()))); + private fun testCodec(number: Long) { + val `is` = ByteArrayOutputStream() + Encode.varInt(number, `is`) + assertEquals(number, Decode.varInt(ByteArrayInputStream(`is`.toByteArray()))) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java deleted file mode 100644 index 23d602c..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2015 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 org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import static org.junit.Assert.assertEquals; - -public class EncodeTest { - @Test - public void testUint8() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int8(0, stream); - checkBytes(stream, 0); - - stream = new ByteArrayOutputStream(); - Encode.int8(255, stream); - checkBytes(stream, 255); - } - - @Test - public void testUint16() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int16(0, stream); - checkBytes(stream, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.int16(513, stream); - checkBytes(stream, 2, 1); - } - - @Test - public void testUint32() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int32(0, stream); - checkBytes(stream, 0, 0, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.int32(67305985, stream); - checkBytes(stream, 4, 3, 2, 1); - - stream = new ByteArrayOutputStream(); - Encode.int32(3355443201L, stream); - checkBytes(stream, 200, 0, 0, 1); - } - - @Test - public void testUint64() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int64(0, stream); - checkBytes(stream, 0, 0, 0, 0, 0, 0, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.int64(578437695752307201L, stream); - checkBytes(stream, 8, 7, 6, 5, 4, 3, 2, 1); - - stream = new ByteArrayOutputStream(); - // 200 * 72057594037927936L + 1 - Encode.int64(0xc800000000000001L, stream); - checkBytes(stream, 200, 0, 0, 0, 0, 0, 0, 1); - } - - @Test - public void testVarInt() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.varInt(0, stream); - checkBytes(stream, 0); - - stream = new ByteArrayOutputStream(); - Encode.varInt(252, stream); - checkBytes(stream, 252); - - stream = new ByteArrayOutputStream(); - Encode.varInt(253, stream); - checkBytes(stream, 253, 0, 253); - - stream = new ByteArrayOutputStream(); - Encode.varInt(65535, stream); - checkBytes(stream, 253, 255, 255); - - stream = new ByteArrayOutputStream(); - Encode.varInt(65536, stream); - checkBytes(stream, 254, 0, 1, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.varInt(4294967295L, stream); - checkBytes(stream, 254, 255, 255, 255, 255); - - stream = new ByteArrayOutputStream(); - Encode.varInt(4294967296L, stream); - checkBytes(stream, 255, 0, 0, 0, 1, 0, 0, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.varInt(-1L, stream); - checkBytes(stream, 255, 255, 255, 255, 255, 255, 255, 255, 255); - } - - - public void checkBytes(ByteArrayOutputStream stream, int... bytes) { - assertEquals(bytes.length, stream.size()); - byte[] streamBytes = stream.toByteArray(); - - for (int i = 0; i < bytes.length; i++) { - assertEquals((byte) bytes[i], streamBytes[i]); - } - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt new file mode 100644 index 0000000..9027162 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2017 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 org.junit.Assert.assertEquals +import org.junit.Test +import java.io.ByteArrayOutputStream + +class EncodeTest { + @Test + fun `test uint8`() { + var stream = ByteArrayOutputStream() + Encode.int8(0, stream) + checkBytes(stream, 0) + + stream = ByteArrayOutputStream() + Encode.int8(255, stream) + checkBytes(stream, 255) + } + + @Test + fun `test uint16`() { + var stream = ByteArrayOutputStream() + Encode.int16(0, stream) + checkBytes(stream, 0, 0) + + stream = ByteArrayOutputStream() + Encode.int16(513, stream) + checkBytes(stream, 2, 1) + } + + @Test + fun `test uint32`() { + var stream = ByteArrayOutputStream() + Encode.int32(0, stream) + checkBytes(stream, 0, 0, 0, 0) + + stream = ByteArrayOutputStream() + Encode.int32(67305985, stream) + checkBytes(stream, 4, 3, 2, 1) + + stream = ByteArrayOutputStream() + Encode.int32(3355443201L, stream) + checkBytes(stream, 200, 0, 0, 1) + } + + @Test + fun `test uint64`() { + var stream = ByteArrayOutputStream() + Encode.int64(0, stream) + checkBytes(stream, 0, 0, 0, 0, 0, 0, 0, 0) + + stream = ByteArrayOutputStream() + Encode.int64(578437695752307201L, stream) + checkBytes(stream, 8, 7, 6, 5, 4, 3, 2, 1) + + stream = ByteArrayOutputStream() + @Suppress("INTEGER_OVERFLOW") // 0xc800000000000001L + Encode.int64(200 * 72057594037927936L + 1, stream) + checkBytes(stream, 200, 0, 0, 0, 0, 0, 0, 1) + } + + @Test + fun `test varInt`() { + var stream = ByteArrayOutputStream() + Encode.varInt(0, stream) + checkBytes(stream, 0) + + stream = ByteArrayOutputStream() + Encode.varInt(252, stream) + checkBytes(stream, 252) + + stream = ByteArrayOutputStream() + Encode.varInt(253, stream) + checkBytes(stream, 253, 0, 253) + + stream = ByteArrayOutputStream() + Encode.varInt(65535, stream) + checkBytes(stream, 253, 255, 255) + + stream = ByteArrayOutputStream() + Encode.varInt(65536, stream) + checkBytes(stream, 254, 0, 1, 0, 0) + + stream = ByteArrayOutputStream() + Encode.varInt(4294967295L, stream) + checkBytes(stream, 254, 255, 255, 255, 255) + + stream = ByteArrayOutputStream() + Encode.varInt(4294967296L, stream) + checkBytes(stream, 255, 0, 0, 0, 1, 0, 0, 0, 0) + + stream = ByteArrayOutputStream() + Encode.varInt(-1L, stream) + checkBytes(stream, 255, 255, 255, 255, 255, 255, 255, 255, 255) + } + + + fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) { + assertEquals(bytes.size.toLong(), stream.size().toLong()) + val streamBytes = stream.toByteArray() + + for (i in bytes.indices) { + assertEquals(bytes[i].toByte().toLong(), streamBytes[i].toLong()) + } + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java b/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java deleted file mode 100644 index 5be73ff..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.mockito.ArgumentMatcher; -import org.mockito.ArgumentMatchers; - -/** - * @author Christian Basler - */ -public class MessageMatchers { - public static Plaintext plaintext(final Plaintext.Type type) { - return ArgumentMatchers.argThat(new ArgumentMatcher<Plaintext>() { - @Override - public boolean matches(Plaintext item) { - return item != null && item.getType() == type; - } - }); - } - - public static ObjectMessage object(final ObjectType type) { - return ArgumentMatchers.argThat(new ArgumentMatcher<ObjectMessage>() { - @Override - public boolean matches(ObjectMessage item) { - return item != null && item.getPayload().getType() == type; - } - }); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt similarity index 65% rename from core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java rename to core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt index 2aa1113..1097e01 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,16 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import org.junit.Test; +import org.junit.Test -import static org.junit.Assert.assertEquals; +import org.junit.Assert.assertEquals -public class SqlStringsTest { +class SqlStringsTest { @Test - public void ensureJoinWorksWithLongArray() { - long[] test = {1L, 2L}; - assertEquals("1, 2", SqlStrings.join(test).toString()); + fun `ensure join works with long array`() { + val test = longArrayOf(1L, 2L) + assertEquals("1, 2", SqlStrings.join(*test)) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt new file mode 100644 index 0000000..815d8e8 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2017 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 org.junit.Test + +import org.junit.Assert.assertEquals + +class StringsTest { + @Test + fun `test hex string`() { + assertEquals("48656c6c6f21", Strings.hex("Hello!".toByteArray())) + assertEquals("0001", Strings.hex(byteArrayOf(0, 1))) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt similarity index 62% rename from core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.java rename to core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt index e688cb1..90e2b2d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,19 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import org.junit.BeforeClass /** * @author Christian Basler */ -public class ApplicationException extends RuntimeException { - private static final long serialVersionUID = 1796776684126759324L; - - public ApplicationException(Throwable cause) { - super(cause); - } - - public ApplicationException(String message) { - super(message); +open class TestBase { + companion object { + @BeforeClass + @JvmStatic fun setUpClass() { + Singleton.initialize(BouncyCryptography()) + } } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java deleted file mode 100644 index 6fadce4..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015 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.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.factory.Factory; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; - -import static org.junit.Assert.assertEquals; - -/** - * If there's ever a need for this in production code, it should be rewritten to be more efficient. - */ -public class TestUtils { - public static final Random RANDOM = new Random(); - - public static byte[] int16(int number) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Encode.int16(number, out); - return out.toByteArray(); - } - - public static ObjectMessage loadObjectMessage(int version, String resourceName) throws IOException { - byte[] data = getBytes(resourceName); - InputStream in = new ByteArrayInputStream(data); - return Factory.getObjectMessage(version, in, data.length); - } - - public static byte[] getBytes(String resourceName) throws IOException { - InputStream in = TestUtils.class.getClassLoader().getResourceAsStream(resourceName); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - return out.toByteArray(); - } - - public static InventoryVector randomInventoryVector() { - byte[] bytes = new byte[32]; - RANDOM.nextBytes(bytes); - return InventoryVector.fromHash(bytes); - } - - public static InputStream getResource(String resourceName) { - return TestUtils.class.getClassLoader().getResourceAsStream(resourceName); - } - - public static BitmessageAddress loadIdentity(String address) throws IOException { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey")); - BitmessageAddress identity = new BitmessageAddress(privateKey); - assertEquals(address, identity.getAddress()); - return identity; - } - - public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - ObjectMessage object = TestUtils.loadObjectMessage(3, "V4Pubkey.payload"); - object.decrypt(address.getPublicDecryptionKey()); - address.setPubkey((V4Pubkey) object.getPayload()); - return address; - } - - public static void loadPubkey(BitmessageAddress address) throws IOException { - byte[] bytes = getBytes(address.getAddress() + ".pubkey"); - Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), new ByteArrayInputStream(bytes), bytes.length, false); - address.setPubkey(pubkey); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt new file mode 100644 index 0000000..397f926 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2017 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.BitmessageContext +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +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 com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.spy +import org.junit.Assert.assertEquals +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.util.Random +import kotlin.NoSuchElementException + +/** + * If there's ever a need for this in production code, it should be rewritten to be more efficient. + */ +object TestUtils { + @JvmField val RANDOM = Random() + + @JvmStatic fun int16(number: Int): ByteArray { + val out = ByteArrayOutputStream() + Encode.int16(number.toLong(), out) + return out.toByteArray() + } + + @JvmStatic fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage { + val data = getBytes(resourceName) + val `in` = ByteArrayInputStream(data) + return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message") + } + + @JvmStatic fun getBytes(resourceName: String): ByteArray { + val `in` = javaClass.classLoader.getResourceAsStream(resourceName) + val out = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var len = `in`.read(buffer) + while (len != -1) { + out.write(buffer, 0, len) + len = `in`.read(buffer) + } + return out.toByteArray() + } + + @JvmStatic fun randomInventoryVector(): InventoryVector { + val bytes = ByteArray(32) + RANDOM.nextBytes(bytes) + return InventoryVector(bytes) + } + + @JvmStatic fun getResource(resourceName: String): InputStream { + return javaClass.classLoader.getResourceAsStream(resourceName) + } + + @JvmStatic fun loadIdentity(address: String): BitmessageAddress { + val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey")) + val identity = BitmessageAddress(privateKey) + assertEquals(address, identity.address) + return identity + } + + @Throws(DecryptionFailedException::class) + @JvmStatic fun loadContact(): BitmessageAddress { + val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + val `object` = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") + `object`.decrypt(address.publicDecryptionKey) + address.pubkey = `object`.payload as V4Pubkey + return address + } + + @JvmStatic fun loadPubkey(address: BitmessageAddress) { + val bytes = getBytes(address.address + ".pubkey") + val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false) + address.pubkey = pubkey + } + + @JvmStatic fun mockedInternalContext( + cryptography: Cryptography = mock {}, + inventory: Inventory = mock {}, + nodeRegistry: NodeRegistry = mock {}, + networkHandler: NetworkHandler = mock {}, + addressRepository: AddressRepository = mock {}, + messageRepository: MessageRepository = mock {}, + proofOfWorkRepository: ProofOfWorkRepository = mock {}, + proofOfWorkEngine: ProofOfWorkEngine = mock {}, + customCommandHandler: CustomCommandHandler = mock {}, + listener: BitmessageContext.Listener = mock {}, + labeler: Labeler = mock {}, + port: Int = 0, + connectionTTL: Long = 0, + connectionLimit: Int = 0 + ): InternalContext { + return spy(InternalContext( + cryptography, + inventory, + nodeRegistry, + networkHandler, + addressRepository, + messageRepository, + proofOfWorkRepository, + proofOfWorkEngine, + customCommandHandler, + listener, + labeler, + port, + connectionTTL, + connectionLimit + )) + } +} diff --git a/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline 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 b0937d3..f19be27 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 @@ -1,3 +1,19 @@ +/* + * Copyright 2017 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.security; import ch.dissem.bitmessage.InternalContext; @@ -31,12 +47,12 @@ import static org.mockito.Mockito.when; public class CryptographyTest { public static final byte[] TEST_VALUE = "teststring".getBytes(); public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary("" - + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); + + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary("" - + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" - + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); + + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" - + "cd566972b5e50104011a92b59fa8e0b1234851ae"); + + "cd566972b5e50104011a92b59fa8e0b1234851ae"); private static BouncyCryptography crypto; @@ -46,7 +62,6 @@ public class CryptographyTest { Singleton.initialize(crypto); InternalContext ctx = mock(InternalContext.class); when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - crypto.setContext(ctx); } @Test @@ -77,30 +92,30 @@ public class CryptographyTest { @Test(expected = IOException.class) public void ensureExceptionForInsufficientProofOfWork() throws IOException { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+28 * DAY)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 28 * DAY) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); crypto.checkProofOfWork(objectMessage, 1000, 1000); } @Test public void testDoProofOfWork() throws Exception { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+2 * MINUTE)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 2 * MINUTE) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>(); crypto.doProofOfWork(objectMessage, 1000, 1000, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter.setValue(nonce); - } - }); + new ProofOfWorkEngine.Callback() { + @Override + public void onNonceCalculated(byte[] initialHash, byte[] nonce) { + waiter.setValue(nonce); + } + }); objectMessage.setNonce(waiter.waitForValue()); try { crypto.checkProofOfWork(objectMessage, 1000, 1000); diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java index dc3cdea..1589fcf 100644 --- a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java +++ b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 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.security; import ch.dissem.bitmessage.InternalContext; @@ -11,6 +27,7 @@ import ch.dissem.bitmessage.ports.ProofOfWorkEngine; import ch.dissem.bitmessage.utils.CallbackWaiter; import ch.dissem.bitmessage.utils.Singleton; import ch.dissem.bitmessage.utils.UnixTime; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -31,12 +48,12 @@ import static org.mockito.Mockito.when; public class CryptographyTest { public static final byte[] TEST_VALUE = "teststring".getBytes(); public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary("" - + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); + + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary("" - + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" - + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); + + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" - + "cd566972b5e50104011a92b59fa8e0b1234851ae"); + + "cd566972b5e50104011a92b59fa8e0b1234851ae"); private static SpongyCryptography crypto; @@ -45,62 +62,62 @@ public class CryptographyTest { crypto = new SpongyCryptography(); Singleton.initialize(crypto); InternalContext ctx = mock(InternalContext.class); + InternalContext.Companion.setValue(null, null, ctx); when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - crypto.setContext(ctx); } @Test public void testRipemd160() { - assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)); + Assert.assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)); } @Test public void testSha1() { - assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)); + Assert.assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)); } @Test public void testSha512() { - assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)); + Assert.assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)); } @Test public void testChaining() { - assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes())); + Assert.assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes())); } @Test public void ensureDoubleHashYieldsSameResultAsHashOfHash() { - assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)); + Assert.assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)); } @Test(expected = IOException.class) public void ensureExceptionForInsufficientProofOfWork() throws IOException { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+28 * DAY)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 28 * DAY) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); crypto.checkProofOfWork(objectMessage, 1000, 1000); } @Test public void testDoProofOfWork() throws Exception { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+2 * MINUTE)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 2 * MINUTE) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>(); crypto.doProofOfWork(objectMessage, 1000, 1000, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter.setValue(nonce); - } - }); + new ProofOfWorkEngine.Callback() { + @Override + public void onNonceCalculated(byte[] initialHash, byte[] nonce) { + waiter.setValue(nonce); + } + }); objectMessage.setNonce(waiter.waitForValue()); try { crypto.checkProofOfWork(objectMessage, 1000, 1000); diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index 9f97432..fe3343d 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -1,3 +1,19 @@ +/* + * 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; diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestListener.java b/demo/src/test/java/ch/dissem/bitmessage/TestListener.java index 9c00776..d2b1cd5 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestListener.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestListener.java @@ -1,3 +1,19 @@ +/* + * 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.entity.Plaintext; diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java index 2f9f070..3ac7348 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java @@ -31,10 +31,10 @@ class TestNodeRegistry implements NodeRegistry { public TestNodeRegistry(int... ports) { for (int port : ports) { nodes.add( - new NetworkAddress.Builder() - .ipv4(127, 0, 0, 1) - .port(port) - .build() + new NetworkAddress.Builder() + .ipv4(127, 0, 0, 1) + .port(port) + .build() ); } } diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java deleted file mode 100644 index 7955e5b..0000000 --- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2015 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.extensions; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.payload.CryptoBox; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.*; - -import static ch.dissem.bitmessage.utils.Decode.*; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * A {@link CustomMessage} implementation that contains signed and encrypted data. - * - * @author Christian Basler - */ -public class CryptoCustomMessage<T extends Streamable> extends CustomMessage { - private static final long serialVersionUID = 7395193565986284426L; - - public static final String COMMAND = "ENCRYPTED"; - - private final Reader<T> dataReader; - private CryptoBox container; - private BitmessageAddress sender; - private T data; - - public CryptoCustomMessage(T data) throws IOException { - super(COMMAND); - this.data = data; - this.dataReader = null; - } - - private CryptoCustomMessage(CryptoBox container, Reader<T> dataReader) { - super(COMMAND); - this.container = container; - this.dataReader = dataReader; - } - - public static <T extends Streamable> CryptoCustomMessage<T> read(CustomMessage data, Reader<T> dataReader) throws IOException { - CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data.getData()), data.getData().length); - return new CryptoCustomMessage<>(cryptoBox, dataReader); - } - - public BitmessageAddress getSender() { - return sender; - } - - public void signAndEncrypt(BitmessageAddress identity, byte[] publicKey) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - Encode.varInt(identity.getVersion(), out); - Encode.varInt(identity.getStream(), out); - Encode.int32(identity.getPubkey().getBehaviorBitfield(), out); - out.write(identity.getPubkey().getSigningKey(), 1, 64); - out.write(identity.getPubkey().getEncryptionKey(), 1, 64); - if (identity.getVersion() >= 3) { - Encode.varInt(identity.getPubkey().getNonceTrialsPerByte(), out); - Encode.varInt(identity.getPubkey().getExtraBytes(), out); - } - - data.write(out); - Encode.varBytes(cryptography().getSignature(out.toByteArray(), identity.getPrivateKey()), out); - container = new CryptoBox(out.toByteArray(), publicKey); - } - - public T decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - SignatureCheckingInputStream in = new SignatureCheckingInputStream(container.decrypt(privateKey)); - - long addressVersion = varInt(in); - long stream = varInt(in); - int behaviorBitfield = int32(in); - byte[] publicSigningKey = bytes(in, 64); - byte[] publicEncryptionKey = bytes(in, 64); - long nonceTrialsPerByte = addressVersion >= 3 ? varInt(in) : 0; - long extraBytes = addressVersion >= 3 ? varInt(in) : 0; - - sender = new BitmessageAddress(Factory.createPubkey( - addressVersion, - stream, - publicSigningKey, - publicEncryptionKey, - nonceTrialsPerByte, - extraBytes, - behaviorBitfield - )); - - data = dataReader.read(sender, in); - - in.checkSignature(sender.getPubkey()); - - return data; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varString(COMMAND, out); - container.write(out); - } - - public interface Reader<T> { - T read(BitmessageAddress sender, InputStream in) throws IOException; - } - - private class SignatureCheckingInputStream extends InputStream { - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); - private final InputStream wrapped; - - private SignatureCheckingInputStream(InputStream wrapped) { - this.wrapped = wrapped; - } - - @Override - public int read() throws IOException { - int read = wrapped.read(); - if (read >= 0) out.write(read); - return read; - } - - public void checkSignature(Pubkey pubkey) throws IOException, IllegalStateException { - if (!cryptography().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { - throw new IllegalStateException("Signature check failed"); - } - } - } -} diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java deleted file mode 100644 index e5ba4f8..0000000 --- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2015 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.extensions.pow; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.extensions.CryptoCustomMessage; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -import static ch.dissem.bitmessage.utils.Decode.*; - -/** - * @author Christian Basler - */ -public class ProofOfWorkRequest implements Streamable { - private static final long serialVersionUID = 4729003751499662713L; - - private final BitmessageAddress sender; - private final byte[] initialHash; - private final Request request; - - private final byte[] data; - - public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request) { - this(sender, initialHash, request, new byte[0]); - } - - public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) { - this.sender = sender; - this.initialHash = initialHash; - this.request = request; - this.data = data; - } - - public static ProofOfWorkRequest read(BitmessageAddress client, InputStream in) throws IOException { - return new ProofOfWorkRequest( - client, - bytes(in, 64), - Request.valueOf(varString(in)), - varBytes(in) - ); - } - - public BitmessageAddress getSender() { - return sender; - } - - public byte[] getInitialHash() { - return initialHash; - } - - public Request getRequest() { - return request; - } - - public byte[] getData() { - return data; - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(initialHash); - Encode.varString(request.name(), out); - Encode.varBytes(data, out); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(initialHash); - Encode.varString(request.name(), buffer); - Encode.varBytes(data, buffer); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ProofOfWorkRequest other = (ProofOfWorkRequest) o; - - if (!sender.equals(other.sender)) return false; - if (!Arrays.equals(initialHash, other.initialHash)) return false; - if (request != other.request) return false; - return Arrays.equals(data, other.data); - } - - @Override - public int hashCode() { - int result = sender.hashCode(); - result = 31 * result + Arrays.hashCode(initialHash); - result = 31 * result + request.hashCode(); - result = 31 * result + Arrays.hashCode(data); - return result; - } - - public static class Reader implements CryptoCustomMessage.Reader<ProofOfWorkRequest> { - private final BitmessageAddress identity; - - public Reader(BitmessageAddress identity) { - this.identity = identity; - } - - @Override - public ProofOfWorkRequest read(BitmessageAddress sender, InputStream in) throws IOException { - return ProofOfWorkRequest.read(identity, in); - } - } - - public enum Request { - CALCULATE, - CALCULATING, - COMPLETE - } -} diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt new file mode 100644 index 0000000..1c76bdb --- /dev/null +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2017 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.extensions + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.payload.CryptoBox +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.int32 +import ch.dissem.bitmessage.utils.Decode.varBytes +import ch.dissem.bitmessage.utils.Decode.varInt +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream + +/** + * A [CustomMessage] implementation that contains signed and encrypted data. + + * @author Christian Basler + */ +class CryptoCustomMessage<T : Streamable> : CustomMessage { + + private val dataReader: Reader<T>? + private var container: CryptoBox? = null + var sender: BitmessageAddress? = null + private set + private var data: T? = null + private set + + constructor(data: T) : super(COMMAND, null) { + this.data = data + this.dataReader = null + } + + private constructor(container: CryptoBox, dataReader: Reader<T>) : super(COMMAND, null) { + this.container = container + this.dataReader = dataReader + } + + fun signAndEncrypt(identity: BitmessageAddress, publicKey: ByteArray) { + val out = ByteArrayOutputStream() + + val privateKey = identity.privateKey ?: throw IllegalStateException("signing identity must have a private key") + + Encode.varInt(identity.version, out) + Encode.varInt(identity.stream, out) + Encode.int32(privateKey.pubkey.behaviorBitfield.toLong(), out) + out.write(privateKey.pubkey.signingKey, 1, 64) + out.write(privateKey.pubkey.encryptionKey, 1, 64) + if (identity.version >= 3) { + Encode.varInt(privateKey.pubkey.nonceTrialsPerByte, out) + Encode.varInt(privateKey.pubkey.extraBytes, out) + } + + data?.write(out) ?: throw IllegalStateException("no unencrypted data available") + Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out) + container = CryptoBox(out.toByteArray(), publicKey) + } + + @Throws(DecryptionFailedException::class) + fun decrypt(privateKey: ByteArray): T { + val `in` = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")) + if (dataReader == null) throw IllegalStateException("no data reader available") + + val addressVersion = varInt(`in`) + val stream = varInt(`in`) + val behaviorBitfield = int32(`in`) + val publicSigningKey = bytes(`in`, 64) + val publicEncryptionKey = bytes(`in`, 64) + val nonceTrialsPerByte = if (addressVersion >= 3) varInt(`in`) else 0 + val extraBytes = if (addressVersion >= 3) varInt(`in`) else 0 + + val sender = BitmessageAddress(Factory.createPubkey( + addressVersion, + stream, + publicSigningKey, + publicEncryptionKey, + nonceTrialsPerByte, + extraBytes, + behaviorBitfield + )) + this.sender = sender + + data = dataReader.read(sender, `in`) + + `in`.checkSignature(sender.pubkey!!) + + return data!! + } + + override fun write(out: OutputStream) { + Encode.varString(COMMAND, out) + container?.write(out) ?: throw IllegalStateException("not encrypted yet") + } + + interface Reader<T> { + fun read(sender: BitmessageAddress, `in`: InputStream): T + } + + private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : InputStream() { + private val out = ByteArrayOutputStream() + + override fun read(): Int { + val read = wrapped.read() + if (read >= 0) out.write(read) + return read + } + + @Throws(IllegalStateException::class) + fun checkSignature(pubkey: Pubkey) { + if (!cryptography().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { + throw IllegalStateException("Signature check failed") + } + } + } + + companion object { + @JvmField val COMMAND = "ENCRYPTED" + + @JvmStatic fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> { + val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size) + return CryptoCustomMessage(cryptoBox, dataReader) + } + } +} diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt new file mode 100644 index 0000000..ef987c3 --- /dev/null +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2017 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.extensions.pow + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.extensions.CryptoCustomMessage +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.varBytes +import ch.dissem.bitmessage.utils.Decode.varString +import ch.dissem.bitmessage.utils.Encode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * @author Christian Basler + */ +data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable { + + override fun write(out: OutputStream) { + out.write(initialHash) + Encode.varString(request.name, out) + Encode.varBytes(data, out) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(initialHash) + Encode.varString(request.name, buffer) + Encode.varBytes(data, buffer) + } + + class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> { + + override fun read(sender: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest { + return ProofOfWorkRequest.read(identity, `in`) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ProofOfWorkRequest) return false + + if (sender != other.sender) return false + if (!Arrays.equals(initialHash, other.initialHash)) return false + if (request != other.request) return false + return Arrays.equals(data, other.data) + } + + override fun hashCode(): Int { + var result = sender.hashCode() + result = 31 * result + Arrays.hashCode(initialHash) + result = 31 * result + request.hashCode() + result = 31 * result + Arrays.hashCode(data) + return result + } + + enum class Request { + CALCULATE, + CALCULATING, + COMPLETE + } + + companion object { + fun read(client: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest { + return ProofOfWorkRequest( + client, + bytes(`in`, 64), + Request.valueOf(varString(`in`)), + varBytes(`in`) + ) + } + } +} diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java deleted file mode 100644 index bf0fa6d..0000000 --- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015 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.extensions; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.assertEquals; - -public class CryptoCustomMessageTest extends TestBase { - @Test - public void ensureEncryptThenDecryptYieldsSameObject() throws Exception { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); - BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); - - GenericPayload payloadBefore = new GenericPayload(0, 1, cryptography().randomBytes(100)); - CryptoCustomMessage<GenericPayload> messageBefore = new CryptoCustomMessage<>(payloadBefore); - messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey())); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - messageBefore.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - - CustomMessage customMessage = CustomMessage.read(in, out.size()); - CryptoCustomMessage<GenericPayload> messageAfter = CryptoCustomMessage.read(customMessage, - new CryptoCustomMessage.Reader<GenericPayload>() { - @Override - public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException { - return GenericPayload.read(0, 1, in, 100); - } - }); - GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey()); - - assertEquals(payloadBefore, payloadAfter); - } - - @Test - public void testWithActualRequest() throws Exception { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); - final BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); - - ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64), - ProofOfWorkRequest.Request.CALCULATE); - - CryptoCustomMessage<ProofOfWorkRequest> messageBefore = new CryptoCustomMessage<>(requestBefore); - messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey())); - - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - messageBefore.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - - CustomMessage customMessage = CustomMessage.read(in, out.size()); - CryptoCustomMessage<ProofOfWorkRequest> messageAfter = CryptoCustomMessage.read(customMessage, - new ProofOfWorkRequest.Reader(sendingIdentity)); - ProofOfWorkRequest requestAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey()); - - assertEquals(requestBefore, requestAfter); - } -} diff --git a/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt b/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt new file mode 100644 index 0000000..f722fa0 --- /dev/null +++ b/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2017 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.extensions + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Test + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream + +import ch.dissem.bitmessage.utils.Singleton.cryptography +import org.junit.Assert.assertEquals + +class CryptoCustomMessageTest : TestBase() { + @Test + fun `ensure encrypt then decrypt yields same object`() { + val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) + val sendingIdentity = BitmessageAddress(privateKey) + + val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100)) + val messageBefore = CryptoCustomMessage(payloadBefore) + messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) + + val out = ByteArrayOutputStream() + messageBefore.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + + val customMessage = CustomMessage.read(`in`, out.size()) + val messageAfter = CryptoCustomMessage.read(customMessage, + object : CryptoCustomMessage.Reader<GenericPayload> { + override fun read(sender: BitmessageAddress, `in`: InputStream): GenericPayload { + return GenericPayload.read(0, 1, `in`, 100) + } + }) + val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey) + + assertEquals(payloadBefore, payloadAfter) + } + + @Test + fun `test with actual request`() { + val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) + val sendingIdentity = BitmessageAddress(privateKey) + + val requestBefore = ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64), + ProofOfWorkRequest.Request.CALCULATE) + + val messageBefore = CryptoCustomMessage(requestBefore) + messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) + + + val out = ByteArrayOutputStream() + messageBefore.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + + val customMessage = CustomMessage.read(`in`, out.size()) + val messageAfter = CryptoCustomMessage.read(customMessage, + ProofOfWorkRequest.Reader(sendingIdentity)) + val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey) + + assertEquals(requestBefore, requestAfter) + } +} diff --git a/gradle.properties b/gradle.properties index bb8beb4..ad2d684 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,8 @@ # Don't change this file - override those properties in the # gradle.properties file in your home directory instead - signing.keyId= signing.password= #signing.secretKeyRingFile= - ossrhUsername= ossrhPassword= - -systemTestsEnabled=false \ No newline at end of file +systemTestsEnabled=false diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java index 4ad759a..8bcd2b0 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java @@ -78,7 +78,7 @@ public abstract class AbstractConnection { this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build(); this.node = node; this.listener = context.getNetworkListener(); - this.syncTimeout = (syncTimeout > 0 ? UnixTime.now(+syncTimeout) : 0); + this.syncTimeout = (syncTimeout > 0 ? UnixTime.now() + syncTimeout : 0); this.requestedObjects = Collections.newSetFromMap(new ConcurrentHashMap<InventoryVector, Boolean>(10_000)); this.ivCache = new ConcurrentHashMap<>(); this.sendingQueue = new ConcurrentLinkedDeque<>(); @@ -146,7 +146,7 @@ public abstract class AbstractConnection { missing.removeAll(commonRequestedObjects.keySet()); LOG.trace("Received inventory with " + originalSize + " elements, of which are " + missing.size() + " missing."); - send(new GetData.Builder().inventory(missing).build()); + send(new GetData(missing)); } private void receiveMessage(GetData getData) { @@ -195,9 +195,7 @@ public abstract class AbstractConnection { } public void offer(InventoryVector iv) { - sendingQueue.offer(new Inv.Builder() - .addInventoryVector(iv) - .build()); + sendingQueue.offer(new Inv(Collections.singletonList(iv))); updateIvCache(Collections.singletonList(iv)); } @@ -210,7 +208,7 @@ public abstract class AbstractConnection { } private void cleanupIvCache() { - Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE); + long fiveMinutesAgo = UnixTime.now() - 5 * MINUTE; for (Map.Entry<InventoryVector, Long> entry : ivCache.entrySet()) { if (entry.getValue() < fiveMinutesAgo) { ivCache.remove(entry.getKey()); @@ -256,15 +254,13 @@ public abstract class AbstractConnection { private void sendAddresses() { List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams); - sendingQueue.offer(new Addr.Builder().addresses(addresses).build()); + sendingQueue.offer(new Addr(addresses)); } private void sendInventory() { List<InventoryVector> inventory = ctx.getInventory().getInventory(streams); for (int i = 0; i < inventory.size(); i += 50000) { - sendingQueue.offer(new Inv.Builder() - .inventory(inventory.subList(i, Math.min(inventory.size(), i + 50000))) - .build()); + sendingQueue.offer(new Inv(inventory.subList(i, Math.min(inventory.size(), i + 50000)))); } } @@ -328,10 +324,6 @@ public abstract class AbstractConnection { protected abstract void send(MessagePayload payload); - public enum Mode {SERVER, CLIENT, SYNC} - - public enum State {CONNECTING, ACTIVE, DISCONNECTED} - @Override public boolean equals(Object o) { if (this == o) return true; @@ -344,4 +336,8 @@ public abstract class AbstractConnection { public int hashCode() { return Objects.hash(node); } + + public enum Mode {SERVER, CLIENT, SYNC} + + public enum State {CONNECTING, ACTIVE, DISCONNECTED} } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java index 6d7ae04..7e0c96e 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java @@ -26,7 +26,7 @@ import java.util.Iterator; import java.util.List; import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; -import static ch.dissem.bitmessage.networking.DefaultNetworkHandler.NETWORK_MAGIC_NUMBER; +import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; /** * @author Christian Basler diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java index b0966d4..b3e51e8 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java @@ -35,6 +35,7 @@ import java.net.Socket; import java.util.*; import java.util.concurrent.*; +import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; import static ch.dissem.bitmessage.utils.DebugUtils.inc; @@ -193,7 +194,7 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { } return new Property("network", null, new Property("connectionManager", running ? "running" : "stopped"), - new Property("connections", null, streamProperties), + new Property("connections", streamProperties), new Property("requestedObjects", requestedObjects.size()) ); } @@ -222,7 +223,7 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { if (connection.knowsOf(next)) { List<InventoryVector> ivs = distribution.get(connection); if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); ivs.clear(); } ivs.add(next); @@ -241,7 +242,7 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { for (Connection connection : distribution.keySet()) { List<InventoryVector> ivs = distribution.get(connection); if (!ivs.isEmpty()) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); } } } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java index 0a4e7a2..eca4d5d 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java @@ -37,12 +37,10 @@ public class ServerRunnable implements Runnable, Closeable { private final InternalContext ctx; private final ServerSocket serverSocket; private final DefaultNetworkHandler networkHandler; - private final NetworkHandler.MessageListener listener; public ServerRunnable(InternalContext ctx, DefaultNetworkHandler networkHandler) throws IOException { this.ctx = ctx; this.networkHandler = networkHandler; - this.listener = ctx.getNetworkListener(); this.serverSocket = new ServerSocket(ctx.getPort()); } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java index 04e6d4d..7f6dc5d 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java @@ -25,7 +25,9 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.NodeException; import ch.dissem.bitmessage.factory.V3MessageReader; +import ch.dissem.bitmessage.networking.AbstractConnection; import ch.dissem.bitmessage.ports.NetworkHandler; +import ch.dissem.bitmessage.utils.DebugUtils; import ch.dissem.bitmessage.utils.Property; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,11 +41,14 @@ import java.nio.channels.*; import java.util.*; import java.util.concurrent.*; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.*; +import static ch.dissem.bitmessage.constants.Network.HEADER_SIZE; +import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; +import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; +import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; +import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC; import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; import static ch.dissem.bitmessage.networking.AbstractConnection.State.DISCONNECTED; import static ch.dissem.bitmessage.utils.Collections.selectRandom; -import static ch.dissem.bitmessage.utils.DebugUtils.inc; import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; import static java.nio.channels.SelectionKey.*; @@ -434,7 +439,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex if (connection.knowsOf(next) && !connection.requested(next)) { List<InventoryVector> ivs = distribution.get(connection); if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); ivs.clear(); } ivs.add(next); @@ -458,7 +463,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex for (ConnectionInfo connection : distribution.keySet()) { List<InventoryVector> ivs = distribution.get(connection); if (!ivs.isEmpty()) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); } } } @@ -474,9 +479,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex for (long stream : connection.getStreams()) { streams.add(stream); if (connection.getMode() == SERVER) { - inc(incomingConnections, stream); + DebugUtils.inc(incomingConnections, stream); } else { - inc(outgoingConnections, stream); + DebugUtils.inc(outgoingConnections, stream); } } } @@ -495,7 +500,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex } return new Property("network", null, new Property("connectionManager", isRunning() ? "running" : "stopped"), - new Property("connections", null, streamProperties), + new Property("connections", streamProperties), new Property("requestedObjects", requestedObjects.size()) ); } diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java index 5422997..b296bc3 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java @@ -97,10 +97,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private List<BitmessageAddress> find(String where) { List<BitmessageAddress> result = new LinkedList<>(); try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed, chan " + - "FROM Address WHERE " + where) + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed, chan " + + "FROM Address WHERE " + where) ) { while (rs.next()) { BitmessageAddress address; @@ -111,7 +111,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito Blob publicKeyBlob = rs.getBlob("public_key"); if (publicKeyBlob != null) { Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), - publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false); + publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false); if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { pubkey = new V4Pubkey((V3Pubkey) pubkey); } @@ -127,7 +127,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito result.add(address); } - } catch (IOException | SQLException e) { + } catch (SQLException e) { LOG.error(e.getMessage(), e); } return result; @@ -135,10 +135,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private boolean exists(BitmessageAddress address) { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " + - "WHERE address='" + address.getAddress() + "'") + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " + + "WHERE address='" + address.getAddress() + "'") ) { if (rs.next()) { return rs.getInt(1) > 0; @@ -172,8 +172,8 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito } statement.append(", subscribed=?, chan=? WHERE address=?"); try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement(statement.toString()) + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement(statement.toString()) ) { int i = 0; ps.setString(++i, address.getAlias()); @@ -192,10 +192,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private void insert(BitmessageAddress address) throws IOException, SQLException { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement( + "INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)") ) { ps.setString(1, address.getAddress()); ps.setLong(2, address.getVersion()); @@ -221,8 +221,8 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito @Override public void remove(BitmessageAddress address) { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement() + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement() ) { stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.getAddress() + "'"); } catch (SQLException e) { diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java index b0be9c1..8548267 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java @@ -17,24 +17,16 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.exception.ApplicationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.Collection; - -import static ch.dissem.bitmessage.utils.Strings.hex; /** * Helper class that does Flyway migration, provides JDBC connections and some helper methods. */ public abstract class JdbcHelper { - private static final Logger LOG = LoggerFactory.getLogger(JdbcHelper.class); protected final JdbcConfig config; diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java index 33b7290..65ea15f 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java @@ -49,8 +49,8 @@ public class JdbcInventory extends JdbcHelper implements Inventory { List<InventoryVector> result = new LinkedList<>(); for (long stream : streams) { getCache(stream).entrySet().stream() - .filter(e -> e.getValue() > now()) - .forEach(e -> result.add(e.getKey())); + .filter(e -> e.getValue() > now()) + .forEach(e -> result.add(e.getKey())); } return result; } @@ -63,10 +63,10 @@ public class JdbcInventory extends JdbcHelper implements Inventory { result = new ConcurrentHashMap<>(); cache.put(stream, result); try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory " + - "WHERE expires > " + now(-5 * MINUTE) + " AND stream = " + stream) + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory " + + "WHERE expires > " + (now() - 5 * MINUTE) + " AND stream = " + stream) ) { while (rs.next()) { result.put(InventoryVector.fromHash(rs.getBytes("hash")), rs.getLong("expires")); @@ -91,9 +91,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory { @Override public ObjectMessage getObject(InventoryVector vector) { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'") + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'") ) { if (rs.next()) { Blob data = rs.getBlob("data"); @@ -121,9 +121,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory { query.append(" AND type IN (").append(join(types)).append(')'); } try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(query.toString()) + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(query.toString()) ) { List<ObjectMessage> result = new LinkedList<>(); while (rs.next()) { @@ -143,9 +143,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory { return; try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory " + - "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory " + + "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)") ) { InventoryVector iv = object.getInventoryVector(); LOG.trace("Storing object " + iv); @@ -167,21 +167,21 @@ public class JdbcInventory extends JdbcHelper implements Inventory { @Override public boolean contains(ObjectMessage object) { return getCache(object.getStream()).entrySet().stream() - .anyMatch(x -> x.getKey().equals(object.getInventoryVector())); + .anyMatch(x -> x.getKey().equals(object.getInventoryVector())); } @Override public void cleanup() { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement() + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement() ) { - stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + now(-5 * MINUTE)); + stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + (now() - 5 * MINUTE)); } catch (SQLException e) { LOG.debug(e.getMessage(), e); } for (Map<InventoryVector, Long> c : cache.values()) { - c.entrySet().removeIf(e -> e.getValue() < now(-5 * MINUTE)); + c.entrySet().removeIf(e -> e.getValue() < (now() - 5 * MINUTE)); } } } diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java index 11c7028..e9fffb6 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java @@ -111,8 +111,8 @@ public class JdbcMessageRepository extends AbstractMessageRepository implements long id = rs.getLong("id"); builder.id(id); builder.IV(InventoryVector.fromHash(iv)); - builder.from(ctx.getAddressRepository().getAddress(rs.getString("sender"))); - builder.to(ctx.getAddressRepository().getAddress(rs.getString("recipient"))); + builder.from(getCtx().getAddressRepository().getAddress(rs.getString("sender"))); + builder.to(getCtx().getAddressRepository().getAddress(rs.getString("recipient"))); builder.ackData(rs.getBytes("ack_data")); builder.sent(rs.getObject("sent", Long.class)); builder.received(rs.getObject("received", Long.class)); @@ -127,7 +127,7 @@ public class JdbcMessageRepository extends AbstractMessageRepository implements message.setInitialHash(rs.getBytes("initial_hash")); result.add(message); } - } catch (IOException | SQLException e) { + } catch (SQLException e) { LOG.error(e.getMessage(), e); } return result; diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java index a889db6..acecd33 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java @@ -1,3 +1,19 @@ +/* + * 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.repository; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; @@ -33,7 +49,7 @@ public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry { PreparedStatement ps = connection.prepareStatement( "DELETE FROM Node WHERE time<?") ) { - ps.setLong(1, now(-28 * DAY)); + ps.setLong(1, now() - 28 * DAY); ps.executeUpdate(); } catch (SQLException e) { LOG.error(e.getMessage(), e); @@ -135,7 +151,7 @@ public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry { public void offerAddresses(List<NetworkAddress> nodes) { cleanUp(); nodes.stream() - .filter(node -> node.getTime() < now(+2 * MINUTE) && node.getTime() > now(-28 * DAY)) + .filter(node -> node.getTime() < now() + 2 * MINUTE && node.getTime() > now() - 28 * DAY) .forEach(node -> { synchronized (this) { NetworkAddress existing = loadExisting(node); diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java index 0fda3fa..2af1450 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java @@ -1,3 +1,19 @@ +/* + * 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.repository; import ch.dissem.bitmessage.InternalContext; @@ -30,9 +46,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public Item getItem(byte[] initialHash) { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " + - "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " + + "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?") ) { ps.setBytes(1, initialHash); try (ResultSet rs = ps.executeQuery()) { @@ -40,17 +56,17 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork Blob data = rs.getBlob("data"); if (rs.getObject("message_id") == null) { return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes") + Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes") ); } else { return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes"), - rs.getLong("expiration_time"), - ctx.getMessageRepository().getMessage(rs.getLong("message_id")) + Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes"), + rs.getLong("expiration_time"), + ctx.getMessageRepository().getMessage(rs.getLong("message_id")) ); } } else { @@ -66,9 +82,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public List<byte[]> getItems() { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW") + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW") ) { List<byte[]> result = new LinkedList<>(); while (rs.next()) { @@ -84,27 +100,27 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public void putObject(Item item) { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " + - "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " + + "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)") ) { - ps.setBytes(1, cryptography().getInitialHash(item.object)); - writeBlob(ps, 2, item.object); - ps.setLong(3, item.object.getVersion()); - ps.setLong(4, item.nonceTrialsPerByte); - ps.setLong(5, item.extraBytes); + ps.setBytes(1, cryptography().getInitialHash(item.getObject())); + writeBlob(ps, 2, item.getObject()); + ps.setLong(3, item.getObject().getVersion()); + ps.setLong(4, item.getNonceTrialsPerByte()); + ps.setLong(5, item.getExtraBytes()); - if (item.message == null) { + if (item.getMessage() == null) { ps.setObject(6, null); ps.setObject(7, null); } else { - ps.setLong(6, item.expirationTime); - ps.setLong(7, (Long) item.message.getId()); + ps.setLong(6, item.getExpirationTime()); + ps.setLong(7, (Long) item.getMessage().getId()); } ps.executeUpdate(); } catch (IOException | SQLException e) { - LOG.debug("Error storing object of type " + item.object.getPayload().getClass().getSimpleName(), e); + LOG.debug("Error storing object of type " + item.getObject().getPayload().getClass().getSimpleName(), e); throw new ApplicationException(e); } } @@ -117,8 +133,8 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public void removeObject(byte[] initialHash) { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?") ) { ps.setBytes(1, initialHash); ps.executeUpdate(); diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java index 7f3098b..c028837 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java @@ -17,7 +17,6 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import org.junit.Before; import org.junit.Test; @@ -72,7 +71,7 @@ public class JdbcAddressRepositoryTest extends TestBase { BitmessageAddress storedIdentity = repo.findIdentity(identity.getTag()); assertEquals(identity, storedIdentity); - assertTrue(storedIdentity.has(Pubkey.Feature.DOES_ACK)); + assertTrue(storedIdentity.has(DOES_ACK)); } @Test @@ -181,4 +180,4 @@ public class JdbcAddressRepositoryTest extends TestBase { subscription.setSubscribed(true); repo.save(subscription); } -} \ No newline at end of file +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java index 43be79f..dd0bd07 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java @@ -25,8 +25,7 @@ import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.ports.AddressRepository; -import ch.dissem.bitmessage.ports.MessageRepository; +import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.utils.TestUtils; import ch.dissem.bitmessage.utils.UnixTime; import org.hamcrest.BaseMatcher; @@ -45,6 +44,7 @@ import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; public class JdbcMessageRepositoryTest extends TestBase { private BitmessageAddress contactA; @@ -64,12 +64,21 @@ public class JdbcMessageRepositoryTest extends TestBase { config.reset(); AddressRepository addressRepo = new JdbcAddressRepository(config); repo = new JdbcMessageRepository(config); - new InternalContext(new BitmessageContext.Builder() - .cryptography(cryptography()) - .addressRepo(addressRepo) - .messageRepo(repo) + new InternalContext( + cryptography(), + mock(Inventory.class), + mock(NodeRegistry.class), + mock(NetworkHandler.class), + addressRepo, + repo, + mock(ProofOfWorkRepository.class), + mock(ProofOfWorkEngine.class), + mock(CustomCommandHandler.class), + mock(BitmessageContext.Listener.class), + mock(Labeler.class), + 12345, + 10, 10 ); - BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); contactA = new BitmessageAddress(tmp.getAddress()); contactA.setPubkey(tmp.getPubkey()); @@ -225,7 +234,7 @@ public class JdbcMessageRepositoryTest extends TestBase { message.updateNextTry(); assertThat(message.getRetries(), is(1)); assertThat(message.getNextTry(), greaterThan(UnixTime.now())); - assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now(+2))); + assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now() + 2)); repo.save(message); Thread.sleep(4100); // somewhat longer than 2*TTL List<Plaintext> messagesToResend = repo.findMessagesToResend(); diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java index 48ae664..1078e24 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java @@ -18,7 +18,6 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NodeRegistry; -import ch.dissem.bitmessage.utils.UnixTime; import org.junit.Before; import org.junit.Test; @@ -28,7 +27,6 @@ import java.util.List; import static ch.dissem.bitmessage.utils.UnixTime.now; import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java deleted file mode 100644 index 4396eb5..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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.repository; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.ports.AddressRepository; -import ch.dissem.bitmessage.ports.MessageRepository; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item; -import ch.dissem.bitmessage.utils.TestUtils; -import ch.dissem.bitmessage.utils.UnixTime; -import org.junit.Before; -import org.junit.Test; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -/** - * @author Christian Basler - */ -public class JdbcProofOfWorkRepositoryTest extends TestBase { - private TestJdbcConfig config; - private JdbcProofOfWorkRepository repo; - private AddressRepository addressRepo; - private MessageRepository messageRepo; - - private byte[] initialHash1; - private byte[] initialHash2; - - @Before - public void setUp() throws Exception { - config = new TestJdbcConfig(); - config.reset(); - - addressRepo = new JdbcAddressRepository(config); - messageRepo = new JdbcMessageRepository(config); - repo = new JdbcProofOfWorkRepository(config); - InternalContext ctx = new InternalContext(new BitmessageContext.Builder() - .addressRepo(addressRepo) - .messageRepo(messageRepo) - .powRepo(repo) - .cryptography(cryptography()) - ); - - repo.putObject(new ObjectMessage.Builder() - .payload(new GetPubkey(new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(), - 1000, 1000); - initialHash1 = repo.getItems().get(0); - - BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress recipient = TestUtils.loadContact(); - addressRepo.save(sender); - addressRepo.save(recipient); - Plaintext plaintext = new Plaintext.Builder(MSG) - .ackData(cryptography().randomBytes(32)) - .from(sender) - .to(recipient) - .message("Subject", "Message") - .status(Plaintext.Status.DOING_PROOF_OF_WORK) - .build(); - messageRepo.save(plaintext); - initialHash2 = cryptography().getInitialHash(plaintext.getAckMessage()); - repo.putObject(new Item( - plaintext.getAckMessage(), - 1000, 1000, - UnixTime.now(+10 * MINUTE), - plaintext - )); - } - - @Test - public void ensureObjectIsStored() throws Exception { - int sizeBefore = repo.getItems().size(); - repo.putObject(new ObjectMessage.Builder() - .payload(new GetPubkey(new BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2"))).build(), - 1000, 1000); - assertThat(repo.getItems().size(), is(sizeBefore + 1)); - } - - @Test - public void ensureAckObjectsAreStored() throws Exception { - int sizeBefore = repo.getItems().size(); - BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress recipient = TestUtils.loadContact(); - addressRepo.save(sender); - addressRepo.save(recipient); - Plaintext plaintext = new Plaintext.Builder(MSG) - .ackData(cryptography().randomBytes(32)) - .from(sender) - .to(recipient) - .message("Subject", "Message") - .status(Plaintext.Status.DOING_PROOF_OF_WORK) - .build(); - messageRepo.save(plaintext); - repo.putObject(new Item( - plaintext.getAckMessage(), - 1000, 1000, - UnixTime.now(+10 * MINUTE), - plaintext - )); - assertThat(repo.getItems().size(), is(sizeBefore + 1)); - } - - @Test - public void ensureItemCanBeRetrieved() { - Item item = repo.getItem(initialHash1); - assertThat(item, notNullValue()); - assertThat(item.object.getPayload(), instanceOf(GetPubkey.class)); - assertThat(item.nonceTrialsPerByte, is(1000L)); - assertThat(item.extraBytes, is(1000L)); - } - - @Test - public void ensureAckItemCanBeRetrieved() { - Item item = repo.getItem(initialHash2); - assertThat(item, notNullValue()); - assertThat(item.object.getPayload(), instanceOf(GenericPayload.class)); - assertThat(item.nonceTrialsPerByte, is(1000L)); - assertThat(item.extraBytes, is(1000L)); - assertThat(item.expirationTime, not(0)); - assertThat(item.message, notNullValue()); - assertThat(item.message.getFrom().getPrivateKey(), notNullValue()); - assertThat(item.message.getTo().getPubkey(), notNullValue()); - } - - @Test(expected = RuntimeException.class) - public void ensureRetrievingNonexistingItemThrowsException() { - repo.getItem(new byte[0]); - } - - @Test - public void ensureItemCanBeDeleted() { - repo.removeObject(initialHash1); - repo.removeObject(initialHash2); - assertTrue(repo.getItems().isEmpty()); - } - - @Test - public void ensureDeletionOfNonexistingItemIsHandledSilently() { - repo.removeObject(new byte[0]); - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt new file mode 100644 index 0000000..76031e9 --- /dev/null +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2017 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.repository + +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.MSG +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.GetPubkey +import ch.dissem.bitmessage.entity.payload.ObjectPayload +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.ports.AddressRepository +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.UnixTime +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import org.hamcrest.CoreMatchers.* +import org.junit.Assert.assertThat +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import kotlin.properties.Delegates + +/** + * @author Christian Basler + */ +class JdbcProofOfWorkRepositoryTest : TestBase() { + private var config: TestJdbcConfig by Delegates.notNull<TestJdbcConfig>() + private var repo: JdbcProofOfWorkRepository by Delegates.notNull<JdbcProofOfWorkRepository>() + private var addressRepo: AddressRepository by Delegates.notNull<AddressRepository>() + private var messageRepo: MessageRepository by Delegates.notNull<MessageRepository>() + + private var initialHash1: ByteArray by Delegates.notNull<ByteArray>() + private var initialHash2: ByteArray by Delegates.notNull<ByteArray>() + + @Before + fun setUp() { + config = TestJdbcConfig() + config.reset() + + addressRepo = JdbcAddressRepository(config) + messageRepo = JdbcMessageRepository(config) + repo = JdbcProofOfWorkRepository(config) + TestUtils.mockedInternalContext( + addressRepository = addressRepo, + messageRepository = messageRepo, + proofOfWorkRepository = repo, + cryptography = cryptography() + ) + + repo.putObject(ObjectMessage.Builder() + .payload(GetPubkey(BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(), + 1000, 1000) + initialHash1 = repo.getItems()[0] + + val sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val recipient = TestUtils.loadContact() + addressRepo.save(sender) + addressRepo.save(recipient) + val plaintext = Plaintext.Builder(MSG) + .ackData(cryptography().randomBytes(32)) + .from(sender) + .to(recipient) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build() + messageRepo.save(plaintext) + initialHash2 = cryptography().getInitialHash(plaintext.ackMessage!!) + repo.putObject(Item( + plaintext.ackMessage!!, + 1000, 1000, + UnixTime.now + 10 * MINUTE, + plaintext + )) + } + + @Test + fun `ensure object is stored`() { + val sizeBefore = repo.getItems().size + repo.putObject(ObjectMessage.Builder() + .payload(GetPubkey(BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2"))).build(), + 1000, 1000) + assertThat(repo.getItems().size, `is`(sizeBefore + 1)) + } + + @Test + fun `ensure ack objects are stored`() { + val sizeBefore = repo.getItems().size + val sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val recipient = TestUtils.loadContact() + addressRepo.save(sender) + addressRepo.save(recipient) + val plaintext = Plaintext.Builder(MSG) + .ackData(cryptography().randomBytes(32)) + .from(sender) + .to(recipient) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build() + messageRepo.save(plaintext) + repo.putObject(Item( + plaintext.ackMessage!!, + 1000, 1000, + UnixTime.now + 10 * MINUTE, + plaintext + )) + assertThat(repo.getItems().size, `is`(sizeBefore + 1)) + } + + @Test + fun `ensure item can be retrieved`() { + val item = repo.getItem(initialHash1) + assertThat(item, notNullValue()) + assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) + assertThat(item.nonceTrialsPerByte, `is`(1000L)) + assertThat(item.extraBytes, `is`(1000L)) + } + + @Test + fun `ensure ack item can be retrieved`() { + val item = repo.getItem(initialHash2) + assertThat(item, notNullValue()) + assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) + assertThat(item.nonceTrialsPerByte, `is`(1000L)) + assertThat(item.extraBytes, `is`(1000L)) + assertThat(item.expirationTime, not<Number>(0)) + assertThat(item.message, notNullValue()) + assertThat<PrivateKey>(item.message?.from?.privateKey, notNullValue()) + assertThat<Pubkey>(item.message?.to?.pubkey, notNullValue()) + } + + @Test(expected = RuntimeException::class) + fun `ensure retrieving nonexisting item causes exception`() { + repo.getItem(ByteArray(0)) + } + + @Test + fun `ensure item can be deleted`() { + repo.removeObject(initialHash1) + repo.removeObject(initialHash2) + assertTrue(repo.getItems().isEmpty()) + } + + @Test + fun `ensure deletion of nonexisting item is handled silently`() { + repo.removeObject(ByteArray(0)) + } +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java deleted file mode 100644 index be386cd..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015 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.repository; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine; -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.utils.Singleton; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Created by chris on 20.07.15. - */ -public class TestBase { - static { - BouncyCryptography security = new BouncyCryptography(); - Singleton.initialize(security); - InternalContext ctx = mock(InternalContext.class); - when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - security.setContext(ctx); - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt new file mode 100644 index 0000000..107e462 --- /dev/null +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2017 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.repository + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestUtils.mockedInternalContext + +/** + * Created by chris on 20.07.15. + */ +open class TestBase { + companion object { + init { + val security = BouncyCryptography() + Singleton.initialize(security) + mockedInternalContext( + cryptography = security, + proofOfWorkEngine = MultiThreadedPOWEngine() + ) + } + } +}