Merge branch 'feature/kotlin' into develop
This commit is contained in:
commit
0478431c9c
55
build.gradle
55
build.gradle
@ -1,22 +1,38 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.3'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.14.0'
|
||||
id 'com.github.ben-manes.versions' version '0.15.0'
|
||||
id "io.spring.dependency-management" version "1.0.3.RELEASE"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'signing'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'gitflow-version'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
targetCompatibility = 1.7
|
||||
group = 'ch.dissem.jabit'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
}
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect"
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
@ -38,6 +54,14 @@ subprojects {
|
||||
archives javadocJar, sourcesJar
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Implementation-Title': "Jabit ${project.name.capitalize()}",
|
||||
'Implementation-Version': version
|
||||
}
|
||||
baseName "jabit-${project.name}"
|
||||
}
|
||||
|
||||
signing {
|
||||
required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 }
|
||||
sign configurations.archives
|
||||
@ -93,4 +117,31 @@ subprojects {
|
||||
}
|
||||
|
||||
check.dependsOn jacocoTestReport
|
||||
|
||||
dependencyManagement {
|
||||
dependencies {
|
||||
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
|
||||
entry 'kotlin-stdlib-jre7'
|
||||
entry 'kotlin-reflect'
|
||||
}
|
||||
dependencySet(group: 'org.slf4j', version: '1.7.25') {
|
||||
entry 'slf4j-api'
|
||||
entry 'slf4j-simple'
|
||||
}
|
||||
|
||||
dependency 'ch.dissem.msgpack:msgpack:1.0.0'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||
dependency 'com.madgag.spongycastle:prov:1.56.0.0'
|
||||
dependency 'org.apache.commons:commons-lang3:3.6'
|
||||
dependency 'org.flywaydb:flyway-core:4.2.0'
|
||||
|
||||
dependency 'args4j:args4j:2.33'
|
||||
dependency 'org.ini4j:ini4j:0.5.4'
|
||||
dependency 'com.h2database:h2:1.4.196'
|
||||
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'org.hamcrest:hamcrest-library:1.3'
|
||||
dependency 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,28 @@ artifacts {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.25'
|
||||
compile 'org.slf4j:slf4j-api'
|
||||
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
testCompile 'org.mockito:mockito-core:2.7.21'
|
||||
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
||||
|
||||
def generatedResources = "${project.buildDir}/generated-resources/main"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
output.dir(generatedResources, builtBy: 'generateVersionInfo')
|
||||
}
|
||||
}
|
||||
task('generateVersionInfo') {
|
||||
doLast {
|
||||
def dir = new File(generatedResources)
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs()
|
||||
}
|
||||
def file = new File(generatedResources, "version")
|
||||
file.write(project.version.toString())
|
||||
}
|
||||
}
|
||||
|
@ -1,437 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage;
|
||||
|
||||
import ch.dissem.bitmessage.entity.*;
|
||||
import ch.dissem.bitmessage.entity.payload.Broadcast;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.utils.Property;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
||||
|
||||
/**
|
||||
* <p>Use this class if you want to create a Bitmessage client.</p>
|
||||
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
||||
* <ul>
|
||||
* <li>addressRepo</li>
|
||||
* <li>inventory</li>
|
||||
* <li>nodeRegistry</li>
|
||||
* <li>networkHandler</li>
|
||||
* <li>messageRepo</li>
|
||||
* <li>streams</li>
|
||||
* </ul>
|
||||
* <p>The default implementations in the different module builds can be used.</p>
|
||||
* <p>The port defaults to 8444 (the default Bitmessage port)</p>
|
||||
*/
|
||||
public class BitmessageContext {
|
||||
public static final int CURRENT_VERSION = 3;
|
||||
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
|
||||
|
||||
private final InternalContext ctx;
|
||||
|
||||
private final Labeler labeler;
|
||||
|
||||
private final boolean sendPubkeyOnIdentityCreation;
|
||||
|
||||
private BitmessageContext(Builder builder) {
|
||||
ctx = new InternalContext(builder);
|
||||
labeler = builder.labeler;
|
||||
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
|
||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
||||
if (builder.listener instanceof Listener.WithContext) {
|
||||
((Listener.WithContext) builder.listener).setContext(this);
|
||||
}
|
||||
}
|
||||
|
||||
public AddressRepository addresses() {
|
||||
return ctx.getAddressRepository();
|
||||
}
|
||||
|
||||
public MessageRepository messages() {
|
||||
return ctx.getMessageRepository();
|
||||
}
|
||||
|
||||
public Labeler labeler() {
|
||||
return labeler;
|
||||
}
|
||||
|
||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
||||
shorter,
|
||||
ctx.getStreams()[0],
|
||||
NETWORK_NONCE_TRIALS_PER_BYTE,
|
||||
NETWORK_EXTRA_BYTES,
|
||||
features
|
||||
));
|
||||
ctx.getAddressRepository().save(identity);
|
||||
if (sendPubkeyOnIdentityCreation) {
|
||||
ctx.sendPubkey(identity, identity.getStream());
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public BitmessageAddress joinChan(String passphrase, String address) {
|
||||
BitmessageAddress chan = BitmessageAddress.chan(address, passphrase);
|
||||
chan.setAlias(passphrase);
|
||||
ctx.getAddressRepository().save(chan);
|
||||
return chan;
|
||||
}
|
||||
|
||||
public BitmessageAddress createChan(String passphrase) {
|
||||
// FIXME: hardcoded stream number
|
||||
BitmessageAddress chan = BitmessageAddress.chan(1, passphrase);
|
||||
ctx.getAddressRepository().save(chan);
|
||||
return chan;
|
||||
}
|
||||
|
||||
public List<BitmessageAddress> createDeterministicAddresses(
|
||||
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
|
||||
List<BitmessageAddress> result = BitmessageAddress.deterministic(
|
||||
passphrase, numberOfAddresses, version, stream, shorter);
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
BitmessageAddress address = result.get(i);
|
||||
address.setAlias("deterministic (" + (i + 1) + ")");
|
||||
ctx.getAddressRepository().save(address);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
|
||||
Plaintext msg = new Plaintext.Builder(BROADCAST)
|
||||
.from(from)
|
||||
.message(subject, message)
|
||||
.build();
|
||||
send(msg);
|
||||
}
|
||||
|
||||
public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
|
||||
if (from.getPrivateKey() == null) {
|
||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
||||
}
|
||||
Plaintext msg = new Plaintext.Builder(MSG)
|
||||
.from(from)
|
||||
.to(to)
|
||||
.message(subject, message)
|
||||
.build();
|
||||
send(msg);
|
||||
}
|
||||
|
||||
public void send(final Plaintext msg) {
|
||||
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
|
||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
||||
}
|
||||
labeler().markAsSending(msg);
|
||||
BitmessageAddress to = msg.getTo();
|
||||
if (to != null) {
|
||||
if (to.getPubkey() == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.");
|
||||
ctx.requestPubkey(to);
|
||||
}
|
||||
if (to.getPubkey() == null) {
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
}
|
||||
if (to == null || to.getPubkey() != null) {
|
||||
LOG.info("Sending message.");
|
||||
ctx.getMessageRepository().save(msg);
|
||||
if (msg.getType() == MSG) {
|
||||
ctx.send(msg);
|
||||
} else {
|
||||
ctx.send(
|
||||
msg.getFrom(),
|
||||
to,
|
||||
Factory.getBroadcast(msg),
|
||||
msg.getTTL()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
ctx.getNetworkHandler().start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
ctx.getNetworkHandler().stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
||||
* @param port of the trusted host, default is 8444
|
||||
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
|
||||
* if not all objects were fetched
|
||||
* @param wait waits for the synchronization thread to finish
|
||||
*/
|
||||
public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) {
|
||||
Future<?> future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds);
|
||||
if (wait) {
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.");
|
||||
future.cancel(true);
|
||||
} catch (CancellationException | ExecutionException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
||||
* the response, which in turn is expected to be a {@link CustomMessage}.
|
||||
*
|
||||
* @param server the node's address
|
||||
* @param port the node's port
|
||||
* @param request the request
|
||||
* @return the response
|
||||
*/
|
||||
public CustomMessage send(InetAddress server, int port, CustomMessage request) {
|
||||
return ctx.getNetworkHandler().send(server, port, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired objects from the inventory. You should call this method regularly,
|
||||
* e.g. daily and on each shutdown.
|
||||
*/
|
||||
public void cleanup() {
|
||||
ctx.getInventory().cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
||||
* recipient is expected to send acknowledgements.
|
||||
* <p>
|
||||
* You should call this method regularly, but be aware of the following:
|
||||
* <ul>
|
||||
* <li>As messages might be sent, POW will be done. It is therefore not advised to
|
||||
* call it on shutdown.</li>
|
||||
* <li>It shouldn't be called right after startup, as it's possible the missing
|
||||
* acknowledgement was sent while the client was offline.</li>
|
||||
* <li>Other than that, the call isn't expensive as long as there is no message
|
||||
* to send, so it might be a good idea to just call it every few minutes.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void resendUnacknowledgedMessages() {
|
||||
ctx.resendUnacknowledged();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return ctx.getNetworkHandler().isRunning();
|
||||
}
|
||||
|
||||
public void addContact(BitmessageAddress contact) {
|
||||
ctx.getAddressRepository().save(contact);
|
||||
if (contact.getPubkey() == null) {
|
||||
BitmessageAddress stored = ctx.getAddressRepository().getAddress(contact.getAddress());
|
||||
if (stored.getPubkey() == null) {
|
||||
ctx.requestPubkey(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addSubscribtion(BitmessageAddress address) {
|
||||
address.setSubscribed(true);
|
||||
ctx.getAddressRepository().save(address);
|
||||
tryToFindBroadcastsForAddress(address);
|
||||
}
|
||||
|
||||
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
|
||||
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
||||
try {
|
||||
Broadcast broadcast = (Broadcast) object.getPayload();
|
||||
broadcast.decrypt(address);
|
||||
// This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
|
||||
// other subscriptions and the interface stays as simple as possible.
|
||||
ctx.getNetworkListener().receive(object);
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
} catch (Exception e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Property status() {
|
||||
return new Property("status", null,
|
||||
ctx.getNetworkHandler().getNetworkStatus(),
|
||||
new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link InternalContext} - normally you wouldn't need it,
|
||||
* unless you are doing something crazy with the protocol.
|
||||
*/
|
||||
public InternalContext internals() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void receive(Plaintext plaintext);
|
||||
|
||||
/**
|
||||
* A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot.
|
||||
*/
|
||||
interface WithContext extends Listener {
|
||||
void setContext(BitmessageContext ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
int port = 8444;
|
||||
Inventory inventory;
|
||||
NodeRegistry nodeRegistry;
|
||||
NetworkHandler networkHandler;
|
||||
AddressRepository addressRepo;
|
||||
MessageRepository messageRepo;
|
||||
ProofOfWorkRepository proofOfWorkRepository;
|
||||
ProofOfWorkEngine proofOfWorkEngine;
|
||||
Cryptography cryptography;
|
||||
CustomCommandHandler customCommandHandler;
|
||||
Labeler labeler;
|
||||
Listener listener;
|
||||
int connectionLimit = 150;
|
||||
long connectionTTL = 30 * MINUTE;
|
||||
boolean sendPubkeyOnIdentityCreation = true;
|
||||
|
||||
public Builder port(int port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder inventory(Inventory inventory) {
|
||||
this.inventory = inventory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nodeRegistry(NodeRegistry nodeRegistry) {
|
||||
this.nodeRegistry = nodeRegistry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder networkHandler(NetworkHandler networkHandler) {
|
||||
this.networkHandler = networkHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addressRepo(AddressRepository addressRepo) {
|
||||
this.addressRepo = addressRepo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder messageRepo(MessageRepository messageRepo) {
|
||||
this.messageRepo = messageRepo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
|
||||
this.proofOfWorkRepository = proofOfWorkRepository;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cryptography(Cryptography cryptography) {
|
||||
this.cryptography = cryptography;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder customCommandHandler(CustomCommandHandler handler) {
|
||||
this.customCommandHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder labeler(Labeler labeler) {
|
||||
this.labeler = labeler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder listener(Listener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder connectionLimit(int connectionLimit) {
|
||||
this.connectionLimit = connectionLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder connectionTTL(int hours) {
|
||||
this.connectionTTL = hours * HOUR;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||
* this behaviour might not be desirable.
|
||||
*/
|
||||
public Builder doNotSendPubkeyOnIdentityCreation() {
|
||||
this.sendPubkeyOnIdentityCreation = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BitmessageContext build() {
|
||||
nonNull("inventory", inventory);
|
||||
nonNull("nodeRegistry", nodeRegistry);
|
||||
nonNull("networkHandler", networkHandler);
|
||||
nonNull("addressRepo", addressRepo);
|
||||
nonNull("messageRepo", messageRepo);
|
||||
nonNull("proofOfWorkRepo", proofOfWorkRepository);
|
||||
if (proofOfWorkEngine == null) {
|
||||
proofOfWorkEngine = new MultiThreadedPOWEngine();
|
||||
}
|
||||
if (labeler == null) {
|
||||
labeler = new DefaultLabeler();
|
||||
}
|
||||
if (customCommandHandler == null) {
|
||||
customCommandHandler = new CustomCommandHandler() {
|
||||
@Override
|
||||
public MessagePayload handle(CustomMessage request) {
|
||||
LOG.debug("Received custom request, but no custom command handler configured.");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return new BitmessageContext(this);
|
||||
}
|
||||
|
||||
private void nonNull(String name, Object o) {
|
||||
if (o == null) throw new IllegalStateException(name + " must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Status;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface MessageRepository {
|
||||
List<Label> getLabels();
|
||||
|
||||
List<Label> getLabels(Label.Type... types);
|
||||
|
||||
int countUnread(Label label);
|
||||
|
||||
Plaintext getMessage(Object id);
|
||||
|
||||
Plaintext getMessage(InventoryVector iv);
|
||||
|
||||
Plaintext getMessage(byte[] initialHash);
|
||||
|
||||
Plaintext getMessageForAck(byte[] ackData);
|
||||
|
||||
/**
|
||||
* @param label to search for
|
||||
* @return a distinct list of all conversations that have at least one message with the given label.
|
||||
*/
|
||||
List<UUID> findConversations(Label label);
|
||||
|
||||
List<Plaintext> findMessages(Label label);
|
||||
|
||||
List<Plaintext> findMessages(Status status);
|
||||
|
||||
List<Plaintext> findMessages(Status status, BitmessageAddress recipient);
|
||||
|
||||
List<Plaintext> findMessages(BitmessageAddress sender);
|
||||
|
||||
List<Plaintext> findResponses(Plaintext parent);
|
||||
|
||||
List<Plaintext> findMessagesToResend();
|
||||
|
||||
void save(Plaintext message);
|
||||
|
||||
void remove(Plaintext message);
|
||||
|
||||
/**
|
||||
* Returns all messages with this conversation ID. The returned messages aren't sorted in any way,
|
||||
* so you may prefer to use {@link ch.dissem.bitmessage.utils.ConversationService#getConversation(UUID)}
|
||||
* instead.
|
||||
*
|
||||
* @param conversationId ID of the requested conversation
|
||||
* @return all messages with the given conversation ID
|
||||
*/
|
||||
Collection<Plaintext> getConversation(UUID conversationId);
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
|
||||
|
||||
/**
|
||||
* A POW engine using all available CPU cores.
|
||||
*/
|
||||
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
|
||||
private final ExecutorService waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build());
|
||||
private final ExecutorService workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build());
|
||||
|
||||
/**
|
||||
* This method will block until all pending nonce calculations are done, but not wait for its own calculation
|
||||
* to finish.
|
||||
* (This implementation becomes very inefficient if multiple nonce are calculated at the same time.)
|
||||
*
|
||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||
* @param target the target, representing an unsigned long
|
||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
||||
*/
|
||||
@Override
|
||||
public void calculateNonce(final byte[] initialHash, final byte[] target, final Callback callback) {
|
||||
waiterPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
int cores = Runtime.getRuntime().availableProcessors();
|
||||
if (cores > 255) cores = 255;
|
||||
LOG.info("Doing POW using " + cores + " cores");
|
||||
List<Worker> workers = new ArrayList<>(cores);
|
||||
for (int i = 0; i < cores; i++) {
|
||||
Worker w = new Worker((byte) cores, i, initialHash, target);
|
||||
workers.add(w);
|
||||
}
|
||||
List<Future<byte[]>> futures = new ArrayList<>(cores);
|
||||
for (Worker w : workers) {
|
||||
// Doing this in the previous loop might cause a ConcurrentModificationException in the worker
|
||||
// if a worker finds a nonce while new ones are still being added.
|
||||
futures.add(workerPool.submit(w));
|
||||
}
|
||||
try {
|
||||
while (!Thread.interrupted()) {
|
||||
for (Future<byte[]> future : futures) {
|
||||
if (future.isDone()) {
|
||||
callback.onNonceCalculated(initialHash, future.get());
|
||||
LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
|
||||
for (Future<byte[]> f : futures) {
|
||||
f.cancel(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
LOG.error("POW waiter thread interrupted - this should not happen!");
|
||||
} catch (ExecutionException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("POW waiter thread interrupted - this should not happen!", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class Worker implements Callable<byte[]> {
|
||||
private final byte numberOfCores;
|
||||
private final byte[] initialHash;
|
||||
private final byte[] target;
|
||||
private final MessageDigest mda;
|
||||
private final byte[] nonce = new byte[8];
|
||||
|
||||
Worker(byte numberOfCores, int core, byte[] initialHash, byte[] target) {
|
||||
this.numberOfCores = numberOfCores;
|
||||
this.initialHash = initialHash;
|
||||
this.target = target;
|
||||
this.nonce[7] = (byte) core;
|
||||
try {
|
||||
mda = MessageDigest.getInstance("SHA-512");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call() throws Exception {
|
||||
do {
|
||||
inc(nonce, numberOfCores);
|
||||
mda.update(nonce);
|
||||
mda.update(initialHash);
|
||||
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
|
||||
return nonce;
|
||||
}
|
||||
} while (!Thread.interrupted());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Helper class to kick start node registries.
|
||||
*/
|
||||
public class NodeRegistryHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NodeRegistryHelper.class);
|
||||
|
||||
public static Map<Long, Set<NetworkAddress>> loadStableNodes() {
|
||||
try (InputStream in = NodeRegistryHelper.class.getClassLoader().getResourceAsStream("nodes.txt")) {
|
||||
Scanner scanner = new Scanner(in);
|
||||
long stream = 0;
|
||||
Map<Long, Set<NetworkAddress>> result = new HashMap<>();
|
||||
Set<NetworkAddress> streamSet = null;
|
||||
while (scanner.hasNext()) {
|
||||
try {
|
||||
String line = scanner.nextLine().trim();
|
||||
if (line.startsWith("[stream")) {
|
||||
stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
|
||||
streamSet = new HashSet<>();
|
||||
result.put(stream, streamSet);
|
||||
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
|
||||
int portIndex = line.lastIndexOf(':');
|
||||
InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex));
|
||||
int port = Integer.valueOf(line.substring(portIndex + 1));
|
||||
for (InetAddress inetAddress : inetAddresses) {
|
||||
streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
for (Map.Entry<Long, Set<NetworkAddress>> e : result.entrySet()) {
|
||||
LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes.");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects that proof of work is currently being done for.
|
||||
*
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public interface ProofOfWorkRepository {
|
||||
Item getItem(byte[] initialHash);
|
||||
|
||||
List<byte[]> getItems();
|
||||
|
||||
void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
||||
|
||||
void putObject(Item item);
|
||||
|
||||
void removeObject(byte[] initialHash);
|
||||
|
||||
class Item {
|
||||
public final ObjectMessage object;
|
||||
public final long nonceTrialsPerByte;
|
||||
public final long extraBytes;
|
||||
|
||||
// Needed for ACK POW calculation
|
||||
public final Long expirationTime;
|
||||
public final Plaintext message;
|
||||
|
||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
||||
this(object, nonceTrialsPerByte, extraBytes, 0, null);
|
||||
}
|
||||
|
||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) {
|
||||
this.object = object;
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
||||
this.extraBytes = extraBytes;
|
||||
this.expirationTime = expirationTime;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
|
||||
/**
|
||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
||||
* <p>
|
||||
* <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's
|
||||
* another reason not to use this one.
|
||||
* </p>
|
||||
*/
|
||||
public class SimplePOWEngine implements ProofOfWorkEngine {
|
||||
@Override
|
||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
||||
try {
|
||||
MessageDigest mda = MessageDigest.getInstance("SHA-512");
|
||||
byte[] nonce = new byte[8];
|
||||
do {
|
||||
inc(nonce);
|
||||
mda.update(nonce);
|
||||
mda.update(initialHash);
|
||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.exception.AddressFormatException;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
|
||||
/**
|
||||
* Base58 encoder and decoder.
|
||||
*
|
||||
* @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily.
|
||||
*/
|
||||
public class Base58 {
|
||||
private static final int[] INDEXES = new int[128];
|
||||
private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < INDEXES.length; i++) {
|
||||
INDEXES[i] = -1;
|
||||
}
|
||||
for (int i = 0; i < ALPHABET.length; i++) {
|
||||
INDEXES[ALPHABET[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given bytes in base58. No checksum is appended.
|
||||
*
|
||||
* @param data to encode
|
||||
* @return base58 encoded input
|
||||
*/
|
||||
public static String encode(byte[] data) {
|
||||
if (data.length == 0) {
|
||||
return "";
|
||||
}
|
||||
final byte[] bytes = copyOfRange(data, 0, data.length);
|
||||
// Count leading zeroes.
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < bytes.length && bytes[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
// The actual encoding.
|
||||
byte[] temp = new byte[bytes.length * 2];
|
||||
int j = temp.length;
|
||||
|
||||
int startAt = zeroCount;
|
||||
while (startAt < bytes.length) {
|
||||
byte mod = divmod58(bytes, startAt);
|
||||
if (bytes[startAt] == 0) {
|
||||
++startAt;
|
||||
}
|
||||
temp[--j] = (byte) ALPHABET[mod];
|
||||
}
|
||||
|
||||
// Strip extra '1' if there are some after decoding.
|
||||
while (j < temp.length && temp[j] == ALPHABET[0]) {
|
||||
++j;
|
||||
}
|
||||
// Add as many leading '1' as there were leading zeros.
|
||||
while (--zeroCount >= 0) {
|
||||
temp[--j] = (byte) ALPHABET[0];
|
||||
}
|
||||
|
||||
byte[] output = copyOfRange(temp, j, temp.length);
|
||||
try {
|
||||
return new String(output, "US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ApplicationException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decode(String input) throws AddressFormatException {
|
||||
if (input.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
byte[] input58 = new byte[input.length()];
|
||||
// Transform the String to a base58 byte sequence
|
||||
for (int i = 0; i < input.length(); ++i) {
|
||||
char c = input.charAt(i);
|
||||
|
||||
int digit58 = -1;
|
||||
if (c < 128) {
|
||||
digit58 = INDEXES[c];
|
||||
}
|
||||
if (digit58 < 0) {
|
||||
throw new AddressFormatException("Illegal character " + c + " at " + i);
|
||||
}
|
||||
|
||||
input58[i] = (byte) digit58;
|
||||
}
|
||||
// Count leading zeroes
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < input58.length && input58[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
// The encoding
|
||||
byte[] temp = new byte[input.length()];
|
||||
int j = temp.length;
|
||||
|
||||
int startAt = zeroCount;
|
||||
while (startAt < input58.length) {
|
||||
byte mod = divmod256(input58, startAt);
|
||||
if (input58[startAt] == 0) {
|
||||
++startAt;
|
||||
}
|
||||
|
||||
temp[--j] = mod;
|
||||
}
|
||||
// Do no add extra leading zeroes, move j to first non null byte.
|
||||
while (j < temp.length && temp[j] == 0) {
|
||||
++j;
|
||||
}
|
||||
return copyOfRange(temp, j - zeroCount, temp.length);
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 58, returns number % 58
|
||||
//
|
||||
private static byte divmod58(byte[] number, int startAt) {
|
||||
int remainder = 0;
|
||||
for (int i = startAt; i < number.length; i++) {
|
||||
int digit256 = (int) number[i] & 0xFF;
|
||||
int temp = remainder * 256 + digit256;
|
||||
|
||||
number[i] = (byte) (temp / 58);
|
||||
|
||||
remainder = temp % 58;
|
||||
}
|
||||
|
||||
return (byte) remainder;
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 256, returns number % 256
|
||||
//
|
||||
private static byte divmod256(byte[] number58, int startAt) {
|
||||
int remainder = 0;
|
||||
for (int i = startAt; i < number58.length; i++) {
|
||||
int digit58 = (int) number58[i] & 0xFF;
|
||||
int temp = remainder * 58 + digit58;
|
||||
|
||||
number58[i] = (byte) (temp / 256);
|
||||
|
||||
remainder = temp % 256;
|
||||
}
|
||||
|
||||
return (byte) remainder;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,6 +21,10 @@ package ch.dissem.bitmessage.utils;
|
||||
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
||||
* part so we don't have to convert between byte arrays and numbers in time critical
|
||||
* situations.
|
||||
* <p>
|
||||
* Note: This class can't yet be ported to Kotlin, as with Kotlin byte + byte = int, which
|
||||
* would be rather inefficient in our case.
|
||||
* </p>
|
||||
*/
|
||||
public class Bytes {
|
||||
public static final byte BYTE_0x80 = (byte) 0x80;
|
||||
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class Collections {
|
||||
private final static Random RANDOM = new Random();
|
||||
|
||||
/**
|
||||
* @param count the number of elements to return (if possible)
|
||||
* @param collection the collection to take samples from
|
||||
* @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The
|
||||
* result is by no means securely random, but should be random enough so not the same objects get selected over
|
||||
* and over again.
|
||||
*/
|
||||
public static <T> List<T> selectRandom(int count, Collection<T> collection) {
|
||||
ArrayList<T> result = new ArrayList<>(count);
|
||||
if (collection.size() <= count) {
|
||||
result.addAll(collection);
|
||||
} else {
|
||||
double collectionRest = collection.size();
|
||||
double resultRest = count;
|
||||
int skipMax = (int) Math.ceil(collectionRest / resultRest);
|
||||
int skip = RANDOM.nextInt(skipMax);
|
||||
for (T item : collection) {
|
||||
collectionRest--;
|
||||
if (skip > 0) {
|
||||
skip--;
|
||||
} else {
|
||||
result.add(item);
|
||||
resultRest--;
|
||||
if (resultRest == 0) {
|
||||
break;
|
||||
}
|
||||
skipMax = (int) Math.ceil(collectionRest / resultRest);
|
||||
skip = RANDOM.nextInt(skipMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> T selectRandom(Collection<T> collection) {
|
||||
int index = RANDOM.nextInt(collection.size());
|
||||
for (T item : collection) {
|
||||
if (index == 0) {
|
||||
return item;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
throw new IllegalArgumentException("Empty collection? Size: " + collection.size());
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.util.regex.Pattern.CASE_INSENSITIVE;
|
||||
|
||||
/**
|
||||
* Service that helps with conversations.
|
||||
*/
|
||||
public class ConversationService {
|
||||
private final MessageRepository messageRepository;
|
||||
|
||||
private final Pattern SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE);
|
||||
|
||||
public ConversationService(MessageRepository messageRepository) {
|
||||
this.messageRepository = messageRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the whole conversation from one single message. If the message isn't part
|
||||
* of a conversation, a singleton list containing the given message is returned. Otherwise
|
||||
* it's the same as {@link #getConversation(UUID)}
|
||||
*
|
||||
* @param message
|
||||
* @return a list of messages that belong to the same conversation.
|
||||
*/
|
||||
public List<Plaintext> getConversation(Plaintext message) {
|
||||
if (message.getConversationId() == null) {
|
||||
return Collections.singletonList(message);
|
||||
}
|
||||
return getConversation(message.getConversationId());
|
||||
}
|
||||
|
||||
private LinkedList<Plaintext> sorted(Collection<Plaintext> collection) {
|
||||
LinkedList<Plaintext> result = new LinkedList<>(collection);
|
||||
Collections.sort(result, new Comparator<Plaintext>() {
|
||||
@Override
|
||||
public int compare(Plaintext o1, Plaintext o2) {
|
||||
//noinspection NumberEquality - if both are null (if both are the same, it's a bonus)
|
||||
if (o1.getReceived() == o2.getReceived()) {
|
||||
return 0;
|
||||
}
|
||||
if (o1.getReceived() == null) {
|
||||
return -1;
|
||||
}
|
||||
if (o2.getReceived() == null) {
|
||||
return 1;
|
||||
}
|
||||
return -o1.getReceived().compareTo(o2.getReceived());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Plaintext> getConversation(UUID conversationId) {
|
||||
LinkedList<Plaintext> messages = sorted(messageRepository.getConversation(conversationId));
|
||||
Map<InventoryVector, Plaintext> map = new HashMap<>(messages.size());
|
||||
for (Plaintext message : messages) {
|
||||
if (message.getInventoryVector() != null) {
|
||||
map.put(message.getInventoryVector(), message);
|
||||
}
|
||||
}
|
||||
|
||||
LinkedList<Plaintext> result = new LinkedList<>();
|
||||
while (!messages.isEmpty()) {
|
||||
Plaintext last = messages.poll();
|
||||
int pos = lastParentPosition(last, result);
|
||||
result.add(pos, last);
|
||||
addAncestors(last, result, messages, map);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getSubject(List<Plaintext> conversation) {
|
||||
if (conversation.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// TODO: this has room for improvement
|
||||
String subject = conversation.get(0).getSubject();
|
||||
Matcher matcher = SUBJECT_PREFIX.matcher(subject);
|
||||
if (matcher.find()) {
|
||||
return subject.substring(matcher.end()).trim();
|
||||
}
|
||||
return subject.trim();
|
||||
}
|
||||
|
||||
private int lastParentPosition(Plaintext child, LinkedList<Plaintext> messages) {
|
||||
Iterator<Plaintext> plaintextIterator = messages.descendingIterator();
|
||||
int i = 0;
|
||||
while (plaintextIterator.hasNext()) {
|
||||
Plaintext next = plaintextIterator.next();
|
||||
if (isParent(next, child)) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return messages.size() - i;
|
||||
}
|
||||
|
||||
private boolean isParent(Plaintext item, Plaintext child) {
|
||||
for (InventoryVector parentId : child.getParents()) {
|
||||
if (parentId.equals(item.getInventoryVector())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addAncestors(Plaintext message, LinkedList<Plaintext> result, LinkedList<Plaintext> messages, Map<InventoryVector, Plaintext> map) {
|
||||
for (InventoryVector parentKey : message.getParents()) {
|
||||
Plaintext parent = map.remove(parentKey);
|
||||
if (parent != null) {
|
||||
messages.remove(parent);
|
||||
result.addFirst(parent);
|
||||
addAncestors(parent, result, messages, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class DebugUtils {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
|
||||
|
||||
public static void saveToFile(ObjectMessage objectMessage) {
|
||||
try {
|
||||
File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv");
|
||||
f.createNewFile();
|
||||
objectMessage.write(new FileOutputStream(f));
|
||||
} catch (IOException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <K> void inc(Map<K, Integer> map, K key) {
|
||||
if (map.containsKey(key)) {
|
||||
map.put(key, map.get(key) + 1);
|
||||
} else {
|
||||
map.put(key, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
||||
|
||||
/**
|
||||
* This class handles decoding simple types from byte stream, according to
|
||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||
*/
|
||||
public class Decode {
|
||||
public static byte[] shortVarBytes(InputStream in, AccessCounter counter) throws IOException {
|
||||
int length = uint16(in, counter);
|
||||
return bytes(in, length, counter);
|
||||
}
|
||||
|
||||
public static byte[] varBytes(InputStream in) throws IOException {
|
||||
return varBytes(in, null);
|
||||
}
|
||||
|
||||
public static byte[] varBytes(InputStream in, AccessCounter counter) throws IOException {
|
||||
int length = (int) varInt(in, counter);
|
||||
return bytes(in, length, counter);
|
||||
}
|
||||
|
||||
public static byte[] bytes(InputStream in, int count) throws IOException {
|
||||
return bytes(in, count, null);
|
||||
}
|
||||
|
||||
public static byte[] bytes(InputStream in, int count, AccessCounter counter) throws IOException {
|
||||
byte[] result = new byte[count];
|
||||
int off = 0;
|
||||
while (off < count) {
|
||||
int read = in.read(result, off, count - off);
|
||||
if (read < 0) {
|
||||
throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off);
|
||||
}
|
||||
off += read;
|
||||
}
|
||||
inc(counter, count);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long[] varIntList(InputStream in) throws IOException {
|
||||
int length = (int) varInt(in);
|
||||
long[] result = new long[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[i] = varInt(in);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long varInt(InputStream in) throws IOException {
|
||||
return varInt(in, null);
|
||||
}
|
||||
|
||||
public static long varInt(InputStream in, AccessCounter counter) throws IOException {
|
||||
int first = in.read();
|
||||
inc(counter);
|
||||
switch (first) {
|
||||
case 0xfd:
|
||||
return uint16(in, counter);
|
||||
case 0xfe:
|
||||
return uint32(in, counter);
|
||||
case 0xff:
|
||||
return int64(in, counter);
|
||||
default:
|
||||
return first;
|
||||
}
|
||||
}
|
||||
|
||||
public static int uint8(InputStream in) throws IOException {
|
||||
return in.read();
|
||||
}
|
||||
|
||||
public static int uint16(InputStream in) throws IOException {
|
||||
return uint16(in, null);
|
||||
}
|
||||
|
||||
public static int uint16(InputStream in, AccessCounter counter) throws IOException {
|
||||
inc(counter, 2);
|
||||
return in.read() << 8 | in.read();
|
||||
}
|
||||
|
||||
public static long uint32(InputStream in) throws IOException {
|
||||
return uint32(in, null);
|
||||
}
|
||||
|
||||
public static long uint32(InputStream in, AccessCounter counter) throws IOException {
|
||||
inc(counter, 4);
|
||||
return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read();
|
||||
}
|
||||
|
||||
public static long uint32(ByteBuffer in) {
|
||||
return u(in.get()) << 24 | u(in.get()) << 16 | u(in.get()) << 8 | u(in.get());
|
||||
}
|
||||
|
||||
public static int int32(InputStream in) throws IOException {
|
||||
return int32(in, null);
|
||||
}
|
||||
|
||||
public static int int32(InputStream in, AccessCounter counter) throws IOException {
|
||||
inc(counter, 4);
|
||||
return ByteBuffer.wrap(bytes(in, 4)).getInt();
|
||||
}
|
||||
|
||||
public static long int64(InputStream in) throws IOException {
|
||||
return int64(in, null);
|
||||
}
|
||||
|
||||
public static long int64(InputStream in, AccessCounter counter) throws IOException {
|
||||
inc(counter, 8);
|
||||
return ByteBuffer.wrap(bytes(in, 8)).getLong();
|
||||
}
|
||||
|
||||
public static String varString(InputStream in) throws IOException {
|
||||
return varString(in, null);
|
||||
}
|
||||
|
||||
public static String varString(InputStream in, AccessCounter counter) throws IOException {
|
||||
int length = (int) varInt(in, counter);
|
||||
// technically, it says the length in characters, but I think this one might be correct
|
||||
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
||||
return new String(bytes(in, length, counter), "utf-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given byte as if it were unsigned.
|
||||
*/
|
||||
private static int u(byte b) {
|
||||
return b & 0xFF;
|
||||
}
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
||||
|
||||
/**
|
||||
* This class handles encoding simple types from byte stream, according to
|
||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||
*/
|
||||
public class Encode {
|
||||
public static void varIntList(long[] values, OutputStream stream) throws IOException {
|
||||
varInt(values.length, stream);
|
||||
for (long value : values) {
|
||||
varInt(value, stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void varIntList(long[] values, ByteBuffer buffer) {
|
||||
varInt(values.length, buffer);
|
||||
for (long value : values) {
|
||||
varInt(value, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public static void varInt(long value, OutputStream stream) throws IOException {
|
||||
varInt(value, stream, null);
|
||||
}
|
||||
|
||||
public static void varInt(long value, ByteBuffer buffer) {
|
||||
if (value < 0) {
|
||||
// This is due to the fact that Java doesn't really support unsigned values.
|
||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
||||
// Normally, negative values shouldn't occur within the protocol, and longs large enough for being
|
||||
// recognized as negatives aren't realistic.
|
||||
buffer.put((byte) 0xff);
|
||||
buffer.putLong(value);
|
||||
} else if (value < 0xfd) {
|
||||
buffer.put((byte) value);
|
||||
} else if (value <= 0xffffL) {
|
||||
buffer.put((byte) 0xfd);
|
||||
buffer.putShort((short) value);
|
||||
} else if (value <= 0xffffffffL) {
|
||||
buffer.put((byte) 0xfe);
|
||||
buffer.putInt((int) value);
|
||||
} else {
|
||||
buffer.put((byte) 0xff);
|
||||
buffer.putLong(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] varInt(long value) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(9);
|
||||
varInt(value, buffer);
|
||||
buffer.flip();
|
||||
return Bytes.truncate(buffer.array(), buffer.limit());
|
||||
}
|
||||
|
||||
public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(9);
|
||||
varInt(value, buffer);
|
||||
buffer.flip();
|
||||
stream.write(buffer.array(), 0, buffer.limit());
|
||||
inc(counter, buffer.limit());
|
||||
}
|
||||
|
||||
public static void int8(long value, OutputStream stream) throws IOException {
|
||||
int8(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write((int) value);
|
||||
inc(counter);
|
||||
}
|
||||
|
||||
public static void int16(long value, OutputStream stream) throws IOException {
|
||||
int16(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write(ByteBuffer.allocate(2).putShort((short) value).array());
|
||||
inc(counter, 2);
|
||||
}
|
||||
|
||||
public static void int16(long value, ByteBuffer buffer) {
|
||||
buffer.putShort((short) value);
|
||||
}
|
||||
|
||||
public static void int32(long value, OutputStream stream) throws IOException {
|
||||
int32(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array());
|
||||
inc(counter, 4);
|
||||
}
|
||||
|
||||
public static void int32(long value, ByteBuffer buffer) {
|
||||
buffer.putInt((int) value);
|
||||
}
|
||||
|
||||
public static void int64(long value, OutputStream stream) throws IOException {
|
||||
int64(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write(ByteBuffer.allocate(8).putLong(value).array());
|
||||
inc(counter, 8);
|
||||
}
|
||||
|
||||
public static void int64(long value, ByteBuffer buffer) {
|
||||
buffer.putLong(value);
|
||||
}
|
||||
|
||||
public static void varString(String value, OutputStream out) throws IOException {
|
||||
byte[] bytes = value.getBytes("utf-8");
|
||||
// Technically, it says the length in characters, but I think this one might be correct.
|
||||
// It doesn't really matter, as only ASCII characters are being used.
|
||||
// see also Decode#varString()
|
||||
varInt(bytes.length, out);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
public static void varString(String value, ByteBuffer buffer) {
|
||||
try {
|
||||
byte[] bytes = value.getBytes("utf-8");
|
||||
// Technically, it says the length in characters, but I think this one might be correct.
|
||||
// It doesn't really matter, as only ASCII characters are being used.
|
||||
// see also Decode#varString()
|
||||
buffer.put(varInt(bytes.length));
|
||||
buffer.put(bytes);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void varBytes(byte[] data, OutputStream out) throws IOException {
|
||||
varInt(data.length, out);
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
public static void varBytes(byte[] data, ByteBuffer buffer) {
|
||||
varInt(data.length, buffer);
|
||||
buffer.put(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a {@link Streamable} object and returns the byte array.
|
||||
*
|
||||
* @param streamable the object to be serialized
|
||||
* @return an array of bytes representing the given streamable object.
|
||||
*/
|
||||
public static byte[] bytes(Streamable streamable) {
|
||||
if (streamable == null) return null;
|
||||
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try {
|
||||
streamable.write(stream);
|
||||
} catch (IOException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param streamable the object to be serialized
|
||||
* @param padding the result will be padded such that its length is a multiple of <em>padding</em>
|
||||
* @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding.
|
||||
*/
|
||||
public static byte[] bytes(Streamable streamable, int padding) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try {
|
||||
streamable.write(stream);
|
||||
} catch (IOException e) {
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
int offset = padding - stream.size() % padding;
|
||||
int length = stream.size() + offset;
|
||||
byte[] result = new byte[length];
|
||||
stream.write(result, offset, stream.size());
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class Numbers {
|
||||
public static long max(long a, long b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
|
||||
* used to contain different status information. It is by default displayed in some JSON inspired human readable
|
||||
* notation, but you might only want to rely on the 'human readable' part.
|
||||
* <p>
|
||||
* If you need a real JSON representation, please add a method <code>toJson()</code>.
|
||||
* </p>
|
||||
*/
|
||||
public class Property {
|
||||
private String name;
|
||||
private Object value;
|
||||
private Property[] properties;
|
||||
|
||||
public Property(String name, Object value, Property... properties) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property if available or <code>null</code> otherwise.
|
||||
* Subproperties can be requested by submitting the sequence of properties.
|
||||
*/
|
||||
public Property getProperty(String... name) {
|
||||
if (name == null || name.length == 0) return null;
|
||||
|
||||
for (Property p : properties) {
|
||||
if (Objects.equals(name[0], p.name)) {
|
||||
if (name.length == 1)
|
||||
return p;
|
||||
else
|
||||
return p.getProperty(Arrays.copyOfRange(name, 1, name.length));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Property[] getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString("");
|
||||
}
|
||||
|
||||
private String toString(String indentation) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(indentation).append(name).append(": ");
|
||||
if (value != null || properties.length == 0) {
|
||||
result.append(value);
|
||||
}
|
||||
if (properties.length > 0) {
|
||||
result.append("{\n");
|
||||
for (Property property : properties) {
|
||||
result.append(property.toString(indentation + " ")).append('\n');
|
||||
}
|
||||
result.append(indentation).append("}");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Strings.hex;
|
||||
|
||||
public class SqlStrings {
|
||||
public static StringBuilder join(long... objects) {
|
||||
StringBuilder streamList = new StringBuilder();
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (i > 0) streamList.append(", ");
|
||||
streamList.append(objects[i]);
|
||||
}
|
||||
return streamList;
|
||||
}
|
||||
|
||||
public static StringBuilder join(byte[]... objects) {
|
||||
StringBuilder streamList = new StringBuilder();
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (i > 0) streamList.append(", ");
|
||||
streamList.append(hex(objects[i]));
|
||||
}
|
||||
return streamList;
|
||||
}
|
||||
|
||||
public static StringBuilder join(ObjectType... types) {
|
||||
StringBuilder streamList = new StringBuilder();
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (i > 0) streamList.append(", ");
|
||||
streamList.append(types[i].getNumber());
|
||||
}
|
||||
return streamList;
|
||||
}
|
||||
|
||||
public static StringBuilder join(Enum... types) {
|
||||
StringBuilder streamList = new StringBuilder();
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (i > 0) streamList.append(", ");
|
||||
streamList.append('\'').append(types[i].name()).append('\'');
|
||||
}
|
||||
return streamList;
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
|
||||
/**
|
||||
* Stores times to live in seconds for different object types. Usually this shouldn't be messed with, but for tests
|
||||
* it might be a good idea to reduce it to a minimum, and on mobile clients you might want to optimize it as well.
|
||||
*
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class TTL {
|
||||
private static long msg = 2 * DAY;
|
||||
private static long getpubkey = 2 * DAY;
|
||||
private static long pubkey = 28 * DAY;
|
||||
|
||||
public static long msg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static void msg(long msg) {
|
||||
TTL.msg = validate(msg);
|
||||
}
|
||||
|
||||
public static long getpubkey() {
|
||||
return getpubkey;
|
||||
}
|
||||
|
||||
public static void getpubkey(long getpubkey) {
|
||||
TTL.getpubkey = validate(getpubkey);
|
||||
}
|
||||
|
||||
public static long pubkey() {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
public static void pubkey(long pubkey) {
|
||||
TTL.pubkey = validate(pubkey);
|
||||
}
|
||||
|
||||
private static long validate(long ttl) {
|
||||
if (ttl < 0 || ttl > 28 * DAY) throw new IllegalArgumentException("TTL must be between 0 seconds and 28 days");
|
||||
return ttl;
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ThreadFactoryBuilder {
|
||||
private final String namePrefix;
|
||||
private int prio = Thread.NORM_PRIORITY;
|
||||
private boolean daemon = false;
|
||||
|
||||
private ThreadFactoryBuilder(String pool) {
|
||||
this.namePrefix = pool + "-thread-";
|
||||
}
|
||||
|
||||
|
||||
public static ThreadFactoryBuilder pool(String name) {
|
||||
return new ThreadFactoryBuilder(name);
|
||||
}
|
||||
|
||||
public ThreadFactoryBuilder lowPrio() {
|
||||
prio = Thread.MIN_PRIORITY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadFactoryBuilder daemon() {
|
||||
daemon = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadFactory build() {
|
||||
SecurityManager s = System.getSecurityManager();
|
||||
final ThreadGroup group = (s != null) ? s.getThreadGroup() :
|
||||
Thread.currentThread().getThreadGroup();
|
||||
|
||||
return new ThreadFactory() {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(group, r,
|
||||
namePrefix + threadNumber.getAndIncrement(),
|
||||
0);
|
||||
t.setPriority(prio);
|
||||
t.setDaemon(daemon);
|
||||
return t;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
/**
|
||||
* A simple utility class that simplifies using the second based time used in Bitmessage.
|
||||
*/
|
||||
public class UnixTime {
|
||||
/**
|
||||
* Length of a minute in seconds, intended for use with {@link #now(long)}.
|
||||
*/
|
||||
public static final int MINUTE = 60;
|
||||
/**
|
||||
* Length of an hour in seconds, intended for use with {@link #now(long)}.
|
||||
*/
|
||||
public static final long HOUR = 60 * MINUTE;
|
||||
/**
|
||||
* Length of a day in seconds, intended for use with {@link #now(long)}.
|
||||
*/
|
||||
public static final long DAY = 24 * HOUR;
|
||||
|
||||
/**
|
||||
* @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000)
|
||||
*/
|
||||
public static long now() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #now()} + shiftSeconds, but might be more readable.
|
||||
*
|
||||
* @param shiftSeconds number of seconds from now we're interested in
|
||||
* @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago
|
||||
*/
|
||||
public static long now(long shiftSeconds) {
|
||||
return (System.currentTimeMillis() / 1000) + shiftSeconds;
|
||||
}
|
||||
}
|
509
core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt
Normal file
509
core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt
Normal file
@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.CustomMessage
|
||||
import ch.dissem.bitmessage.entity.MessagePayload
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Status.DRAFT
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
import ch.dissem.bitmessage.entity.payload.Broadcast
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.*
|
||||
import ch.dissem.bitmessage.utils.Property
|
||||
import ch.dissem.bitmessage.utils.UnixTime.HOUR
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
*
|
||||
* Use this class if you want to create a Bitmessage client.
|
||||
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
||||
*
|
||||
* * addressRepo
|
||||
* * inventory
|
||||
* * nodeRegistry
|
||||
* * networkHandler
|
||||
* * messageRepo
|
||||
* * streams
|
||||
*
|
||||
*
|
||||
* The default implementations in the different module builds can be used.
|
||||
*
|
||||
* The port defaults to 8444 (the default Bitmessage port)
|
||||
*/
|
||||
class BitmessageContext(
|
||||
cryptography: Cryptography,
|
||||
inventory: Inventory,
|
||||
nodeRegistry: NodeRegistry,
|
||||
networkHandler: NetworkHandler,
|
||||
addressRepository: AddressRepository,
|
||||
messageRepository: MessageRepository,
|
||||
proofOfWorkRepository: ProofOfWorkRepository,
|
||||
proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(),
|
||||
customCommandHandler: CustomCommandHandler = object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
listener: Listener,
|
||||
labeler: Labeler = DefaultLabeler(),
|
||||
userAgent: String? = null,
|
||||
port: Int = 8444,
|
||||
connectionTTL: Long = 30 * MINUTE,
|
||||
connectionLimit: Int = 150,
|
||||
sendPubkeyOnIdentityCreation: Boolean = true,
|
||||
doMissingProofOfWorkDelayInSeconds: Int = 30
|
||||
) {
|
||||
|
||||
private constructor(builder: BitmessageContext.Builder) : this(
|
||||
builder.cryptography,
|
||||
builder.inventory,
|
||||
builder.nodeRegistry,
|
||||
builder.networkHandler,
|
||||
builder.addressRepo,
|
||||
builder.messageRepo,
|
||||
builder.proofOfWorkRepository,
|
||||
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
builder.listener,
|
||||
builder.labeler ?: DefaultLabeler(),
|
||||
builder.userAgent,
|
||||
builder.port,
|
||||
builder.connectionTTL,
|
||||
builder.connectionLimit,
|
||||
builder.sendPubkeyOnIdentityCreation,
|
||||
builder.doMissingProofOfWorkDelay
|
||||
)
|
||||
|
||||
private val sendPubkeyOnIdentityCreation: Boolean
|
||||
|
||||
/**
|
||||
* The [InternalContext] - normally you wouldn't need it,
|
||||
* unless you are doing something crazy with the protocol.
|
||||
*/
|
||||
val internals: InternalContext
|
||||
@JvmName("internals") get
|
||||
|
||||
val labeler: Labeler
|
||||
@JvmName("labeler") get
|
||||
|
||||
val addresses: AddressRepository
|
||||
@JvmName("addresses") get
|
||||
|
||||
val messages: MessageRepository
|
||||
@JvmName("messages") get
|
||||
|
||||
fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress {
|
||||
val identity = BitmessageAddress(PrivateKey(
|
||||
shorter,
|
||||
internals.streams[0],
|
||||
NETWORK_NONCE_TRIALS_PER_BYTE,
|
||||
NETWORK_EXTRA_BYTES,
|
||||
*features
|
||||
))
|
||||
internals.addressRepository.save(identity)
|
||||
if (sendPubkeyOnIdentityCreation) {
|
||||
internals.sendPubkey(identity, identity.stream)
|
||||
}
|
||||
return identity
|
||||
}
|
||||
|
||||
fun joinChan(passphrase: String, address: String): BitmessageAddress {
|
||||
val chan = BitmessageAddress.chan(address, passphrase)
|
||||
chan.alias = passphrase
|
||||
internals.addressRepository.save(chan)
|
||||
return chan
|
||||
}
|
||||
|
||||
fun createChan(passphrase: String): BitmessageAddress {
|
||||
// FIXME: hardcoded stream number
|
||||
val chan = BitmessageAddress.chan(1, passphrase)
|
||||
internals.addressRepository.save(chan)
|
||||
return chan
|
||||
}
|
||||
|
||||
fun createDeterministicAddresses(
|
||||
passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
|
||||
val result = BitmessageAddress.deterministic(
|
||||
passphrase, numberOfAddresses, version, stream, shorter)
|
||||
for (i in result.indices) {
|
||||
val address = result[i]
|
||||
address.alias = "deterministic (" + (i + 1) + ")"
|
||||
internals.addressRepository.save(address)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun broadcast(from: BitmessageAddress, subject: String, message: String) {
|
||||
send(Plaintext(
|
||||
type = BROADCAST,
|
||||
from = from,
|
||||
subject = subject,
|
||||
body = message,
|
||||
status = DRAFT
|
||||
))
|
||||
}
|
||||
|
||||
fun send(from: BitmessageAddress, to: BitmessageAddress, subject: String, message: String) {
|
||||
if (from.privateKey == null) {
|
||||
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||
}
|
||||
send(Plaintext(
|
||||
type = MSG,
|
||||
from = from,
|
||||
to = to,
|
||||
subject = subject,
|
||||
body = message
|
||||
))
|
||||
}
|
||||
|
||||
fun send(msg: Plaintext) {
|
||||
if (msg.from.privateKey == null) {
|
||||
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||
}
|
||||
labeler.markAsSending(msg)
|
||||
val to = msg.to
|
||||
if (to != null) {
|
||||
if (to.pubkey == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.")
|
||||
internals.requestPubkey(to)
|
||||
}
|
||||
if (to.pubkey == null) {
|
||||
internals.messageRepository.save(msg)
|
||||
}
|
||||
}
|
||||
if (to == null || to.pubkey != null) {
|
||||
LOG.info("Sending message.")
|
||||
internals.messageRepository.save(msg)
|
||||
if (msg.type == MSG) {
|
||||
internals.send(msg)
|
||||
} else {
|
||||
internals.send(
|
||||
msg.from,
|
||||
to,
|
||||
Factory.getBroadcast(msg),
|
||||
msg.ttl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startup() {
|
||||
internals.networkHandler.start()
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
internals.networkHandler.stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
||||
* *
|
||||
* @param port of the trusted host, default is 8444
|
||||
* *
|
||||
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
|
||||
* * if not all objects were fetched
|
||||
* *
|
||||
* @param wait waits for the synchronization thread to finish
|
||||
*/
|
||||
fun synchronize(host: InetAddress, port: Int, timeoutInSeconds: Long, wait: Boolean) {
|
||||
val future = internals.networkHandler.synchronize(host, port, timeoutInSeconds)
|
||||
if (wait) {
|
||||
try {
|
||||
future.get()
|
||||
} catch (e: InterruptedException) {
|
||||
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.")
|
||||
future.cancel(true)
|
||||
} catch (e: CancellationException) {
|
||||
LOG.debug(e.message, e)
|
||||
} catch (e: ExecutionException) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
||||
* the response, which in turn is expected to be a [CustomMessage].
|
||||
|
||||
* @param server the node's address
|
||||
* *
|
||||
* @param port the node's port
|
||||
* *
|
||||
* @param request the request
|
||||
* *
|
||||
* @return the response
|
||||
*/
|
||||
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
|
||||
return internals.networkHandler.send(server, port, request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired objects from the inventory. You should call this method regularly,
|
||||
* e.g. daily and on each shutdown.
|
||||
*/
|
||||
fun cleanup() {
|
||||
internals.inventory.cleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
||||
* recipient is expected to send acknowledgements.
|
||||
*
|
||||
*
|
||||
* You should call this method regularly, but be aware of the following:
|
||||
*
|
||||
* * As messages might be sent, POW will be done. It is therefore not advised to
|
||||
* call it on shutdown.
|
||||
* * It shouldn't be called right after startup, as it's possible the missing
|
||||
* acknowledgement was sent while the client was offline.
|
||||
* * Other than that, the call isn't expensive as long as there is no message
|
||||
* to send, so it might be a good idea to just call it every few minutes.
|
||||
*
|
||||
*/
|
||||
fun resendUnacknowledgedMessages() {
|
||||
internals.resendUnacknowledged()
|
||||
}
|
||||
|
||||
val isRunning: Boolean
|
||||
get() = internals.networkHandler.isRunning
|
||||
|
||||
fun addContact(contact: BitmessageAddress) {
|
||||
internals.addressRepository.save(contact)
|
||||
if (contact.pubkey == null) {
|
||||
// If it already existed, the saved contact might have the public key
|
||||
if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) {
|
||||
internals.requestPubkey(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addSubscribtion(address: BitmessageAddress) {
|
||||
address.isSubscribed = true
|
||||
internals.addressRepository.save(address)
|
||||
tryToFindBroadcastsForAddress(address)
|
||||
}
|
||||
|
||||
private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) {
|
||||
for (objectMessage in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
||||
try {
|
||||
val broadcast = objectMessage.payload as Broadcast
|
||||
broadcast.decrypt(address)
|
||||
// This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
|
||||
// other subscriptions and the interface stays as simple as possible.
|
||||
internals.networkListener.receive(objectMessage)
|
||||
} catch (ignore: DecryptionFailedException) {
|
||||
} catch (e: Exception) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun status(): Property {
|
||||
return Property("status",
|
||||
Property("user agent", internals.userAgent),
|
||||
internals.networkHandler.getNetworkStatus(),
|
||||
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
||||
)
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun receive(plaintext: Plaintext)
|
||||
|
||||
/**
|
||||
* A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot.
|
||||
*/
|
||||
interface WithContext : Listener {
|
||||
fun setContext(ctx: BitmessageContext)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder {
|
||||
internal var port = 8444
|
||||
internal var inventory by Delegates.notNull<Inventory>()
|
||||
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||
internal var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||
internal var addressRepo by Delegates.notNull<AddressRepository>()
|
||||
internal var messageRepo by Delegates.notNull<MessageRepository>()
|
||||
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
|
||||
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||
internal var cryptography by Delegates.notNull<Cryptography>()
|
||||
internal var customCommandHandler: CustomCommandHandler? = null
|
||||
internal var labeler: Labeler? = null
|
||||
internal var userAgent: String? = null
|
||||
internal var listener by Delegates.notNull<Listener>()
|
||||
internal var connectionLimit = 150
|
||||
internal var connectionTTL = 30 * MINUTE
|
||||
internal var sendPubkeyOnIdentityCreation = true
|
||||
internal var doMissingProofOfWorkDelay = 30
|
||||
|
||||
fun port(port: Int): Builder {
|
||||
this.port = port
|
||||
return this
|
||||
}
|
||||
|
||||
fun inventory(inventory: Inventory): Builder {
|
||||
this.inventory = inventory
|
||||
return this
|
||||
}
|
||||
|
||||
fun nodeRegistry(nodeRegistry: NodeRegistry): Builder {
|
||||
this.nodeRegistry = nodeRegistry
|
||||
return this
|
||||
}
|
||||
|
||||
fun networkHandler(networkHandler: NetworkHandler): Builder {
|
||||
this.networkHandler = networkHandler
|
||||
return this
|
||||
}
|
||||
|
||||
fun addressRepo(addressRepo: AddressRepository): Builder {
|
||||
this.addressRepo = addressRepo
|
||||
return this
|
||||
}
|
||||
|
||||
fun messageRepo(messageRepo: MessageRepository): Builder {
|
||||
this.messageRepo = messageRepo
|
||||
return this
|
||||
}
|
||||
|
||||
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
||||
this.proofOfWorkRepository = proofOfWorkRepository
|
||||
return this
|
||||
}
|
||||
|
||||
fun cryptography(cryptography: Cryptography): Builder {
|
||||
this.cryptography = cryptography
|
||||
return this
|
||||
}
|
||||
|
||||
fun customCommandHandler(handler: CustomCommandHandler): Builder {
|
||||
this.customCommandHandler = handler
|
||||
return this
|
||||
}
|
||||
|
||||
fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder {
|
||||
this.proofOfWorkEngine = proofOfWorkEngine
|
||||
return this
|
||||
}
|
||||
|
||||
fun labeler(labeler: Labeler): Builder {
|
||||
this.labeler = labeler
|
||||
return this
|
||||
}
|
||||
|
||||
fun listener(listener: Listener): Builder {
|
||||
this.listener = listener
|
||||
return this
|
||||
}
|
||||
|
||||
@JvmName("kotlinListener")
|
||||
fun listener(listener: (Plaintext) -> Unit): Builder {
|
||||
this.listener = object : Listener {
|
||||
override fun receive(plaintext: Plaintext) {
|
||||
listener.invoke(plaintext)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun connectionLimit(connectionLimit: Int): Builder {
|
||||
this.connectionLimit = connectionLimit
|
||||
return this
|
||||
}
|
||||
|
||||
fun connectionTTL(hours: Int): Builder {
|
||||
this.connectionTTL = hours * HOUR
|
||||
return this
|
||||
}
|
||||
|
||||
fun doMissingProofOfWorkDelay(seconds: Int) {
|
||||
this.doMissingProofOfWorkDelay = seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||
* this behaviour might not be desirable.
|
||||
*/
|
||||
fun doNotSendPubkeyOnIdentityCreation(): Builder {
|
||||
this.sendPubkeyOnIdentityCreation = false
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): BitmessageContext {
|
||||
return BitmessageContext(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
this.labeler = labeler
|
||||
this.internals = InternalContext(
|
||||
cryptography,
|
||||
inventory,
|
||||
nodeRegistry,
|
||||
networkHandler,
|
||||
addressRepository,
|
||||
messageRepository,
|
||||
proofOfWorkRepository,
|
||||
proofOfWorkEngine,
|
||||
customCommandHandler,
|
||||
listener,
|
||||
labeler,
|
||||
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/",
|
||||
port,
|
||||
connectionTTL,
|
||||
connectionLimit
|
||||
)
|
||||
this.addresses = addressRepository
|
||||
this.messages = messageRepository
|
||||
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation
|
||||
(listener as? Listener.WithContext)?.setContext(this)
|
||||
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val CURRENT_VERSION = 3
|
||||
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
||||
|
||||
val version: String by lazy {
|
||||
BitmessageContext::class.java.getResource("/version")?.readText() ?: "local build"
|
||||
}
|
||||
@JvmStatic get
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 ch.dissem.bitmessage.utils.Strings.hex
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
open class DefaultMessageListener(
|
||||
private val labeler: Labeler,
|
||||
private val listener: BitmessageContext.Listener
|
||||
) : NetworkHandler.MessageListener, InternalContext.ContextHolder {
|
||||
private lateinit var ctx: InternalContext
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
override fun receive(objectMessage: ObjectMessage) {
|
||||
val payload = objectMessage.payload
|
||||
|
||||
when (payload.type) {
|
||||
ObjectType.GET_PUBKEY -> {
|
||||
receive(objectMessage, payload as GetPubkey)
|
||||
}
|
||||
ObjectType.PUBKEY -> {
|
||||
receive(payload as Pubkey)
|
||||
}
|
||||
ObjectType.MSG -> {
|
||||
receive(objectMessage, payload as Msg)
|
||||
}
|
||||
ObjectType.BROADCAST -> {
|
||||
receive(objectMessage, payload as Broadcast)
|
||||
}
|
||||
null -> {
|
||||
if (payload is GenericPayload) {
|
||||
receive(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(objectMessage: 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, objectMessage.stream)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(pubkey: Pubkey) {
|
||||
try {
|
||||
if (pubkey is V4Pubkey) {
|
||||
ctx.addressRepository.findContact(pubkey.tag)?.let {
|
||||
if (it.pubkey == null) {
|
||||
pubkey.decrypt(it.publicDecryptionKey)
|
||||
updatePubkey(it, pubkey)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.addressRepository.findContact(pubkey.ripe)?.let {
|
||||
if (it.pubkey == null) {
|
||||
updatePubkey(it, 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(objectMessage: ObjectMessage, msg: Msg) {
|
||||
for (identity in ctx.addressRepository.getIdentities()) {
|
||||
try {
|
||||
msg.decrypt(identity.privateKey!!.privateEncryptionKey)
|
||||
val plaintext = msg.plaintext!!
|
||||
plaintext.to = identity
|
||||
if (!objectMessage.isSignatureValid(plaintext.from.pubkey!!)) {
|
||||
LOG.warn("Msg with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
|
||||
} else {
|
||||
receive(objectMessage.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)
|
||||
} ?: LOG.debug("Message not found for ack ${hex(ack.data)}")
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) {
|
||||
val tag = (broadcast as? V5Broadcast)?.tag
|
||||
ctx.addressRepository.getSubscriptions(broadcast.version)
|
||||
.filter { tag == null || Arrays.equals(tag, it.tag) }
|
||||
.forEach {
|
||||
try {
|
||||
broadcast.decrypt(it.publicDecryptionKey)
|
||||
if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) {
|
||||
LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
|
||||
} else {
|
||||
receive(objectMessage.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)
|
||||
} ?: LOG.debug("ack message expected")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java)
|
||||
}
|
||||
}
|
226
core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt
Normal file
226
core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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 userAgent: String,
|
||||
|
||||
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 {
|
||||
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 objectMessage = ObjectMessage(
|
||||
stream = recipient.stream,
|
||||
expiresTime = expires,
|
||||
payload = payload
|
||||
)
|
||||
if (objectMessage.isSigned) {
|
||||
objectMessage.sign(
|
||||
from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity")
|
||||
)
|
||||
}
|
||||
if (payload is Broadcast) {
|
||||
payload.encrypt()
|
||||
} else if (payload is Encrypted) {
|
||||
objectMessage.encrypt(
|
||||
recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available")
|
||||
)
|
||||
}
|
||||
proofOfWorkService.doProofOfWork(to, objectMessage)
|
||||
}
|
||||
|
||||
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 request = ObjectMessage(
|
||||
stream = contact.stream,
|
||||
expiresTime = expires,
|
||||
payload = GetPubkey(contact)
|
||||
)
|
||||
proofOfWorkService.doProofOfWork(request)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryToFindMatchingPubkey(address: BitmessageAddress) {
|
||||
addressRepository.getAddress(address.address)?.let {
|
||||
address.alias = it.alias
|
||||
address.isSubscribed = it.isSubscribed
|
||||
}
|
||||
for (objectMessage in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) {
|
||||
try {
|
||||
val pubkey = objectMessage.payload as Pubkey
|
||||
if (address.version == 4L) {
|
||||
val v4Pubkey = pubkey as V4Pubkey
|
||||
if (Arrays.equals(address.tag, v4Pubkey.tag)) {
|
||||
v4Pubkey.decrypt(address.publicDecryptionKey)
|
||||
if (objectMessage.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
|
||||
}
|
||||
}
|
121
core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt
Normal file
121
core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage
|
||||
|
||||
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.util.*
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class ProofOfWorkService : ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
|
||||
|
||||
private lateinit var ctx: InternalContext
|
||||
private val cryptography by lazy { ctx.cryptography }
|
||||
private val powRepo by lazy { ctx.proofOfWorkRepository }
|
||||
private val messageRepo by lazy { ctx.messageRepository }
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
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 (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
||||
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes,
|
||||
this@ProofOfWorkService)
|
||||
}
|
||||
}
|
||||
}, delayInMilliseconds)
|
||||
}
|
||||
|
||||
fun doProofOfWork(objectMessage: ObjectMessage) {
|
||||
doProofOfWork(null, objectMessage)
|
||||
}
|
||||
|
||||
fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) {
|
||||
val pubkey = recipient?.pubkey
|
||||
|
||||
val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES
|
||||
|
||||
powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||
if (objectMessage.payload is PlaintextHolder) {
|
||||
objectMessage.payload.plaintext?.let {
|
||||
it.initialHash = cryptography.getInitialHash(objectMessage)
|
||||
messageRepo.save(it)
|
||||
} ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW")
|
||||
}
|
||||
cryptography.doProofOfWork(objectMessage, 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 (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash)
|
||||
if (message == null) {
|
||||
objectMessage.nonce = nonce
|
||||
messageRepo.getMessage(initialHash)?.let {
|
||||
it.inventoryVector = objectMessage.inventoryVector
|
||||
it.updateNextTry()
|
||||
ctx.labeler.markAsSent(it)
|
||||
messageRepo.save(it)
|
||||
}
|
||||
ctx.inventory.storeObject(objectMessage)
|
||||
ctx.networkHandler.offer(objectMessage.inventoryVector)
|
||||
} else {
|
||||
message.ackMessage!!.nonce = nonce
|
||||
val newObjectMessage = ObjectMessage.Builder()
|
||||
.stream(message.stream)
|
||||
.expiresTime(expirationTime!!)
|
||||
.payload(Msg(message))
|
||||
.build()
|
||||
if (newObjectMessage.isSigned) {
|
||||
newObjectMessage.sign(message.from.privateKey!!)
|
||||
}
|
||||
if (newObjectMessage.payload is Encrypted) {
|
||||
newObjectMessage.encrypt(message.to!!.pubkey!!)
|
||||
}
|
||||
doProofOfWork(message.to, newObjectMessage)
|
||||
}
|
||||
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
|
||||
|
||||
/**
|
||||
* Some network constants
|
||||
*/
|
||||
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
|
||||
}
|
43
core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt
Normal file
43
core/src/main/kotlin/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, out)
|
||||
for (address in addresses) {
|
||||
address.write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(addresses.size, buffer)
|
||||
for (address in addresses) {
|
||||
address.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,19 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity;
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
|
||||
/**
|
||||
* Used for objects that have encrypted content
|
||||
*/
|
||||
public interface Encrypted {
|
||||
void encrypt(byte[] publicKey) throws IOException;
|
||||
interface Encrypted {
|
||||
fun encrypt(publicKey: ByteArray)
|
||||
|
||||
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(privateKey: ByteArray)
|
||||
|
||||
boolean isDecrypted();
|
||||
val isDecrypted: Boolean
|
||||
}
|
48
core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt
Normal file
48
core/src/main/kotlin/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, out)
|
||||
for (iv in inventory) {
|
||||
iv.write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(inventory.size, buffer)
|
||||
for (iv in inventory) {
|
||||
iv.write(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val MAX_INVENTORY_SIZE = 50000
|
||||
}
|
||||
}
|
44
core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt
Normal file
44
core/src/main/kotlin/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, out)
|
||||
for (iv in inventory) {
|
||||
iv.write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(inventory.size, 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,15 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity;
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
/**
|
||||
* A command can hold a network message payload
|
||||
*/
|
||||
public interface MessagePayload extends Streamable {
|
||||
Command getCommand();
|
||||
interface MessagePayload : Streamable {
|
||||
val command: Command
|
||||
|
||||
enum Command {
|
||||
enum class Command {
|
||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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])
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
// magic
|
||||
Encode.int32(MAGIC, 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, 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, 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, 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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
753
core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt
Normal file
753
core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt
Normal file
@ -0,0 +1,753 @@
|
||||
/*
|
||||
* 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.*
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
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
|
||||
|
||||
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
||||
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
||||
EXTENDED -> Message.Builder().subject(subject).body(body).build().zip()
|
||||
TRIVIAL -> (subject + body).toByteArray()
|
||||
IGNORE -> ByteArray(0)
|
||||
}
|
||||
|
||||
private fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? {
|
||||
if (ackData != null) {
|
||||
return ackData
|
||||
} else if (type == MSG) {
|
||||
return cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A plaintext message before encryption or after decryption.
|
||||
*/
|
||||
class Plaintext private constructor(
|
||||
val type: Type,
|
||||
val from: BitmessageAddress,
|
||||
to: BitmessageAddress?,
|
||||
val encodingCode: Long,
|
||||
val message: ByteArray,
|
||||
val ackData: ByteArray?,
|
||||
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
|
||||
val conversationId: UUID = UUID.randomUUID(),
|
||||
var inventoryVector: InventoryVector? = null,
|
||||
var signature: ByteArray? = null,
|
||||
sent: Long? = 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? = sent
|
||||
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? = null,
|
||||
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 = type,
|
||||
from = from,
|
||||
to = to,
|
||||
encodingCode = encoding.code,
|
||||
message = message,
|
||||
ackData = ackData(type, ackData),
|
||||
conversationId = conversationId,
|
||||
inventoryVector = inventoryVector,
|
||||
signature = signature,
|
||||
received = received,
|
||||
initialHash = initialHash,
|
||||
ttl = ttl,
|
||||
labels = labels,
|
||||
status = 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 = type,
|
||||
from = from,
|
||||
to = to,
|
||||
encodingCode = encoding,
|
||||
message = message,
|
||||
ackData = null,
|
||||
ackMessage = lazy {
|
||||
if (ackMessage != null && ackMessage.isNotEmpty()) {
|
||||
Factory.getObjectMessage(
|
||||
3,
|
||||
ByteArrayInputStream(ackMessage),
|
||||
ackMessage.size)
|
||||
} else null
|
||||
},
|
||||
conversationId = conversationId,
|
||||
inventoryVector = inventoryVector,
|
||||
signature = signature,
|
||||
received = received,
|
||||
initialHash = initialHash,
|
||||
ttl = ttl,
|
||||
labels = labels,
|
||||
status = status
|
||||
)
|
||||
|
||||
constructor(
|
||||
type: Type,
|
||||
from: BitmessageAddress,
|
||||
to: BitmessageAddress? = null,
|
||||
encoding: Encoding = SIMPLE,
|
||||
subject: String,
|
||||
body: String,
|
||||
ackData: ByteArray? = null,
|
||||
conversationId: UUID = UUID.randomUUID(),
|
||||
ttl: Long = TTL.msg,
|
||||
labels: MutableSet<Label> = HashSet(),
|
||||
status: Status = Status.DRAFT
|
||||
) : this(
|
||||
type = type,
|
||||
from = from,
|
||||
to = to,
|
||||
encoding = encoding,
|
||||
message = message(encoding, subject, body),
|
||||
ackData = ackData(type, ackData),
|
||||
conversationId = conversationId,
|
||||
inventoryVector = null,
|
||||
signature = null,
|
||||
received = null,
|
||||
initialHash = null,
|
||||
ttl = ttl,
|
||||
labels = labels,
|
||||
status = status
|
||||
)
|
||||
|
||||
constructor(builder: Builder) : this(
|
||||
type = builder.type,
|
||||
from = builder.from ?: throw IllegalStateException("sender identity not set"),
|
||||
to = builder.to,
|
||||
encodingCode = builder.encoding,
|
||||
message = builder.message,
|
||||
ackData = builder.ackData,
|
||||
ackMessage = 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)
|
||||
}
|
||||
},
|
||||
conversationId = builder.conversation ?: UUID.randomUUID(),
|
||||
inventoryVector = builder.inventoryVector,
|
||||
signature = builder.signature,
|
||||
sent = builder.sent,
|
||||
received = builder.received,
|
||||
initialHash = null,
|
||||
ttl = builder.ttl,
|
||||
labels = builder.labels,
|
||||
status = builder.status ?: Status.RECEIVED
|
||||
) {
|
||||
id = builder.id
|
||||
}
|
||||
|
||||
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, 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 == MSG) {
|
||||
out.write(to!!.ripe)
|
||||
}
|
||||
Encode.varInt(encodingCode, out)
|
||||
Encode.varInt(message.size, out)
|
||||
out.write(message)
|
||||
if (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.varBytes(signature!!, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, 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 == MSG) {
|
||||
buffer.put(to!!.ripe)
|
||||
}
|
||||
Encode.varInt(encodingCode, buffer)
|
||||
Encode.varBytes(message, buffer)
|
||||
if (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.varBytes(sig, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.removeAll { 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 (address != null) {
|
||||
if (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 != 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 != MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg")
|
||||
this.ackMessage = ack
|
||||
return this
|
||||
}
|
||||
|
||||
fun ackData(ackData: ByteArray?): Builder {
|
||||
if (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 == 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 == MSG) Decode.bytes(`in`, 20) else null)
|
||||
.encoding(Decode.varInt(`in`))
|
||||
.message(Decode.varBytes(`in`))
|
||||
.ackMessage(if (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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity;
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
public interface PlaintextHolder {
|
||||
Plaintext getPlaintext();
|
||||
interface PlaintextHolder {
|
||||
val plaintext: Plaintext?
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,30 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity;
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
||||
*/
|
||||
public class VerAck implements MessagePayload {
|
||||
private static final long serialVersionUID = -4302074845199181687L;
|
||||
class VerAck : MessagePayload {
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.VERACK;
|
||||
}
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
override fun write(out: OutputStream) {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val serialVersionUID = -4302074845199181687L
|
||||
}
|
||||
}
|
204
core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt
Normal file
204
core/src/main/kotlin/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,
|
||||
|
||||
/**
|
||||
* 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, 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, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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, 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, 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, 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, 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)
|
||||
|
||||
@JvmStatic 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
|
||||
return GenericPayload(version, stream, Decode.bytes(`is`, length))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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 val stream: Long
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
||||
return GetPubkey(version, stream, Decode.bytes(`is`, length))
|
||||
}
|
||||
}
|
||||
}
|
103
core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt
Normal file
103
core/src/main/kotlin/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
|
||||
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg {
|
||||
return Msg(stream, CryptoBox.read(`in`, length))
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,31 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload;
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
/**
|
||||
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
||||
*/
|
||||
public enum ObjectType {
|
||||
enum class ObjectType constructor(val number: Long) {
|
||||
GET_PUBKEY(0),
|
||||
PUBKEY(1),
|
||||
MSG(2),
|
||||
BROADCAST(3);
|
||||
|
||||
int number;
|
||||
|
||||
ObjectType(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public static ObjectType fromNumber(long number) {
|
||||
for (ObjectType type : values()) {
|
||||
if (type.number == number) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getNumber() {
|
||||
return number;
|
||||
companion object {
|
||||
@JvmStatic fun fromNumber(number: Long): ObjectType? {
|
||||
return values().firstOrNull { it.number == number }
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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, out)
|
||||
out.write(signingKey, 1, 64)
|
||||
out.write(encryptionKey, 1, 64)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(behaviorBitfield, 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 {
|
||||
@JvmStatic 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
@JvmStatic 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`)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
|
||||
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
@JvmStatic 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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
|
||||
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
|
||||
}
|
||||
}
|
||||
}
|
@ -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<*>>
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
|
||||
return InventoryVector(
|
||||
hash ?: return null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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? = null,
|
||||
/**
|
||||
* RGBA representation for the color.
|
||||
*/
|
||||
var color: Int = 0
|
||||
) : 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
|
||||
}
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.Socket
|
||||
import java.net.SocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
fun ip6(inetAddress: InetAddress): ByteArray {
|
||||
val address = inetAddress.address
|
||||
when (address.size) {
|
||||
16 -> {
|
||||
return address
|
||||
}
|
||||
4 -> {
|
||||
val ip6 = ByteArray(16)
|
||||
ip6[10] = 0xff.toByte()
|
||||
ip6[11] = 0xff.toByte()
|
||||
System.arraycopy(address, 0, ip6, 12, 4)
|
||||
return ip6
|
||||
}
|
||||
else -> throw IllegalArgumentException("Weird address " + inetAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node's address. It's written in IPv6 format.
|
||||
*/
|
||||
data class NetworkAddress(
|
||||
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 {
|
||||
|
||||
constructor(time: Long, stream: Long, services: Long = 1, socket: Socket)
|
||||
: this(time, stream, services, ip6(socket.inetAddress), socket.port)
|
||||
|
||||
constructor(time: Long, stream: Long, services: Long = 1, inetAddress: InetAddress, port: Int)
|
||||
: this(time, stream, services, ip6(inetAddress), port)
|
||||
|
||||
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, 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, 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 {
|
||||
ipv6 = ip6(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) {
|
||||
ip(address.address)
|
||||
port(address.port)
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown type of address: " + address.javaClass)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): NetworkAddress {
|
||||
return NetworkAddress(
|
||||
time ?: UnixTime.now, stream, services, ipv6!!, port
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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(), out)
|
||||
out.write(baos.toByteArray())
|
||||
Encode.varBytes(privateSigningKey, out)
|
||||
Encode.varBytes(privateEncryptionKey, out)
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user