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:
- *
- *
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)
- */
-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