From 83e50e1ad1e710a30de631dde3f3554a88965a24 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Thu, 8 Jun 2017 16:59:24 +0200 Subject: [PATCH] Migrated BitmessageContext and fixed some tests --- .../dissem/bitmessage/BitmessageContext.java | 452 ------------------ .../ch/dissem/bitmessage/BitmessageContext.kt | 451 +++++++++++++++++ .../ch/dissem/bitmessage/entity/Plaintext.kt | 42 +- .../dissem/bitmessage/ports/DefaultLabeler.kt | 3 +- .../bitmessage/BitmessageContextTest.kt | 46 +- cryptography-bc/build.gradle | 1 + .../bitmessage/security/CryptographyTest.java | 172 ------- .../bitmessage/security/CryptographyTest.kt | 172 +++++++ cryptography-sc/build.gradle | 1 + .../bitmessage/security/CryptographyTest.java | 174 ------- .../bitmessage/security/CryptographyTest.kt | 177 +++++++ .../repository/JdbcInventoryTest.java | 12 +- 12 files changed, 872 insertions(+), 831 deletions(-) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt delete mode 100644 cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java create mode 100644 cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt delete mode 100644 cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java create mode 100644 cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java deleted file mode 100644 index 4bf0adc..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ /dev/null @@ -1,452 +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.Broadcast; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Property; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetAddress; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -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.entity.Plaintext.Type.BROADCAST; -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.UnixTime.HOUR; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; - -/** - *

Use this class if you want to create a Bitmessage client.

- * You'll need the Builder to create a BitmessageContext, and set the following properties: - * - *

The default implementations in the different module builds can be used.

- *

The port defaults to 8444 (the default Bitmessage port)

- */ -public class BitmessageContext { - public static final int CURRENT_VERSION = 3; - private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class); - - private final InternalContext ctx; - - private final Labeler labeler; - - private final boolean sendPubkeyOnIdentityCreation; - - private BitmessageContext(Builder 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; - if (builder.listener instanceof Listener.WithContext) { - ((Listener.WithContext) builder.listener).setContext(this); - } - } - - public AddressRepository addresses() { - return ctx.getAddressRepository(); - } - - public MessageRepository messages() { - return ctx.getMessageRepository(); - } - - public Labeler labeler() { - return labeler; - } - - public BitmessageAddress createIdentity(boolean shorter, Feature... features) { - final BitmessageAddress identity = new BitmessageAddress(new PrivateKey( - shorter, - ctx.getStreams()[0], - NETWORK_NONCE_TRIALS_PER_BYTE, - NETWORK_EXTRA_BYTES, - features - )); - ctx.getAddressRepository().save(identity); - if (sendPubkeyOnIdentityCreation) { - ctx.sendPubkey(identity, identity.getStream()); - } - return identity; - } - - public BitmessageAddress joinChan(String passphrase, String address) { - BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase); - chan.setAlias(passphrase); - ctx.getAddressRepository().save(chan); - return chan; - } - - public BitmessageAddress createChan(String passphrase) { - // FIXME: hardcoded stream number - 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.Companion.deterministic( - passphrase, numberOfAddresses, version, stream, shorter); - for (int i = 0; i < result.size(); i++) { - BitmessageAddress address = result.get(i); - address.setAlias("deterministic (" + (i + 1) + ")"); - ctx.getAddressRepository().save(address); - } - return result; - } - - public void broadcast(final BitmessageAddress from, final String subject, final String message) { - Plaintext msg = new Plaintext.Builder(BROADCAST) - .from(from) - .message(subject, message) - .build(); - send(msg); - } - - public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) { - if (from.getPrivateKey() == null) { - throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); - } - Plaintext msg = new Plaintext.Builder(MSG) - .from(from) - .to(to) - .message(subject, message) - .build(); - send(msg); - } - - public void send(final Plaintext msg) { - if (msg.getFrom().getPrivateKey() == null) { - throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); - } - labeler().markAsSending(msg); - BitmessageAddress to = msg.getTo(); - if (to != null) { - if (to.getPubkey() == null) { - LOG.info("Public key is missing from recipient. Requesting."); - ctx.requestPubkey(to); - } - if (to.getPubkey() == null) { - ctx.getMessageRepository().save(msg); - } - } - if (to == null || to.getPubkey() != null) { - LOG.info("Sending message."); - ctx.getMessageRepository().save(msg); - if (msg.getType() == MSG) { - ctx.send(msg); - } else { - ctx.send( - msg.getFrom(), - to, - Factory.getBroadcast(msg), - msg.getTTL() - ); - } - } - } - - public void startup() { - ctx.getNetworkHandler().start(); - } - - public void shutdown() { - ctx.getNetworkHandler().stop(); - } - - /** - * @param host a trusted node that must be reliable (it's used for every synchronization) - * @param port of the trusted host, default is 8444 - * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even - * if not all objects were fetched - * @param wait waits for the synchronization thread to finish - */ - public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) { - Future future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds); - if (wait) { - try { - future.get(); - } catch (InterruptedException e) { - LOG.info("Thread was interrupted. Trying to shut down synchronization and returning."); - future.cancel(true); - } catch (CancellationException | ExecutionException e) { - LOG.debug(e.getMessage(), e); - } - } - } - - /** - * 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}. - * - * @param server the node's address - * @param port the node's port - * @param request the request - * @return the response - */ - public CustomMessage send(InetAddress server, int port, CustomMessage request) { - return ctx.getNetworkHandler().send(server, port, request); - } - - /** - * Removes expired objects from the inventory. You should call this method regularly, - * e.g. daily and on each shutdown. - */ - public void cleanup() { - ctx.getInventory().cleanup(); - } - - /** - * Sends messages again whose time to live expired without being acknowledged. (And whose - * recipient is expected to send acknowledgements. - *

- * You should call this method regularly, but be aware of the following: - *

    - *
  • As messages might be sent, POW will be done. It is therefore not advised to - * call it on shutdown.
  • - *
  • It shouldn't be called right after startup, as it's possible the missing - * acknowledgement was sent while the client was offline.
  • - *
  • Other than that, the call isn't expensive as long as there is no message - * to send, so it might be a good idea to just call it every few minutes.
  • - *
- */ - public void resendUnacknowledgedMessages() { - ctx.resendUnacknowledged(); - } - - public boolean isRunning() { - return ctx.getNetworkHandler().isRunning(); - } - - public void addContact(BitmessageAddress contact) { - ctx.getAddressRepository().save(contact); - if (contact.getPubkey() == null) { - BitmessageAddress stored = ctx.getAddressRepository().getAddress(contact.getAddress()); - if (stored.getPubkey() == null) { - ctx.requestPubkey(contact); - } - } - } - - public void addSubscribtion(BitmessageAddress address) { - address.setSubscribed(true); - ctx.getAddressRepository().save(address); - tryToFindBroadcastsForAddress(address); - } - - private void tryToFindBroadcastsForAddress(BitmessageAddress address) { - for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.Companion.getVersion(address), ObjectType.BROADCAST)) { - try { - Broadcast broadcast = (Broadcast) object.getPayload(); - broadcast.decrypt(address); - // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with - // other subscriptions and the interface stays as simple as possible. - ctx.getNetworkListener().receive(object); - } catch (DecryptionFailedException ignore) { - } catch (Exception e) { - LOG.debug(e.getMessage(), e); - } - } - } - - public Property status() { - return new Property("status", null, - ctx.getNetworkHandler().getNetworkStatus(), - new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size()) - ); - } - - /** - * Returns the {@link InternalContext} - normally you wouldn't need it, - * unless you are doing something crazy with the protocol. - */ - public InternalContext internals() { - return ctx; - } - - public interface Listener { - void receive(Plaintext plaintext); - - /** - * A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot. - */ - interface WithContext extends Listener { - void setContext(BitmessageContext ctx); - } - } - - public static final class Builder { - int port = 8444; - Inventory inventory; - NodeRegistry nodeRegistry; - NetworkHandler networkHandler; - AddressRepository addressRepo; - MessageRepository messageRepo; - ProofOfWorkRepository proofOfWorkRepository; - ProofOfWorkEngine proofOfWorkEngine; - Cryptography cryptography; - CustomCommandHandler customCommandHandler; - Labeler labeler; - Listener listener; - int connectionLimit = 150; - long connectionTTL = 30 * MINUTE; - boolean sendPubkeyOnIdentityCreation = true; - - public Builder port(int port) { - this.port = port; - return this; - } - - public Builder inventory(Inventory inventory) { - this.inventory = inventory; - return this; - } - - public Builder nodeRegistry(NodeRegistry nodeRegistry) { - this.nodeRegistry = nodeRegistry; - return this; - } - - public Builder networkHandler(NetworkHandler networkHandler) { - this.networkHandler = networkHandler; - return this; - } - - public Builder addressRepo(AddressRepository addressRepo) { - this.addressRepo = addressRepo; - return this; - } - - public Builder messageRepo(MessageRepository messageRepo) { - this.messageRepo = messageRepo; - return this; - } - - public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) { - this.proofOfWorkRepository = proofOfWorkRepository; - return this; - } - - public Builder cryptography(Cryptography cryptography) { - this.cryptography = cryptography; - return this; - } - - public Builder customCommandHandler(CustomCommandHandler handler) { - this.customCommandHandler = handler; - return this; - } - - public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) { - this.proofOfWorkEngine = proofOfWorkEngine; - return this; - } - - public Builder labeler(Labeler labeler) { - this.labeler = labeler; - return this; - } - - public Builder listener(Listener listener) { - this.listener = listener; - return this; - } - - public Builder connectionLimit(int connectionLimit) { - this.connectionLimit = connectionLimit; - return this; - } - - public Builder connectionTTL(int hours) { - this.connectionTTL = hours * HOUR; - return this; - } - - /** - * By default a client will send the public key when an identity is being created. On weaker devices - * this behaviour might not be desirable. - */ - public Builder doNotSendPubkeyOnIdentityCreation() { - this.sendPubkeyOnIdentityCreation = false; - return this; - } - - public BitmessageContext build() { - nonNull("inventory", inventory); - nonNull("nodeRegistry", nodeRegistry); - nonNull("networkHandler", networkHandler); - nonNull("addressRepo", addressRepo); - nonNull("messageRepo", messageRepo); - nonNull("proofOfWorkRepo", proofOfWorkRepository); - if (proofOfWorkEngine == null) { - proofOfWorkEngine = new MultiThreadedPOWEngine(); - } - if (labeler == null) { - labeler = new DefaultLabeler(); - } - if (customCommandHandler == null) { - customCommandHandler = new CustomCommandHandler() { - @Override - public MessagePayload handle(CustomMessage request) { - LOG.debug("Received custom request, but no custom command handler configured."); - return null; - } - }; - } - return new BitmessageContext(this); - } - - private void nonNull(String name, Object o) { - if (o == null) throw new IllegalStateException(name + " must not be null"); - } - } - -} diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt new file mode 100644 index 0000000..ef4b1d6 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt @@ -0,0 +1,451 @@ +/* + * 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.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.MessagePayload +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.DRAFT +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.Broadcast +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.* +import ch.dissem.bitmessage.utils.Property +import ch.dissem.bitmessage.utils.UnixTime.HOUR +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import org.slf4j.LoggerFactory +import java.net.InetAddress +import java.util.concurrent.CancellationException +import java.util.concurrent.ExecutionException +import kotlin.properties.Delegates + +/** + * + * Use this class if you want to create a Bitmessage client. + * You'll need the Builder to create a BitmessageContext, and set the following properties: + * + * * addressRepo + * * inventory + * * nodeRegistry + * * networkHandler + * * messageRepo + * * streams + * + * + * The default implementations in the different module builds can be used. + * + * The port defaults to 8444 (the default Bitmessage port) + */ +class BitmessageContext private constructor(builder: BitmessageContext.Builder) { + + private val sendPubkeyOnIdentityCreation: Boolean + + /** + * The [InternalContext] - normally you wouldn't need it, + * unless you are doing something crazy with the protocol. + */ + val internals: InternalContext + @JvmName("internals") get + + val labeler: Labeler + @JvmName("labeler") get + + init { + labeler = builder.labeler ?: DefaultLabeler() + internals = InternalContext( + builder.cryptography, + builder.inventory, + builder.nodeRegistry, + builder.networkHandler, + builder.addressRepo, + builder.messageRepo, + builder.proofOfWorkRepository, + builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(), + builder.customCommandHandler ?: object : CustomCommandHandler { + override fun handle(request: CustomMessage): MessagePayload? { + LOG.debug("Received custom request, but no custom command handler configured.") + return null + } + }, + builder.listener, + labeler, + builder.port, + builder.connectionTTL, + builder.connectionLimit + ) + internals.proofOfWorkService.doMissingProofOfWork(30000) // TODO: this should be configurable + sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation + (builder.listener as? Listener.WithContext)?.setContext(this) + } + + val addresses: AddressRepository = internals.addressRepository + @JvmName("addresses") get + + val messages: MessageRepository = internals.messageRepository + @JvmName("messages") get + + fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress { + val identity = BitmessageAddress(PrivateKey( + shorter, + internals.streams[0], + NETWORK_NONCE_TRIALS_PER_BYTE, + NETWORK_EXTRA_BYTES, + *features + )) + internals.addressRepository.save(identity) + if (sendPubkeyOnIdentityCreation) { + internals.sendPubkey(identity, identity.stream) + } + return identity + } + + fun joinChan(passphrase: String, address: String): BitmessageAddress { + val chan = BitmessageAddress.chan(address, passphrase) + chan.alias = passphrase + internals.addressRepository.save(chan) + return chan + } + + fun createChan(passphrase: String): BitmessageAddress { + // FIXME: hardcoded stream number + val chan = BitmessageAddress.chan(1, passphrase) + internals.addressRepository.save(chan) + return chan + } + + fun createDeterministicAddresses( + passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List { + val result = BitmessageAddress.deterministic( + passphrase, numberOfAddresses, version, stream, shorter) + for (i in result.indices) { + val address = result[i] + address.alias = "deterministic (" + (i + 1) + ")" + internals.addressRepository.save(address) + } + return result + } + + fun broadcast(from: BitmessageAddress, subject: String, message: String) { + send(Plaintext( + type = BROADCAST, + from = from, + subject = subject, + body = message, + status = DRAFT + )) + } + + fun send(from: BitmessageAddress, to: BitmessageAddress, subject: String, message: String) { + if (from.privateKey == null) { + throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.") + } + send(Plaintext( + type = MSG, + from = from, + to = to, + subject = subject, + body = message + )) + } + + fun send(msg: Plaintext) { + if (msg.from.privateKey == null) { + throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.") + } + labeler.markAsSending(msg) + val to = msg.to + if (to != null) { + if (to.pubkey == null) { + LOG.info("Public key is missing from recipient. Requesting.") + internals.requestPubkey(to) + } + if (to.pubkey == null) { + internals.messageRepository.save(msg) + } + } + if (to == null || to.pubkey != null) { + LOG.info("Sending message.") + internals.messageRepository.save(msg) + if (msg.type == MSG) { + internals.send(msg) + } else { + internals.send( + msg.from, + to, + Factory.getBroadcast(msg), + msg.ttl + ) + } + } + } + + fun startup() { + internals.networkHandler.start() + } + + fun shutdown() { + internals.networkHandler.stop() + } + + /** + * @param host a trusted node that must be reliable (it's used for every synchronization) + * * + * @param port of the trusted host, default is 8444 + * * + * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even + * * if not all objects were fetched + * * + * @param wait waits for the synchronization thread to finish + */ + fun synchronize(host: InetAddress, port: Int, timeoutInSeconds: Long, wait: Boolean) { + val future = internals.networkHandler.synchronize(host, port, timeoutInSeconds) + if (wait) { + try { + future.get() + } catch (e: InterruptedException) { + LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.") + future.cancel(true) + } catch (e: CancellationException) { + LOG.debug(e.message, e) + } catch (e: ExecutionException) { + LOG.debug(e.message, e) + } + + } + } + + /** + * 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 [CustomMessage]. + + * @param server the node's address + * * + * @param port the node's port + * * + * @param request the request + * * + * @return the response + */ + fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage { + return internals.networkHandler.send(server, port, request) + } + + /** + * Removes expired objects from the inventory. You should call this method regularly, + * e.g. daily and on each shutdown. + */ + fun cleanup() { + internals.inventory.cleanup() + } + + /** + * Sends messages again whose time to live expired without being acknowledged. (And whose + * recipient is expected to send acknowledgements. + * + * + * You should call this method regularly, but be aware of the following: + * + * * As messages might be sent, POW will be done. It is therefore not advised to + * call it on shutdown. + * * It shouldn't be called right after startup, as it's possible the missing + * acknowledgement was sent while the client was offline. + * * Other than that, the call isn't expensive as long as there is no message + * to send, so it might be a good idea to just call it every few minutes. + * + */ + fun resendUnacknowledgedMessages() { + internals.resendUnacknowledged() + } + + val isRunning: Boolean + get() = internals.networkHandler.isRunning + + fun addContact(contact: BitmessageAddress) { + internals.addressRepository.save(contact) + if (contact.pubkey == null) { + internals.addressRepository.getAddress(contact.address)?.let { + if (it.pubkey == null) { + internals.requestPubkey(contact) + } + } + } + } + + fun addSubscribtion(address: BitmessageAddress) { + address.isSubscribed = true + internals.addressRepository.save(address) + tryToFindBroadcastsForAddress(address) + } + + private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) { + for (`object` in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { + try { + val broadcast = `object`.payload as Broadcast + broadcast.decrypt(address) + // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with + // other subscriptions and the interface stays as simple as possible. + internals.networkListener.receive(`object`) + } catch (ignore: DecryptionFailedException) { + } catch (e: Exception) { + LOG.debug(e.message, e) + } + + } + } + + fun status(): Property { + return Property("status", + internals.networkHandler.networkStatus, + Property("unacknowledged", internals.messageRepository.findMessagesToResend().size) + ) + } + + interface Listener { + fun receive(plaintext: Plaintext) + + /** + * A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot. + */ + interface WithContext : Listener { + fun setContext(ctx: BitmessageContext) + } + } + + class Builder { + internal var port = 8444 + internal var inventory by Delegates.notNull() + internal var nodeRegistry by Delegates.notNull() + internal var networkHandler by Delegates.notNull() + internal var addressRepo by Delegates.notNull() + internal var messageRepo by Delegates.notNull() + internal var proofOfWorkRepository by Delegates.notNull() + internal var proofOfWorkEngine: ProofOfWorkEngine? = null + internal var cryptography by Delegates.notNull() + internal var customCommandHandler: CustomCommandHandler? = null + internal var labeler: Labeler? = null + internal var listener by Delegates.notNull() + internal var connectionLimit = 150 + internal var connectionTTL = 30 * MINUTE + internal var sendPubkeyOnIdentityCreation = true + + fun port(port: Int): Builder { + this.port = port + return this + } + + fun inventory(inventory: Inventory): Builder { + this.inventory = inventory + return this + } + + fun nodeRegistry(nodeRegistry: NodeRegistry): Builder { + this.nodeRegistry = nodeRegistry + return this + } + + fun networkHandler(networkHandler: NetworkHandler): Builder { + this.networkHandler = networkHandler + return this + } + + fun addressRepo(addressRepo: AddressRepository): Builder { + this.addressRepo = addressRepo + return this + } + + fun messageRepo(messageRepo: MessageRepository): Builder { + this.messageRepo = messageRepo + return this + } + + fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder { + this.proofOfWorkRepository = proofOfWorkRepository + return this + } + + fun cryptography(cryptography: Cryptography): Builder { + this.cryptography = cryptography + return this + } + + fun customCommandHandler(handler: CustomCommandHandler): Builder { + this.customCommandHandler = handler + return this + } + + fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder { + this.proofOfWorkEngine = proofOfWorkEngine + return this + } + + fun labeler(labeler: Labeler): Builder { + this.labeler = labeler + return this + } + + fun listener(listener: Listener): Builder { + this.listener = listener + return this + } + + fun connectionLimit(connectionLimit: Int): Builder { + this.connectionLimit = connectionLimit + return this + } + + fun connectionTTL(hours: Int): Builder { + this.connectionTTL = hours * HOUR + return this + } + + /** + * By default a client will send the public key when an identity is being created. On weaker devices + * this behaviour might not be desirable. + */ + fun doNotSendPubkeyOnIdentityCreation(): Builder { + this.sendPubkeyOnIdentityCreation = false + return this + } + + fun build(): BitmessageContext { + nonNull("inventory", inventory) + nonNull("nodeRegistry", nodeRegistry) + nonNull("networkHandler", networkHandler) + nonNull("addressRepo", addressRepo) + nonNull("messageRepo", messageRepo) + nonNull("proofOfWorkRepo", proofOfWorkRepository) + return BitmessageContext(this) + } + + private fun nonNull(name: String, o: Any?) { + if (o == null) throw IllegalStateException(name + " must not be null") + } + } + + companion object { + @JvmField val CURRENT_VERSION = 3 + private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt index 8946d73..fc4ee0a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -16,8 +16,7 @@ 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.Plaintext.Encoding.* import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.entity.payload.Pubkey.Feature import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding @@ -36,6 +35,13 @@ import java.util.* import java.util.Collections import kotlin.collections.HashSet +fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { + SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() + EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() + TRIVIAL -> (subject+body).toByteArray() + IGNORE -> ByteArray(0) +} + /** * The unencrypted message to be sent by 'msg' or 'broadcast'. */ @@ -182,9 +188,39 @@ class Plaintext private constructor( status ) + constructor( + type: Type, + from: BitmessageAddress, + to: BitmessageAddress? = null, + encoding: Encoding = SIMPLE, + subject: String, + body: String, + ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), + conversationId: UUID = UUID.randomUUID(), + ttl: Long = TTL.msg, + labels: MutableSet