Migrated core and extension modules to Kotlin
(Except BitmessageContext and Bytes)
This commit is contained in:
parent
811625c051
commit
fa0e53289c
13
build.gradle
13
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 {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.14.0'
|
id 'com.github.ben-manes.versions' version '0.14.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
@ -17,6 +27,9 @@ subprojects {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
}
|
}
|
||||||
|
dependencies {
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
testLogging {
|
testLogging {
|
||||||
|
@ -28,6 +28,6 @@ dependencies {
|
|||||||
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
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')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,22 @@ public class BitmessageContext {
|
|||||||
private final boolean sendPubkeyOnIdentityCreation;
|
private final boolean sendPubkeyOnIdentityCreation;
|
||||||
|
|
||||||
private BitmessageContext(Builder builder) {
|
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;
|
labeler = builder.labeler;
|
||||||
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
|
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
|
||||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
||||||
@ -103,7 +118,7 @@ public class BitmessageContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BitmessageAddress joinChan(String passphrase, String address) {
|
public BitmessageAddress joinChan(String passphrase, String address) {
|
||||||
BitmessageAddress chan = BitmessageAddress.chan(address, passphrase);
|
BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase);
|
||||||
chan.setAlias(passphrase);
|
chan.setAlias(passphrase);
|
||||||
ctx.getAddressRepository().save(chan);
|
ctx.getAddressRepository().save(chan);
|
||||||
return chan;
|
return chan;
|
||||||
@ -111,14 +126,14 @@ public class BitmessageContext {
|
|||||||
|
|
||||||
public BitmessageAddress createChan(String passphrase) {
|
public BitmessageAddress createChan(String passphrase) {
|
||||||
// FIXME: hardcoded stream number
|
// FIXME: hardcoded stream number
|
||||||
BitmessageAddress chan = BitmessageAddress.chan(1, passphrase);
|
BitmessageAddress chan = BitmessageAddress.Companion.chan(1, passphrase);
|
||||||
ctx.getAddressRepository().save(chan);
|
ctx.getAddressRepository().save(chan);
|
||||||
return chan;
|
return chan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BitmessageAddress> createDeterministicAddresses(
|
public List<BitmessageAddress> createDeterministicAddresses(
|
||||||
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
|
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
|
||||||
List<BitmessageAddress> result = BitmessageAddress.deterministic(
|
List<BitmessageAddress> result = BitmessageAddress.Companion.deterministic(
|
||||||
passphrase, numberOfAddresses, version, stream, shorter);
|
passphrase, numberOfAddresses, version, stream, shorter);
|
||||||
for (int i = 0; i < result.size(); i++) {
|
for (int i = 0; i < result.size(); i++) {
|
||||||
BitmessageAddress address = result.get(i);
|
BitmessageAddress address = result.get(i);
|
||||||
@ -149,7 +164,7 @@ public class BitmessageContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void send(final Plaintext msg) {
|
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.");
|
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
||||||
}
|
}
|
||||||
labeler().markAsSending(msg);
|
labeler().markAsSending(msg);
|
||||||
@ -268,7 +283,7 @@ public class BitmessageContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
|
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 {
|
try {
|
||||||
Broadcast broadcast = (Broadcast) object.getPayload();
|
Broadcast broadcast = (Broadcast) object.getPayload();
|
||||||
broadcast.decrypt(address);
|
broadcast.decrypt(address);
|
||||||
|
@ -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<Plaintext> 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
235
core/src/main/java/ch/dissem/bitmessage/InternalContext.kt
Normal file
235
core/src/main/java/ch/dissem/bitmessage/InternalContext.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
124
core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt
Normal file
124
core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
43
core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt
Normal file
43
core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,19 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for objects that have encrypted content
|
* Used for objects that have encrypted content
|
||||||
*/
|
*/
|
||||||
public interface Encrypted {
|
interface Encrypted {
|
||||||
void encrypt(byte[] publicKey) throws IOException;
|
fun encrypt(publicKey: ByteArray)
|
||||||
|
|
||||||
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
|
@Throws(DecryptionFailedException::class)
|
||||||
|
fun decrypt(privateKey: ByteArray)
|
||||||
|
|
||||||
boolean isDecrypted();
|
val isDecrypted: Boolean
|
||||||
}
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
48
core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt
Normal file
48
core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
44
core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt
Normal file
44
core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,15 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command can hold a network message payload
|
* A command can hold a network message payload
|
||||||
*/
|
*/
|
||||||
public interface MessagePayload extends Streamable {
|
interface MessagePayload : Streamable {
|
||||||
Command getCommand();
|
val command: Command
|
||||||
|
|
||||||
enum Command {
|
enum class Command {
|
||||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
126
core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt
Normal file
126
core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
228
core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt
Normal file
228
core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
706
core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt
Normal file
706
core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,8 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
public interface PlaintextHolder {
|
interface PlaintextHolder {
|
||||||
Plaintext getPlaintext();
|
val plaintext: Plaintext?
|
||||||
}
|
}
|
@ -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);
|
|
||||||
}
|
|
30
core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt
Normal file
30
core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt
Normal file
@ -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)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,30 +14,27 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.OutputStream
|
||||||
import java.io.OutputStream;
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
||||||
*/
|
*/
|
||||||
public class VerAck implements MessagePayload {
|
class VerAck : MessagePayload {
|
||||||
private static final long serialVersionUID = -4302074845199181687L;
|
|
||||||
|
|
||||||
@Override
|
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
||||||
public Command getCommand() {
|
|
||||||
return Command.VERACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
override fun write(out: OutputStream) {
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
// 'verack' doesn't have any payload, so there is nothing to write
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun write(buffer: ByteBuffer) {
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
// 'verack' doesn't have any payload, so there is nothing to write
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val serialVersionUID = -4302074845199181687L
|
||||||
|
}
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
204
core/src/main/java/ch/dissem/bitmessage/entity/Version.kt
Normal file
204
core/src/main/java/ch/dissem/bitmessage/entity/Version.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
103
core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt
Normal file
103
core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,31 +14,20 @@
|
|||||||
* limitations under the License.
|
* 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.
|
* 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),
|
GET_PUBKEY(0),
|
||||||
PUBKEY(1),
|
PUBKEY(1),
|
||||||
MSG(2),
|
MSG(2),
|
||||||
BROADCAST(3);
|
BROADCAST(3);
|
||||||
|
|
||||||
int number;
|
companion object {
|
||||||
|
fun fromNumber(number: Long): ObjectType? {
|
||||||
ObjectType(int number) {
|
return values().firstOrNull { it.number == number }
|
||||||
this.number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectType fromNumber(long number) {
|
|
||||||
for (ObjectType type : values()) {
|
|
||||||
if (type.number == number) return type;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNumber() {
|
|
||||||
return number;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
112
core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt
Normal file
112
core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<*>>
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,15 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
package ch.dissem.bitmessage.exception
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates an illegal Bitmessage address
|
* Indicates an illegal Bitmessage address
|
||||||
*/
|
*/
|
||||||
public class AddressFormatException extends RuntimeException {
|
class AddressFormatException(message: String) : RuntimeException(message)
|
||||||
private static final long serialVersionUID = 6943764578672021573L;
|
|
||||||
|
|
||||||
public AddressFormatException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,14 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
package ch.dissem.bitmessage.exception
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
public interface CustomCommandHandler {
|
class ApplicationException : RuntimeException {
|
||||||
MessagePayload handle(CustomMessage request);
|
|
||||||
|
constructor(cause: Throwable) : super(cause)
|
||||||
|
|
||||||
|
constructor(message: String) : super(message)
|
||||||
|
|
||||||
}
|
}
|
@ -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()
|
@ -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.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.")
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,21 +14,13 @@
|
|||||||
* limitations under the License.
|
* 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.
|
* An exception on the node that's severe enough to cause the client to disconnect this node.
|
||||||
*
|
|
||||||
* @author Ch. Basler
|
* @author Ch. Basler
|
||||||
*/
|
*/
|
||||||
public class NodeException extends RuntimeException {
|
class NodeException(message: String?, cause: Throwable? = null) : RuntimeException(message ?: cause?.message, cause) {
|
||||||
private static final long serialVersionUID = 2965325796118227802L;
|
constructor(message: String) : this(message, null)
|
||||||
|
|
||||||
public NodeException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
206
core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt
Normal file
206
core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
@ -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}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
@ -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>
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,52 +14,51 @@
|
|||||||
* limitations under the License.
|
* 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;
|
interface AddressRepository {
|
||||||
|
|
||||||
public interface AddressRepository {
|
|
||||||
/**
|
/**
|
||||||
* Returns a matching BitmessageAddress if there is one with the given ripe or tag, that
|
* 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.
|
* 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
|
* @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.
|
* @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.
|
* @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.
|
* @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.
|
* @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
|
* @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?
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,209 +14,250 @@
|
|||||||
* 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.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
import java.io.IOException;
|
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom;
|
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.
|
* 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
|
* 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.
|
* success on the same thread.
|
||||||
*
|
|
||||||
* @param data to get hashed
|
* @param data to get hashed
|
||||||
|
* *
|
||||||
* @param offset of the data to be hashed
|
* @param offset of the data to be hashed
|
||||||
|
* *
|
||||||
* @param length of the data to be hashed
|
* @param length of the data to be hashed
|
||||||
|
* *
|
||||||
* @return SHA-512 hash of data within the given range
|
* @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
|
* 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.
|
* success on the same thread.
|
||||||
*
|
|
||||||
* @param data to get hashed
|
* @param data to get hashed
|
||||||
|
* *
|
||||||
* @return SHA-512 hash of data
|
* @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
|
* 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.
|
* success on the same thread.
|
||||||
*
|
|
||||||
* @param data to get hashed
|
* @param data to get hashed
|
||||||
|
* *
|
||||||
* @return SHA-512 hash of data
|
* @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
|
* 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.
|
* 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 data to get hashed
|
||||||
|
* *
|
||||||
* @param length number of bytes to be taken into account
|
* @param length number of bytes to be taken into account
|
||||||
|
* *
|
||||||
* @return SHA-512 hash of data
|
* @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
|
* 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.
|
* 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
|
* 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.
|
* order on the same thread.
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
|
|
||||||
* @param data to get hashed
|
* @param data to get hashed
|
||||||
|
* *
|
||||||
* @return RIPEMD-160 hash of data
|
* @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
|
* 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.
|
* 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
|
* 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.
|
* order on the same thread.
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
|
|
||||||
* @param data to get hashed
|
* @param data to get hashed
|
||||||
|
* *
|
||||||
* @param length number of bytes to be taken into account
|
* @param length number of bytes to be taken into account
|
||||||
|
* *
|
||||||
* @return SHA-256 hash of data
|
* @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
|
* 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.
|
* 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
|
* 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.
|
* order on the same thread.
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
|
|
||||||
* @param data to get hashed
|
* @param data to get hashed
|
||||||
|
* *
|
||||||
* @return SHA hash of data
|
* @return SHA hash of data
|
||||||
*/
|
*/
|
||||||
byte[] sha1(byte[]... data);
|
fun sha1(vararg data: ByteArray): ByteArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param length number of bytes to return
|
* @param length number of bytes to return
|
||||||
|
* *
|
||||||
* @return an array of the given size containing random bytes
|
* @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
|
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
||||||
* live.
|
* live.
|
||||||
*
|
|
||||||
* @param object to do the proof of work for
|
* @param object to do the proof of work for
|
||||||
|
* *
|
||||||
* @param nonceTrialsPerByte difficulty
|
* @param nonceTrialsPerByte difficulty
|
||||||
|
* *
|
||||||
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
|
* @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
|
* @param callback to handle nonce once it's calculated
|
||||||
*/
|
*/
|
||||||
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long,
|
||||||
long extraBytes, ProofOfWorkEngine.Callback callback);
|
extraBytes: Long, callback: ProofOfWorkEngine.Callback)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param object to be checked
|
* @param object to be checked
|
||||||
|
* *
|
||||||
* @param nonceTrialsPerByte difficulty
|
* @param nonceTrialsPerByte difficulty
|
||||||
|
* *
|
||||||
* @param extraBytes bytes to add to the object size
|
* @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)
|
* @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(InsufficientProofOfWorkException::class)
|
||||||
throws IOException;
|
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)
|
* Calculates the MAC for a message (data)
|
||||||
*
|
|
||||||
* @param key_m the symmetric key used
|
* @param key_m the symmetric key used
|
||||||
|
* *
|
||||||
* @param data the message data to calculate the MAC for
|
* @param data the message data to calculate the MAC for
|
||||||
|
* *
|
||||||
* @return the MAC
|
* @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 encrypt if true, encrypts data, otherwise tries to decrypt it.
|
||||||
|
* *
|
||||||
* @param data
|
* @param data
|
||||||
|
* *
|
||||||
* @param key_e
|
* @param key_e
|
||||||
|
* *
|
||||||
* @return
|
* @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.
|
* Create a new public key fom given private keys.
|
||||||
*
|
|
||||||
* @param version of the public key / address
|
* @param version of the public key / address
|
||||||
|
* *
|
||||||
* @param stream of the address
|
* @param stream of the address
|
||||||
|
* *
|
||||||
* @param privateSigningKey private key used for signing
|
* @param privateSigningKey private key used for signing
|
||||||
|
* *
|
||||||
* @param privateEncryptionKey private key used for encryption
|
* @param privateEncryptionKey private key used for encryption
|
||||||
|
* *
|
||||||
* @param nonceTrialsPerByte proof of work difficulty
|
* @param nonceTrialsPerByte proof of work difficulty
|
||||||
|
* *
|
||||||
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
||||||
|
* *
|
||||||
* @param features of the address
|
* @param features of the address
|
||||||
|
* *
|
||||||
* @return a public key object
|
* @return a public key object
|
||||||
*/
|
*/
|
||||||
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
|
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param privateKey private key as byte array
|
* @param privateKey private key as byte array
|
||||||
|
* *
|
||||||
* @return a public key corresponding to the given private key
|
* @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
|
* @param privateKey private key as byte array
|
||||||
|
* *
|
||||||
* @return a big integer representation (unsigned) of the given bytes
|
* @return a big integer representation (unsigned) of the given bytes
|
||||||
*/
|
*/
|
||||||
BigInteger keyToBigInt(byte[] privateKey);
|
fun keyToBigInt(privateKey: ByteArray): BigInteger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data to check
|
* @param data to check
|
||||||
|
* *
|
||||||
* @param signature the signature of the message
|
* @param signature the signature of the message
|
||||||
|
* *
|
||||||
* @param pubkey the sender's public key
|
* @param pubkey the sender's public key
|
||||||
|
* *
|
||||||
* @return true if the signature is valid, false otherwise
|
* @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.
|
* Calculate the signature of data, using the given private key.
|
||||||
*
|
|
||||||
* @param data to be signed
|
* @param data to be signed
|
||||||
|
* *
|
||||||
* @param privateKey to be used for signing
|
* @param privateKey to be used for signing
|
||||||
|
* *
|
||||||
* @return the signature
|
* @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
|
* @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
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,17 +14,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
package ch.dissem.bitmessage.ports
|
||||||
|
|
||||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
|
import ch.dissem.bitmessage.entity.CustomMessage
|
||||||
import org.junit.BeforeClass;
|
import ch.dissem.bitmessage.entity.MessagePayload
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
public class TestBase {
|
interface CustomCommandHandler {
|
||||||
@BeforeClass
|
fun handle(request: CustomMessage): MessagePayload?
|
||||||
public static void setUpClass() {
|
|
||||||
Singleton.initialize(new BouncyCryptography());
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user