Merge branch 'feature/nio' into develop

This commit is contained in:
Christian Basler 2016-09-21 19:38:48 +02:00
commit 1003e7a582
67 changed files with 2455 additions and 922 deletions

7
.editorconfig Normal file
View File

@ -0,0 +1,7 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_size = 4

View File

@ -37,7 +37,11 @@ class GitFlowVersion implements Plugin<Project> {
if (project.ext.isRelease) {
return getTag(project)
} else {
return getBranch(project).replaceAll("/", "-") + "-SNAPSHOT"
def branch = getBranch(project)
if ("develop" == branch) {
return "development-SNAPSHOT"
}
return branch.replaceAll("/", "-") + "-SNAPSHOT"
}
}

View File

@ -25,7 +25,7 @@ artifacts {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile 'junit:junit:4.11'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(':cryptography-bc')

View File

@ -62,16 +62,16 @@ public class BitmessageContext {
private final InternalContext ctx;
private final Labeler labeler;
private final NetworkHandler.MessageListener networkListener;
private final boolean sendPubkeyOnIdentityCreation;
private BitmessageContext(Builder builder) {
if (builder.listener instanceof Listener.WithContext) {
((Listener.WithContext) builder.listener).setContext(this);
}
ctx = new InternalContext(builder);
labeler = builder.labeler;
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
networkListener = new DefaultMessageListener(ctx, labeler, builder.listener);
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
}
@ -89,11 +89,11 @@ public class BitmessageContext {
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
shorter,
ctx.getStreams()[0],
NETWORK_NONCE_TRIALS_PER_BYTE,
NETWORK_EXTRA_BYTES,
features
));
ctx.getAddressRepository().save(identity);
if (sendPubkeyOnIdentityCreation) {
@ -117,9 +117,9 @@ public class BitmessageContext {
}
public List<BitmessageAddress> createDeterministicAddresses(
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
List<BitmessageAddress> result = BitmessageAddress.deterministic(
passphrase, numberOfAddresses, version, stream, shorter);
passphrase, numberOfAddresses, version, stream, shorter);
for (int i = 0; i < result.size(); i++) {
BitmessageAddress address = result.get(i);
address.setAlias("deterministic (" + (i + 1) + ")");
@ -130,9 +130,9 @@ public class BitmessageContext {
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
Plaintext msg = new Plaintext.Builder(BROADCAST)
.from(from)
.message(subject, message)
.build();
.from(from)
.message(subject, message)
.build();
send(msg);
}
@ -141,10 +141,10 @@ public class BitmessageContext {
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();
.from(from)
.to(to)
.message(subject, message)
.build();
send(msg);
}
@ -170,17 +170,17 @@ public class BitmessageContext {
ctx.send(msg);
} else {
ctx.send(
msg.getFrom(),
to,
Factory.getBroadcast(msg),
msg.getTTL()
msg.getFrom(),
to,
Factory.getBroadcast(msg),
msg.getTTL()
);
}
}
}
public void startup() {
ctx.getNetworkHandler().start(networkListener);
ctx.getNetworkHandler().start();
}
public void shutdown() {
@ -195,7 +195,7 @@ public class BitmessageContext {
* @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, networkListener, timeoutInSeconds);
Future<?> future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds);
if (wait) {
try {
future.get();
@ -271,7 +271,7 @@ public class BitmessageContext {
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.
networkListener.receive(object);
ctx.getNetworkListener().receive(object);
} catch (DecryptionFailedException ignore) {
} catch (Exception e) {
LOG.debug(e.getMessage(), e);
@ -281,8 +281,8 @@ public class BitmessageContext {
public Property status() {
return new Property("status", null,
ctx.getNetworkHandler().getNetworkStatus(),
new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
ctx.getNetworkHandler().getNetworkStatus(),
new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
);
}
@ -296,6 +296,13 @@ public class BitmessageContext {
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 {
@ -429,7 +436,7 @@ public class BitmessageContext {
@Override
public MessagePayload handle(CustomMessage request) {
throw new IllegalStateException(
"Received custom request, but no custom command handler configured.");
"Received custom request, but no custom command handler configured.");
}
};
}

View File

@ -24,7 +24,6 @@ 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.TTL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,21 +31,24 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED;
class DefaultMessageListener implements NetworkHandler.MessageListener {
class DefaultMessageListener implements NetworkHandler.MessageListener, InternalContext.ContextHolder {
private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
private final InternalContext ctx;
private final Labeler labeler;
private final BitmessageContext.Listener listener;
private InternalContext ctx;
public DefaultMessageListener(InternalContext context, Labeler labeler, BitmessageContext.Listener listener) {
this.ctx = context;
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 {

View File

@ -56,6 +56,7 @@ public class InternalContext {
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;
@ -79,6 +80,7 @@ public class InternalContext {
this.connectionLimit = builder.connectionLimit;
this.connectionTTL = builder.connectionTTL;
this.labeler = builder.labeler;
this.networkListener = new DefaultMessageListener(labeler, builder.listener);
Singleton.initialize(cryptography);
@ -94,7 +96,8 @@ public class InternalContext {
}
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler);
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler,
networkListener);
for (BitmessageAddress identity : addressRepository.getIdentities()) {
streams.add(identity.getStream());
}
@ -148,6 +151,10 @@ public class InternalContext {
return labeler;
}
public NetworkHandler.MessageListener getNetworkListener() {
return networkListener;
}
public long[] getStreams() {
long[] result = new long[streams.size()];
int i = 0;
@ -178,10 +185,10 @@ public class InternalContext {
long expires = UnixTime.now(+timeToLive);
LOG.info("Expires at " + expires);
final ObjectMessage object = new ObjectMessage.Builder()
.stream(recipient.getStream())
.expiresTime(expires)
.payload(payload)
.build();
.stream(recipient.getStream())
.expiresTime(expires)
.payload(payload)
.build();
if (object.isSigned()) {
object.sign(from.getPrivateKey());
}
@ -201,10 +208,10 @@ public class InternalContext {
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();
.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!
@ -239,10 +246,10 @@ public class InternalContext {
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();
.stream(contact.getStream())
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
proofOfWorkService.doProofOfWork(request);
}

View File

@ -11,6 +11,7 @@ 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;
@ -42,7 +43,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
for (byte[] initialHash : items) {
Item item = powRepo.getItem(initialHash);
cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes,
ProofOfWorkService.this);
ProofOfWorkService.this);
}
}
}, delayInMilliseconds);
@ -71,7 +72,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
final ObjectMessage ack = plaintext.getAckMessage();
messageRepo.save(plaintext);
Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES,
expirationTime, plaintext);
expirationTime, plaintext);
powRepo.putObject(item);
cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this);
}
@ -89,15 +90,20 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
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();
.stream(item.message.getStream())
.expiresTime(item.expirationTime)
.payload(new Msg(item.message))
.build();
if (object.isSigned()) {
object.sign(item.message.getFrom().getPrivateKey());
}

View File

@ -21,6 +21,7 @@ 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;
@ -47,10 +48,18 @@ public class Addr implements MessagePayload {
}
@Override
public void write(OutputStream stream) throws IOException {
Encode.varInt(addresses.size(), stream);
public void write(OutputStream out) throws IOException {
Encode.varInt(addresses.size(), out);
for (NetworkAddress address : addresses) {
address.write(stream);
address.write(out);
}
}
@Override
public void write(ByteBuffer buffer) {
Encode.varInt(addresses.size(), buffer);
for (NetworkAddress address : addresses) {
address.write(buffer);
}
}

View File

@ -21,6 +21,7 @@ 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;
@ -85,6 +86,17 @@ public class CustomMessage implements MessagePayload {
}
}
@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);
}

View File

@ -21,6 +21,7 @@ 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;
@ -55,6 +56,14 @@ public class GetData implements MessagePayload {
}
}
@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<>();

View File

@ -21,6 +21,7 @@ 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;
@ -53,6 +54,14 @@ public class Inv implements MessagePayload {
}
}
@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<>();

View File

@ -19,9 +19,9 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
@ -74,9 +74,7 @@ public class NetworkMessage implements Streamable {
out.write('\0');
}
ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
payload.write(payloadStream);
byte[] payloadBytes = payloadStream.toByteArray();
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
@ -93,4 +91,61 @@ public class NetworkMessage implements Streamable {
// 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;
}
}

View File

@ -29,6 +29,7 @@ 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;
@ -168,6 +169,16 @@ public class ObjectMessage implements MessagePayload {
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);

View File

@ -25,6 +25,7 @@ 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;
@ -197,12 +198,49 @@ public class Plaintext implements Streamable {
}
}
}
public void write(ByteBuffer buffer, boolean includeSignature) {
Encode.varInt(from.getVersion(), buffer);
Encode.varInt(from.getStream(), buffer);
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;
}

View File

@ -19,10 +19,13 @@ 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);
}

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* The 'verack' command answers a 'version' command, accepting the other node's version.
@ -34,4 +35,9 @@ public class VerAck implements MessagePayload {
public void write(OutputStream stream) throws IOException {
// 'verack' doesn't have any payload, so there is nothing to write
}
@Override
public void write(ByteBuffer buffer) {
// 'verack' doesn't have any payload, so there is nothing to write
}
}

View File

@ -23,7 +23,7 @@ import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import java.nio.ByteBuffer;
/**
* The 'version' command advertises this node's latest supported protocol version upon initiation.
@ -134,6 +134,18 @@ public class Version implements MessagePayload {
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;

View File

@ -24,6 +24,7 @@ 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;
@ -144,12 +145,29 @@ public class CryptoBox implements Streamable {
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;

View File

@ -21,6 +21,7 @@ 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;
/**
@ -62,6 +63,11 @@ public class GenericPayload extends ObjectPayload {
stream.write(data);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(data);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -22,6 +22,7 @@ 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.
@ -73,4 +74,9 @@ public class GetPubkey extends ObjectPayload {
public void write(OutputStream stream) throws IOException {
stream.write(ripeTag);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(ripeTag);
}
}

View File

@ -24,6 +24,7 @@ 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;
@ -111,6 +112,12 @@ public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
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;

View File

@ -18,6 +18,7 @@ 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;
@ -60,6 +61,10 @@ public abstract class Pubkey extends ObjectPayload {
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];

View File

@ -22,6 +22,7 @@ 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.
@ -86,10 +87,17 @@ public class V2Pubkey extends Pubkey {
}
@Override
public void write(OutputStream os) throws IOException {
Encode.int32(behaviorBitfield, os);
os.write(publicSigningKey, 1, 64);
os.write(publicEncryptionKey, 1, 64);
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 {

View File

@ -22,6 +22,7 @@ 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.
@ -58,4 +59,9 @@ public class V4Broadcast extends Broadcast {
public void write(OutputStream out) throws IOException {
encrypted.write(out);
}
@Override
public void write(ByteBuffer buffer) {
encrypted.write(buffer);
}
}

View File

@ -24,6 +24,7 @@ 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;
/**
@ -85,11 +86,22 @@ public class V4Pubkey extends Pubkey implements Encrypted {
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);

View File

@ -22,6 +22,7 @@ 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 {
@ -56,8 +57,13 @@ public class InventoryVector implements Streamable, Serializable {
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(hash);
public void write(OutputStream out) throws IOException {
out.write(hash);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(hash);
}
@Override

View File

@ -24,7 +24,10 @@ 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;
/**
@ -38,19 +41,19 @@ public class NetworkAddress implements Streamable {
/**
* Stream number for this node
*/
private long stream;
private final long stream;
/**
* same service(s) listed in version
*/
private long services;
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 byte[] ipv6;
private int port;
private final byte[] ipv6;
private final int port;
private NetworkAddress(Builder builder) {
time = builder.time;
@ -119,14 +122,29 @@ public class NetworkAddress implements Streamable {
write(stream, false);
}
public void write(OutputStream stream, boolean light) throws IOException {
public void write(OutputStream out, boolean light) throws IOException {
if (!light) {
Encode.int64(time, stream);
Encode.int32(this.stream, stream);
Encode.int64(time, out);
Encode.int32(stream, out);
}
Encode.int64(services, stream);
stream.write(ipv6);
Encode.int16(port, stream);
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 {
@ -199,6 +217,17 @@ public class NetworkAddress implements Streamable {
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();

View File

@ -27,6 +27,7 @@ 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;
@ -113,24 +114,20 @@ public class PrivateKey implements Streamable {
}
Builder generate() {
try {
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));
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;
} catch (IOException e) {
throw new ApplicationException(e);
}
signingKeyNonce += 2;
encryptionKeyNonce += 2;
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
nextNonce = signingKeyNonce;
return this;
}
}
@ -182,4 +179,20 @@ public class PrivateKey implements Streamable {
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);
}
}