Major refactoring

This commit is contained in:
2015-05-19 19:16:20 +02:00
parent b143225af5
commit 1fbc4a1d74
45 changed files with 1447 additions and 444 deletions

View File

@ -16,8 +16,16 @@
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.Plaintext.Encoding;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
@ -26,115 +34,126 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* Created by chris on 05.04.15.
*/
public class BitmessageContext {
public static final int CURRENT_VERSION = 3;
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
private final Inventory inventory;
private final NodeRegistry nodeRegistry;
private final NetworkHandler networkHandler;
private final AddressRepository addressRepo;
private final ProofOfWorkEngine proofOfWorkEngine;
private final TreeSet<Long> streams;
private final int port;
private long networkNonceTrialsPerByte = 1000;
private long networkExtraBytes = 1000;
private final InternalContext ctx;
private BitmessageContext(Builder builder) {
port = builder.port;
inventory = builder.inventory;
nodeRegistry = builder.nodeRegistry;
networkHandler = builder.networkHandler;
addressRepo = builder.addressRepo;
proofOfWorkEngine = builder.proofOfWorkEngine;
streams = builder.streams;
init(inventory, nodeRegistry, networkHandler, addressRepo, proofOfWorkEngine);
ctx = new InternalContext(builder);
}
private void init(Object... objects) {
for (Object o : objects) {
if (o instanceof ContextHolder) {
((ContextHolder) o).setContext(this);
}
public List<BitmessageAddress> getIdentities() {
return ctx.getAddressRepo().getIdentities();
}
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
shorter,
ctx.getStreams()[0],
ctx.getNetworkNonceTrialsPerByte(),
ctx.getNetworkExtraBytes(),
features
));
ctx.getAddressRepo().save(identity);
return identity;
}
public void addDistributedMailingList(String address, String alias) {
// TODO
}
public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) {
if (from.getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
}
}
public void send(long stream, long version, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) throws IOException {
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
ObjectMessage object = new ObjectMessage.Builder()
.stream(stream)
.version(version)
.expiresTime(expires)
.payload(payload)
Plaintext msg = new Plaintext.Builder()
.from(from)
.to(to)
.encoding(Encoding.SIMPLE)
.message(subject, message)
.build();
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
inventory.storeObject(object);
networkHandler.offer(object.getInventoryVector());
}
public Inventory getInventory() {
return inventory;
}
public NodeRegistry getAddressRepository() {
return nodeRegistry;
}
public NetworkHandler getNetworkHandler() {
return networkHandler;
}
public int getPort() {
return port;
}
public long[] getStreams() {
long[] result = new long[streams.size()];
int i = 0;
for (long stream : streams) {
result[i++] = stream;
if (to.getPubkey() == null) {
requestPubkey(from, to);
msg.setStatus(PUBKEY_REQUESTED);
ctx.getMessageRepository().save(msg);
} else {
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
from,
to,
new Msg(msg),
+2 * DAY,
ctx.getNonceTrialsPerByte(to),
ctx.getExtraBytes(to)
);
msg.setStatus(SENT);
ctx.getMessageRepository().save(msg);
}
return result;
}
public void addStream(long stream) {
streams.add(stream);
private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
ctx.send(
requestingIdentity,
address,
new GetPubkey(address),
+28 * DAY,
ctx.getNetworkNonceTrialsPerByte(),
ctx.getNetworkExtraBytes()
);
}
public void removeStream(long stream) {
streams.remove(stream);
private void send(long stream, long version, ObjectPayload payload, long timeToLive) {
try {
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
ObjectMessage object = new ObjectMessage.Builder()
.stream(stream)
.version(version)
.expiresTime(expires)
.payload(payload)
.build();
Security.doProofOfWork(object, ctx.getProofOfWorkEngine(),
ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
ctx.getInventory().storeObject(object);
ctx.getNetworkHandler().offer(object.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public long getNetworkNonceTrialsPerByte() {
return networkNonceTrialsPerByte;
public void startup(Listener listener) {
ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
}
public long getNetworkExtraBytes() {
return networkExtraBytes;
public void shutdown() {
ctx.getNetworkHandler().stop();
}
public interface ContextHolder {
void setContext(BitmessageContext context);
public interface Listener {
void receive(Plaintext plaintext);
}
public static final class Builder {
private int port = 8444;
private Inventory inventory;
private NodeRegistry nodeRegistry;
private NetworkHandler networkHandler;
private AddressRepository addressRepo;
private ProofOfWorkEngine proofOfWorkEngine;
private TreeSet<Long> streams;
int port = 8444;
Inventory inventory;
NodeRegistry nodeRegistry;
NetworkHandler networkHandler;
AddressRepository addressRepo;
MessageRepository messageRepo;
ProofOfWorkEngine proofOfWorkEngine;
TreeSet<Long> streams;
public Builder() {
}
@ -164,6 +183,11 @@ public class BitmessageContext {
return this;
}
public Builder messageRepo(MessageRepository messageRepo) {
this.messageRepo = messageRepo;
return this;
}
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;
@ -187,6 +211,7 @@ public class BitmessageContext {
nonNull("nodeRegistry", nodeRegistry);
nonNull("networkHandler", networkHandler);
nonNull("addressRepo", addressRepo);
nonNull("messageRepo", messageRepo);
if (streams == null) {
streams(1);
}
@ -200,4 +225,5 @@ public class BitmessageContext {
if (o == null) throw new IllegalStateException(name + " must not be null");
}
}
}

View File

@ -0,0 +1,160 @@
/*
* 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.Encrypted;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Status.DOING_PROOF_OF_WORK;
import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
class DefaultMessageListener implements NetworkHandler.MessageListener {
private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
private final InternalContext ctx;
private final BitmessageContext.Listener listener;
public DefaultMessageListener(InternalContext context, BitmessageContext.Listener listener) {
this.ctx = context;
this.listener = listener;
}
@Override
public void receive(ObjectMessage object) {
ObjectPayload payload = object.getPayload();
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;
}
}
}
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null) {
try {
long expires = UnixTime.now(+28 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(object.getStream())
.version(identity.getVersion())
.expiresTime(expires)
.payload(identity.getPubkey())
.build();
Security.doProofOfWork(response, ctx.getProofOfWorkEngine(),
ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false));
}
ctx.getInventory().storeObject(response);
ctx.getNetworkHandler().offer(response.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
protected void receive(ObjectMessage object, Pubkey pubkey) {
BitmessageAddress address;
try {
if (pubkey instanceof V4Pubkey) {
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
address = ctx.getAddressRepo().findContact(v4Pubkey.getTag());
if (address != null) {
v4Pubkey.decrypt(address.getPubkeyDecryptionKey());
}
} else {
address = ctx.getAddressRepo().findContact(pubkey.getRipe());
}
if (address != null) {
address.setPubkey(pubkey);
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
for (Plaintext msg:messages){
// TODO: send messages enqueued for this address
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
msg.getFrom(),
msg.getTo(),
new Msg(msg),
+2 * DAY,
ctx.getNonceTrialsPerByte(msg.getTo()),
ctx.getExtraBytes(msg.getTo())
);
msg.setStatus(SENT);
ctx.getMessageRepository().save(msg);
}
}
} catch (IllegalArgumentException | IOException e) {
LOG.debug(e.getMessage(), e);
}
}
protected void receive(ObjectMessage object, Msg msg) {
for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) {
try {
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
msg.getPlaintext().setTo(identity);
object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey());
ctx.getMessageRepository().save(msg.getPlaintext());
listener.receive(msg.getPlaintext());
break;
} catch (IOException ignore) {
}
}
}
protected void receive(ObjectMessage object, Broadcast broadcast) {
// TODO this should work fine as-is, but checking the tag might be more efficient
// V5Broadcast v5 = broadcast instanceof V5Broadcast ? (V5Broadcast) broadcast : null;
for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions()) {
try {
broadcast.decrypt(subscription.getPubkeyDecryptionKey());
object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey());
listener.receive(broadcast.getPlaintext());
} catch (IOException ignore) {
}
}
}
}

View File

@ -0,0 +1,166 @@
/*
* 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.Encrypted;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
/**
* 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.
*/
public class InternalContext {
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
private final Inventory inventory;
private final NodeRegistry nodeRegistry;
private final NetworkHandler networkHandler;
private final AddressRepository addressRepository;
private final MessageRepository messageRepository;
private final ProofOfWorkEngine proofOfWorkEngine;
private final TreeSet<Long> streams;
private final int port;
private long networkNonceTrialsPerByte = 1000;
private long networkExtraBytes = 1000;
public InternalContext(BitmessageContext.Builder builder) {
this.inventory = builder.inventory;
this.nodeRegistry = builder.nodeRegistry;
this.networkHandler = builder.networkHandler;
this.addressRepository = builder.addressRepo;
this.messageRepository = builder.messageRepo;
this.proofOfWorkEngine = builder.proofOfWorkEngine;
port = builder.port;
streams = builder.streams;
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
}
private void init(Object... objects) {
for (Object o : objects) {
if (o instanceof ContextHolder) {
((ContextHolder) o).setContext(this);
}
}
}
public Inventory getInventory() {
return inventory;
}
public NodeRegistry getNodeRegistry() {
return nodeRegistry;
}
public NetworkHandler getNetworkHandler() {
return networkHandler;
}
public AddressRepository getAddressRepo() {
return addressRepository;
}
public MessageRepository getMessageRepository() {
return messageRepository;
}
public ProofOfWorkEngine getProofOfWorkEngine() {
return proofOfWorkEngine;
}
public long[] getStreams() {
long[] result = new long[streams.size()];
int i = 0;
for (long stream : streams) {
result[i++] = stream;
}
return result;
}
public void addStream(long stream) {
streams.add(stream);
}
public void removeStream(long stream) {
streams.remove(stream);
}
public int getPort() {
return port;
}
public long getNetworkNonceTrialsPerByte() {
return networkNonceTrialsPerByte;
}
public long getNonceTrialsPerByte(BitmessageAddress address) {
long nonceTrialsPerByte = address.getPubkey().getNonceTrialsPerByte();
return networkNonceTrialsPerByte > nonceTrialsPerByte ? networkNonceTrialsPerByte : nonceTrialsPerByte;
}
public long getNetworkExtraBytes() {
return networkExtraBytes;
}
public long getExtraBytes(BitmessageAddress address) {
long extraBytes = address.getPubkey().getExtraBytes();
return networkExtraBytes > extraBytes ? networkExtraBytes : extraBytes;
}
public void send(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) {
try {
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
ObjectMessage object = new ObjectMessage.Builder()
.stream(to.getStream())
.version(to.getVersion())
.expiresTime(expires)
.payload(payload)
.build();
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
if (object.isSigned()) {
object.sign(from.getPrivateKey());
}
if (object instanceof Encrypted) {
object.encrypt(to.getPubkey());
}
inventory.storeObject(object);
networkHandler.offer(object.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public interface ContextHolder {
void setContext(InternalContext context);
}
}

View File

@ -49,8 +49,9 @@ public class BitmessageAddress {
private Pubkey pubkey;
private String alias;
private boolean subscribed;
private BitmessageAddress(long version, long stream, byte[] ripe) {
BitmessageAddress(long version, long stream, byte[] ripe) {
try {
this.version = version;
this.stream = stream;
@ -74,7 +75,7 @@ public class BitmessageAddress {
}
}
private BitmessageAddress(Pubkey publicKey) {
BitmessageAddress(Pubkey publicKey) {
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
this.pubkey = publicKey;
}
@ -163,6 +164,10 @@ public class BitmessageAddress {
this.alias = alias;
}
public void setSubscribed(boolean subscribed) {
this.subscribed = subscribed;
}
@Override
public String toString() {
return alias != null ? alias : address;

View File

@ -19,7 +19,7 @@ package ch.dissem.bitmessage.entity;
import java.io.IOException;
/**
* Created by chris on 12.05.15.
* Used for objects that have encrypted content
*/
public interface Encrypted {
void encrypt(byte[] publicKey) throws IOException;

View File

@ -128,15 +128,19 @@ public class ObjectMessage implements MessagePayload {
}
}
public void encrypt(byte[] publicEncryptionKey) throws IOException{
if (payload instanceof Encrypted){
public void encrypt(byte[] publicEncryptionKey) throws IOException {
if (payload instanceof Encrypted) {
((Encrypted) payload).encrypt(publicEncryptionKey);
}
}
public void encrypt(Pubkey publicKey) throws IOException{
if (payload instanceof Encrypted){
((Encrypted) payload).encrypt(publicKey.getEncryptionKey());
public void encrypt(Pubkey publicKey) {
try {
if (payload instanceof Encrypted) {
((Encrypted) payload).encrypt(publicKey.getEncryptionKey());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

View File

@ -0,0 +1,352 @@
/*
* 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.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
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.io.UnsupportedEncodingException;
import java.util.*;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
public class Plaintext implements Streamable {
private final BitmessageAddress from;
private final long encoding;
private final byte[] message;
private final byte[] ack;
private Object id;
private BitmessageAddress to;
private byte[] signature;
private Status status;
private Long sent;
private Long received;
private Set<Label> labels;
private Plaintext(Builder builder) {
id = builder.id;
from = builder.from;
to = builder.to;
encoding = builder.encoding;
message = builder.message;
ack = builder.ack;
signature = builder.signature;
status = builder.status;
sent = builder.sent;
received = builder.received;
labels = builder.labels;
}
public static Plaintext read(InputStream in) throws IOException {
return readWithoutSignature(in)
.signature(Decode.varBytes(in))
.build();
}
public static Plaintext.Builder readWithoutSignature(InputStream in) throws IOException {
return new Builder()
.addressVersion(Decode.varInt(in))
.stream(Decode.varInt(in))
.behaviorBitfield(Decode.int32(in))
.publicSigningKey(Decode.bytes(in, 64))
.publicEncryptionKey(Decode.bytes(in, 64))
.nonceTrialsPerByte(Decode.varInt(in))
.extraBytes(Decode.varInt(in))
.destinationRipe(Decode.bytes(in, 20))
.encoding(Decode.varInt(in))
.message(Decode.varBytes(in))
.ack(Decode.varBytes(in));
}
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 RuntimeException("Correct address already set");
if (Arrays.equals(this.to.getRipe(), to.getRipe())) {
throw new RuntimeException("RIPEs don't match");
}
this.to = to;
}
public Set<Label> getLabels() {
return labels;
}
public long getStream() {
return from.getStream();
}
public byte[] getSignature() {
return signature;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public void write(OutputStream out, boolean includeSignature) throws IOException {
Encode.varInt(from.getVersion(), out);
Encode.varInt(from.getStream(), out);
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
out.write(from.getPubkey().getSigningKey());
out.write(from.getPubkey().getEncryptionKey());
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
Encode.varInt(from.getPubkey().getExtraBytes(), out);
out.write(to.getRipe());
Encode.varInt(encoding, out);
Encode.varInt(message.length, out);
out.write(message);
Encode.varInt(ack.length, out);
out.write(ack);
if (includeSignature) {
Encode.varInt(signature.length, out);
out.write(signature);
}
}
@Override
public void write(OutputStream out) throws IOException {
write(out, 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) {
this.status = status;
}
public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2);
long code;
Encoding(long code) {
this.code = code;
}
public static Encoding fromCode(long code) {
for (Encoding e : values()) {
if (e.getCode() == code) return e;
}
return null;
}
public long getCode() {
return code;
}
}
public enum Status {
PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK,
SENT,
ACKNOWLEDGED
}
public static final class Builder {
private Object id;
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[] ack = new byte[0];
private byte[] signature;
private long sent;
private long received;
private Status status;
private Set<Label> labels = new TreeSet<>();
public Builder() {
}
public Builder id(Object id) {
this.id = id;
return this;
}
public Builder from(BitmessageAddress address) {
from = address;
return this;
}
public Builder to(BitmessageAddress address) {
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) {
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(String subject, String message) {
try {
this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return this;
}
public Builder message(byte[] message) {
this.message = message;
return this;
}
public Builder ack(byte[] ack) {
this.ack = ack;
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 Plaintext build() {
if (id == null) {
id = UUID.randomUUID();
}
if (from == null) {
from = new BitmessageAddress(Factory.createPubkey(
addressVersion,
stream,
publicSigningKey,
publicEncryptionKey,
nonceTrialsPerByte,
extraBytes,
Pubkey.Feature.features(behaviorBitfield)
));
}
if (to == null) {
to = new BitmessageAddress(0, 0, destinationRipe);
}
return new Plaintext(this);
}
}
}

View File

@ -16,10 +16,47 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import java.io.IOException;
/**
* 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 {
public abstract byte[] getEncrypted();
public abstract class Broadcast extends ObjectPayload implements Encrypted {
protected final long stream;
protected CryptoBox encrypted;
protected Plaintext plaintext;
protected Broadcast(long stream, CryptoBox encrypted, Plaintext plaintext) {
this.stream = stream;
this.encrypted = encrypted;
this.plaintext = plaintext;
}
@Override
public long getStream() {
return stream;
}
public Plaintext getPlaintext() {
return plaintext;
}
@Override
public void encrypt(byte[] publicKey) throws IOException {
this.encrypted = new CryptoBox(plaintext, publicKey);
}
@Override
public void decrypt(byte[] privateKey) throws IOException {
plaintext = Plaintext.read(encrypted.decrypt(privateKey));
}
@Override
public boolean isDecrypted() {
return plaintext != null;
}
}

View File

@ -17,9 +17,7 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Security;
import java.io.IOException;
import java.io.InputStream;
@ -30,30 +28,33 @@ import java.io.OutputStream;
*/
public class GetPubkey extends ObjectPayload {
private long stream;
private byte[] ripe;
private byte[] tag;
private byte[] ripeTag;
public GetPubkey(BitmessageAddress address) {
this.stream = address.getStream();
if (address.getVersion() < 4)
this.ripe = address.getRipe();
this.ripeTag = address.getRipe();
else
this.tag = address.getTag();
this.ripeTag = address.getTag();
}
private GetPubkey(long stream, long version, byte[] ripeOrTag) {
this.stream = stream;
if (version < 4) {
ripe = ripeOrTag;
} else {
tag = ripeOrTag;
}
this.ripeTag = ripeOrTag;
}
public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException {
return new GetPubkey(stream, version, Decode.bytes(is, length));
}
/**
* Returns 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;
@ -66,10 +67,6 @@ public class GetPubkey extends ObjectPayload {
@Override
public void write(OutputStream stream) throws IOException {
if (tag != null) {
stream.write(tag);
} else {
stream.write(ripe);
}
stream.write(ripeTag);
}
}

View File

@ -16,6 +16,9 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -23,26 +26,29 @@ import java.io.OutputStream;
/**
* Used for person-to-person messages.
*/
public class Msg extends ObjectPayload {
public class Msg extends ObjectPayload implements Encrypted {
private long stream;
private CryptoBox encrypted;
private UnencryptedMessage decrypted;
private Plaintext plaintext;
private Msg(long stream, CryptoBox encrypted) {
this.stream = stream;
this.encrypted = encrypted;
}
public Msg(UnencryptedMessage unencrypted, Pubkey publicKey) {
this.stream = unencrypted.getStream();
this.decrypted = unencrypted;
this.encrypted = new CryptoBox(unencrypted, publicKey.getEncryptionKey());
public Msg(Plaintext plaintext) {
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));
}
public Plaintext getPlaintext() {
return plaintext;
}
@Override
public ObjectType getType() {
return ObjectType.MSG;
@ -55,26 +61,37 @@ public class Msg extends ObjectPayload {
@Override
public boolean isSigned() {
return decrypted != null;
return true;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
decrypted.write(out, false);
plaintext.write(out, false);
}
@Override
public byte[] getSignature() {
return decrypted.getSignature();
return plaintext.getSignature();
}
@Override
public void setSignature(byte[] signature) {
decrypted.setSignature(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 {
decrypted = UnencryptedMessage.read(encrypted.decrypt(privateKey));
plaintext = Plaintext.read(encrypted.decrypt(privateKey));
}
@Override
public boolean isDecrypted() {
return plaintext != null;
}
@Override

View File

@ -37,10 +37,20 @@ public abstract class Pubkey extends ObjectPayload {
public abstract byte[] getEncryptionKey();
public abstract int getBehaviorBitfield();
public byte[] getRipe() {
return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
}
public long getNonceTrialsPerByte() {
return 0;
}
public long getExtraBytes() {
return 0;
}
protected byte[] add0x04(byte[] key) {
if (key.length == 65) return key;
byte[] result = new byte[65];

View File

@ -1,173 +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.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
public class UnencryptedMessage implements Streamable {
private final long addressVersion;
private final long stream;
private final int behaviorBitfield;
private final byte[] publicSigningKey;
private final byte[] publicEncryptionKey;
private final long nonceTrialsPerByte;
private final long extraBytes;
private final long encoding;
private final byte[] message;
private byte[] signature;
private UnencryptedMessage(Builder builder) {
addressVersion = builder.addressVersion;
stream = builder.stream;
behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = builder.publicSigningKey;
publicEncryptionKey = builder.publicEncryptionKey;
nonceTrialsPerByte = builder.nonceTrialsPerByte;
extraBytes = builder.extraBytes;
encoding = builder.encoding;
message = builder.message;
signature = builder.signature;
}
public static UnencryptedMessage read(InputStream is) throws IOException {
return new Builder()
.addressVersion(Decode.varInt(is))
.stream(Decode.varInt(is))
.behaviorBitfield(Decode.int32(is))
.publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64))
.nonceTrialsPerByte(Decode.varInt(is))
.extraBytes(Decode.varInt(is))
.encoding(Decode.varInt(is))
.message(Decode.varBytes(is))
.signature(Decode.varBytes(is))
.build();
}
public long getStream() {
return stream;
}
public byte[] getSignature() {
return signature;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public void write(OutputStream out, boolean includeSignature) throws IOException {
Encode.varInt(addressVersion, out);
Encode.varInt(stream, out);
Encode.int32(behaviorBitfield, out);
out.write(publicSigningKey);
out.write(publicEncryptionKey);
Encode.varInt(nonceTrialsPerByte, out);
Encode.varInt(extraBytes, out);
Encode.varInt(encoding, out);
Encode.varInt(message.length, out);
out.write(message);
if (includeSignature) {
Encode.varInt(signature.length, out);
out.write(signature);
}
}
@Override
public void write(OutputStream out) throws IOException {
write(out, true);
}
public static final class Builder {
private long addressVersion;
private long stream;
private int behaviorBitfield;
private byte[] publicSigningKey;
private byte[] publicEncryptionKey;
private long nonceTrialsPerByte;
private long extraBytes;
private long encoding;
private byte[] message;
private byte[] signature;
public Builder() {
}
public Builder addressVersion(long addressVersion) {
this.addressVersion = addressVersion;
return this;
}
public Builder stream(long stream) {
this.stream = stream;
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 encoding(long encoding) {
this.encoding = encoding;
return this;
}
public Builder message(byte[] message) {
this.message = message;
return this;
}
public Builder signature(byte[] signature) {
this.signature = signature;
return this;
}
public UnencryptedMessage build() {
return new UnencryptedMessage(this);
}
}
}

View File

@ -76,6 +76,11 @@ public class V2Pubkey extends Pubkey {
return publicEncryptionKey;
}
@Override
public int getBehaviorBitfield() {
return behaviorBitfield;
}
@Override
public void write(OutputStream os) throws IOException {
Encode.int32(behaviorBitfield, os);

View File

@ -18,7 +18,6 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import java.io.IOException;
import java.io.InputStream;
@ -66,6 +65,14 @@ public class V3Pubkey extends V2Pubkey {
return 3;
}
public long getNonceTrialsPerByte() {
return nonceTrialsPerByte;
}
public long getExtraBytes() {
return extraBytes;
}
public boolean isSigned() {
return true;
}

View File

@ -16,8 +16,6 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -27,17 +25,12 @@ import java.io.OutputStream;
* Broadcasts are version 4 or 5.
*/
public class V4Broadcast extends Broadcast {
private long stream;
private byte[] encrypted;
private UnencryptedMessage unencrypted;
protected V4Broadcast(long stream, byte[] encrypted) {
this.stream = stream;
this.encrypted = encrypted;
protected V4Broadcast(long stream, CryptoBox encrypted) {
super(stream, encrypted, null);
}
public static V4Broadcast read(InputStream is, long stream, int length) throws IOException {
return new V4Broadcast(stream, Decode.bytes(is, length));
public static V4Broadcast read(InputStream in, long stream, int length) throws IOException {
return new V4Broadcast(stream, CryptoBox.read(in, length));
}
@Override
@ -45,32 +38,23 @@ public class V4Broadcast extends Broadcast {
return ObjectType.BROADCAST;
}
@Override
public long getStream() {
return stream;
}
public byte[] getEncrypted() {
return encrypted;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
unencrypted.write(out, false);
plaintext.write(out, false);
}
@Override
public byte[] getSignature() {
return unencrypted.getSignature();
return plaintext.getSignature();
}
@Override
public void setSignature(byte[] signature) {
unencrypted.setSignature(signature);
plaintext.setSignature(signature);
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(getEncrypted());
public void write(OutputStream out) throws IOException {
encrypted.write(out);
}
}

View File

@ -111,6 +111,11 @@ public class V4Pubkey extends Pubkey implements Encrypted {
return decrypted.getEncryptionKey();
}
@Override
public int getBehaviorBitfield() {
return decrypted.getBehaviorBitfield();
}
@Override
public byte[] getSignature() {
if (decrypted != null)
@ -128,4 +133,12 @@ public class V4Pubkey extends Pubkey implements Encrypted {
public boolean isSigned() {
return true;
}
public long getNonceTrialsPerByte() {
return decrypted.getNonceTrialsPerByte();
}
public long getExtraBytes() {
return decrypted.getExtraBytes();
}
}

View File

@ -28,13 +28,13 @@ import java.io.OutputStream;
public class V5Broadcast extends V4Broadcast {
private byte[] tag;
private V5Broadcast(long stream, byte[] tag, byte[] encrypted) {
private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) {
super(stream, encrypted);
this.tag = tag;
}
public static V5Broadcast read(InputStream is, long stream, int length) throws IOException {
return new V5Broadcast(stream, Decode.bytes(is, 32), Decode.bytes(is, length - 32));
return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32));
}
public byte[] getTag() {
@ -42,8 +42,8 @@ public class V5Broadcast extends V4Broadcast {
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(tag);
super.write(stream);
public void write(OutputStream out) throws IOException {
out.write(tag);
super.write(out);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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;
public class Label {
private Object id;
private String label;
private int color;
public Label(String label, int color) {
this.label = label;
this.color = color;
}
/**
* RGBA representation for the color.
*/
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
@Override
public String toString() {
return label;
}
public Object getId() {
return id;
}
}

View File

@ -20,21 +20,25 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import java.util.List;
/**
* Created by chris on 23.04.15.
*/
public interface AddressRepository {
BitmessageAddress findContact(byte[] ripeOrTag);
BitmessageAddress findIdentity(byte[] ripeOrTag);
/**
* Returns all Bitmessage addresses that belong to this user, i.e. have a private key.
*/
List<BitmessageAddress> findIdentities();
List<BitmessageAddress> getIdentities();
List<BitmessageAddress> getSubscriptions();
/**
* Returns all Bitmessage addresses that have no private key.
*/
List<BitmessageAddress> findContacts();
List<BitmessageAddress> getContacts();
void save(BitmessageAddress address);
void remove(BitmessageAddress address);
BitmessageAddress getAddress(String address);
}

View File

@ -0,0 +1,38 @@
/*
* 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.Label;
import java.util.List;
public interface MessageRepository {
List<String> getLabels();
List<Plaintext> findMessages(Label label);
List<Plaintext> findMessages(Status status);
List<Plaintext> findMessages(Status status, BitmessageAddress recipient);
void save(Plaintext message);
void remove(Plaintext message);
}

View File

@ -28,7 +28,7 @@ import java.util.List;
import static ch.dissem.bitmessage.utils.Bytes.inc;
/**
* Created by chris on 14.04.15.
* A POW engine using all available CPU cores.
*/
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
private static Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
@ -31,6 +32,6 @@ public interface NetworkHandler {
void offer(InventoryVector iv);
interface MessageListener {
void receive(ObjectPayload payload);
void receive(ObjectMessage object);
}
}

View File

@ -89,16 +89,20 @@ public class Security {
return result;
}
public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte, long extraBytes) throws IOException {
if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
if (extraBytes < 1000) extraBytes = 1000;
public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte, long extraBytes) {
try {
if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
if (extraBytes < 1000) extraBytes = 1000;
byte[] initialHash = getInitialHash(object);
byte[] initialHash = getInitialHash(object);
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] nonce = worker.calculateNonce(initialHash, target);
object.setNonce(nonce);
byte[] nonce = worker.calculateNonce(initialHash, target);
object.setNonce(nonce);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**

View File

@ -20,6 +20,8 @@ package ch.dissem.bitmessage.utils;
* Created by chris on 18.04.15.
*/
public class UnixTime {
public static final long DAY = 60 * 60 * 24;
/**
* Returns the time in second based Unix time ({@link System#currentTimeMillis()}/1000)
*/