Merge branch 'feature/nio' into develop
This commit is contained in:
commit
1003e7a582
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@ -0,0 +1,7 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_size = 4
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<>();
|
||||
|
||||
|
@ -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<>();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|