First implementation of extended message encoding. Works as far as the PyBitmessage implementation, with some additional code.
This commit is contained in:
parent
e1dcbbf19c
commit
6d67598a40
@ -18,11 +18,13 @@ package ch.dissem.bitmessage.entity;
|
|||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
import ch.dissem.bitmessage.entity.payload.Msg;
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Attachment;
|
import ch.dissem.bitmessage.entity.valueobject.extended.Attachment;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||||
|
import ch.dissem.bitmessage.factory.ExtendedEncodingFactory;
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
import ch.dissem.bitmessage.factory.Factory;
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
import ch.dissem.bitmessage.utils.Decode;
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
import ch.dissem.bitmessage.utils.Encode;
|
||||||
@ -333,10 +335,10 @@ public class Plaintext implements Streamable {
|
|||||||
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
|
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
|
||||||
String firstLine = s.nextLine();
|
String firstLine = s.nextLine();
|
||||||
if (encoding == EXTENDED.code) {
|
if (encoding == EXTENDED.code) {
|
||||||
if (getExtendedData().getMessage() == null) {
|
if (Message.TYPE.equals(getExtendedData().getType())) {
|
||||||
return null;
|
return ((Message) extendedData.getContent()).getSubject();
|
||||||
} else {
|
} else {
|
||||||
return extendedData.getMessage().getSubject();
|
return null;
|
||||||
}
|
}
|
||||||
} else if (encoding == SIMPLE.code) {
|
} else if (encoding == SIMPLE.code) {
|
||||||
return firstLine.substring("Subject:".length()).trim();
|
return firstLine.substring("Subject:".length()).trim();
|
||||||
@ -349,10 +351,10 @@ public class Plaintext implements Streamable {
|
|||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
if (encoding == EXTENDED.code) {
|
if (encoding == EXTENDED.code) {
|
||||||
if (getExtendedData().getMessage() == null) {
|
if (Message.TYPE.equals(getExtendedData().getType())) {
|
||||||
return null;
|
return ((Message) extendedData.getContent()).getBody();
|
||||||
} else {
|
} else {
|
||||||
return extendedData.getMessage().getBody();
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@ -370,24 +372,24 @@ public class Plaintext implements Streamable {
|
|||||||
protected ExtendedEncoding getExtendedData() {
|
protected ExtendedEncoding getExtendedData() {
|
||||||
if (extendedData == null && encoding == EXTENDED.code) {
|
if (extendedData == null && encoding == EXTENDED.code) {
|
||||||
// TODO: make sure errors are properly handled
|
// TODO: make sure errors are properly handled
|
||||||
extendedData = ExtendedEncoding.unzip(message);
|
extendedData = ExtendedEncodingFactory.getInstance().unzip(message);
|
||||||
}
|
}
|
||||||
return extendedData;
|
return extendedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<InventoryVector> getParents() {
|
public List<InventoryVector> getParents() {
|
||||||
if (getExtendedData() == null || extendedData.getMessage() == null) {
|
if (Message.TYPE.equals(getExtendedData().getType())) {
|
||||||
return Collections.emptyList();
|
return ((Message) extendedData.getContent()).getParents();
|
||||||
} else {
|
} else {
|
||||||
return extendedData.getMessage().getParents();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Attachment> getFiles() {
|
public List<Attachment> getFiles() {
|
||||||
if (getExtendedData() == null || extendedData.getMessage() == null) {
|
if (Message.TYPE.equals(getExtendedData().getType())) {
|
||||||
return Collections.emptyList();
|
return ((Message) extendedData.getContent()).getFiles();
|
||||||
} else {
|
} else {
|
||||||
return extendedData.getMessage().getFiles();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,35 +4,38 @@ import ch.dissem.bitmessage.exception.ApplicationException;
|
|||||||
import org.msgpack.core.MessagePack;
|
import org.msgpack.core.MessagePack;
|
||||||
import org.msgpack.core.MessagePacker;
|
import org.msgpack.core.MessagePacker;
|
||||||
import org.msgpack.core.MessageUnpacker;
|
import org.msgpack.core.MessageUnpacker;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
import java.util.zip.InflaterInputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended encoding message object.
|
* Extended encoding message object.
|
||||||
*/
|
*/
|
||||||
public class ExtendedEncoding implements Serializable {
|
public class ExtendedEncoding implements Serializable {
|
||||||
private static final long serialVersionUID = 3876871488247305200L;
|
private static final long serialVersionUID = 3876871488247305200L;
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncoding.class);
|
||||||
|
|
||||||
private Message message;
|
private ExtendedType content;
|
||||||
|
|
||||||
public ExtendedEncoding(Message message) {
|
public ExtendedEncoding(ExtendedType content) {
|
||||||
this.message = message;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtendedEncoding() {
|
public String getType() {
|
||||||
|
if (content == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return content.getType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message getMessage() {
|
public ExtendedType getContent() {
|
||||||
return message;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] zip() {
|
public byte[] zip() {
|
||||||
@ -40,10 +43,7 @@ public class ExtendedEncoding implements Serializable {
|
|||||||
DeflaterOutputStream zipper = new DeflaterOutputStream(out)) {
|
DeflaterOutputStream zipper = new DeflaterOutputStream(out)) {
|
||||||
|
|
||||||
MessagePacker packer = MessagePack.newDefaultPacker(zipper);
|
MessagePacker packer = MessagePack.newDefaultPacker(zipper);
|
||||||
// FIXME: this should work for trivial cases
|
content.pack(packer);
|
||||||
if (message != null) {
|
|
||||||
message.pack(packer);
|
|
||||||
}
|
|
||||||
packer.close();
|
packer.close();
|
||||||
zipper.close();
|
zipper.close();
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
@ -52,152 +52,28 @@ public class ExtendedEncoding implements Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedEncoding unzip(byte[] zippedData) {
|
|
||||||
ExtendedEncoding result = new ExtendedEncoding();
|
|
||||||
try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) {
|
|
||||||
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(unzipper);
|
|
||||||
int mapSize = unpacker.unpackMapHeader();
|
|
||||||
for (int i = 0; i < mapSize; i++) {
|
|
||||||
String key = unpacker.unpackString();
|
|
||||||
switch (key) {
|
|
||||||
case "":
|
|
||||||
switch (unpacker.unpackString()) {
|
|
||||||
case "message":
|
|
||||||
result.message = new Message();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "subject":
|
|
||||||
result.message.subject = unpacker.unpackString();
|
|
||||||
break;
|
|
||||||
case "body":
|
|
||||||
result.message.body = unpacker.unpackString();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
ExtendedEncoding that = (ExtendedEncoding) o;
|
ExtendedEncoding that = (ExtendedEncoding) o;
|
||||||
return Objects.equals(message, that.message);
|
return Objects.equals(content, that.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(message);
|
return Objects.hash(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Message implements Serializable {
|
public interface Unpacker<T extends ExtendedType> {
|
||||||
private static final long serialVersionUID = -2724977231484285467L;
|
String getType();
|
||||||
|
|
||||||
private String subject;
|
T unpack(MessageUnpacker unpacker, int size);
|
||||||
private String body;
|
|
||||||
private List<InventoryVector> parents;
|
|
||||||
private List<Attachment> files;
|
|
||||||
|
|
||||||
private Message() {
|
|
||||||
parents = Collections.emptyList();
|
|
||||||
files = Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message(Builder builder) {
|
|
||||||
subject = builder.subject;
|
|
||||||
body = builder.body;
|
|
||||||
parents = Collections.unmodifiableList(builder.parents);
|
|
||||||
files = Collections.unmodifiableList(builder.files);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubject() {
|
|
||||||
return subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBody() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InventoryVector> getParents() {
|
|
||||||
return parents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attachment> getFiles() {
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Message message = (Message) o;
|
|
||||||
return Objects.equals(subject, message.subject) &&
|
|
||||||
Objects.equals(body, message.body) &&
|
|
||||||
Objects.equals(parents, message.parents) &&
|
|
||||||
Objects.equals(files, message.files);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(subject, body, parents, files);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pack(MessagePacker packer) throws IOException {
|
|
||||||
packer.packMapHeader(3);
|
|
||||||
packer.packString("");
|
|
||||||
packer.packString("message");
|
|
||||||
packer.packString("subject");
|
|
||||||
packer.packString(subject);
|
|
||||||
packer.packString("body");
|
|
||||||
packer.packString(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private String subject;
|
|
||||||
private String body;
|
|
||||||
private List<InventoryVector> parents = new LinkedList<>();
|
|
||||||
private List<Attachment> files = new LinkedList<>();
|
|
||||||
|
|
||||||
private Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder subject(String subject) {
|
|
||||||
this.subject = subject;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder body(String body) {
|
|
||||||
this.body = body;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addParent(InventoryVector iv) {
|
|
||||||
parents.add(iv);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addFile(Attachment file) {
|
|
||||||
files.add(file);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExtendedEncoding build() {
|
|
||||||
return new ExtendedEncoding(new Message(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public interface ExtendedType extends Serializable {
|
||||||
public Message.Builder message() {
|
String getType();
|
||||||
return new Message.Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: vote (etc.?)
|
void pack(MessagePacker packer) throws IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package ch.dissem.bitmessage.entity.valueobject;
|
package ch.dissem.bitmessage.entity.valueobject.extended;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -48,7 +48,7 @@ public class Attachment implements Serializable {
|
|||||||
return Objects.hash(name, data, type, disposition);
|
return Objects.hash(name, data, type, disposition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Disposition {
|
public enum Disposition {
|
||||||
inline, attachment
|
inline, attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +83,11 @@ public class Attachment implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder disposition(Disposition disposition) {
|
||||||
|
this.disposition = disposition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Attachment build() {
|
public Attachment build() {
|
||||||
Attachment attachment = new Attachment();
|
Attachment attachment = new Attachment();
|
||||||
attachment.type = this.type;
|
attachment.type = this.type;
|
@ -0,0 +1,257 @@
|
|||||||
|
package ch.dissem.bitmessage.entity.valueobject.extended;
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||||
|
import org.msgpack.core.MessagePacker;
|
||||||
|
import org.msgpack.core.MessageUnpacker;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work
|
||||||
|
* properly with future PyBitmessage implementations.
|
||||||
|
*/
|
||||||
|
public class Message implements ExtendedEncoding.ExtendedType {
|
||||||
|
private static final long serialVersionUID = -2724977231484285467L;
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Message.class);
|
||||||
|
|
||||||
|
public static final String TYPE = "message";
|
||||||
|
|
||||||
|
private String subject;
|
||||||
|
private String body;
|
||||||
|
private List<InventoryVector> parents;
|
||||||
|
private List<Attachment> files;
|
||||||
|
|
||||||
|
private Message(Builder builder) {
|
||||||
|
subject = builder.subject;
|
||||||
|
body = builder.body;
|
||||||
|
parents = Collections.unmodifiableList(builder.parents);
|
||||||
|
files = Collections.unmodifiableList(builder.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InventoryVector> getParents() {
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Attachment> getFiles() {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Message message = (Message) o;
|
||||||
|
return Objects.equals(subject, message.subject) &&
|
||||||
|
Objects.equals(body, message.body) &&
|
||||||
|
Objects.equals(parents, message.parents) &&
|
||||||
|
Objects.equals(files, message.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(subject, body, parents, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pack(MessagePacker packer) throws IOException {
|
||||||
|
int size = 3;
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
if (!parents.isEmpty()) {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
packer.packMapHeader(size);
|
||||||
|
packer.packString("");
|
||||||
|
packer.packString("message");
|
||||||
|
packer.packString("subject");
|
||||||
|
packer.packString(subject);
|
||||||
|
packer.packString("body");
|
||||||
|
packer.packString(body);
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
packer.packString("files");
|
||||||
|
packer.packArrayHeader(files.size());
|
||||||
|
for (Attachment file : files) {
|
||||||
|
packer.packMapHeader(4);
|
||||||
|
packer.packString("name");
|
||||||
|
packer.packString(file.getName());
|
||||||
|
packer.packString("data");
|
||||||
|
packer.packBinaryHeader(file.getData().length);
|
||||||
|
packer.writePayload(file.getData());
|
||||||
|
packer.packString("type");
|
||||||
|
packer.packString(file.getType());
|
||||||
|
packer.packString("disposition");
|
||||||
|
packer.packString(file.getDisposition().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!parents.isEmpty()) {
|
||||||
|
packer.packString("parents");
|
||||||
|
packer.packArrayHeader(parents.size());
|
||||||
|
for (InventoryVector parent : parents) {
|
||||||
|
packer.packBinaryHeader(parent.getHash().length);
|
||||||
|
packer.writePayload(parent.getHash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private String subject;
|
||||||
|
private String body;
|
||||||
|
private List<InventoryVector> parents = new LinkedList<>();
|
||||||
|
private List<Attachment> files = new LinkedList<>();
|
||||||
|
|
||||||
|
public Builder subject(String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder body(String body) {
|
||||||
|
this.body = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addParent(Plaintext parent) {
|
||||||
|
parents.add(parent.getInventoryVector());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addParent(InventoryVector iv) {
|
||||||
|
parents.add(iv);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addFile(File file, Attachment.Disposition disposition) {
|
||||||
|
try {
|
||||||
|
files.add(new Attachment.Builder()
|
||||||
|
.name(file.getName())
|
||||||
|
.disposition(disposition)
|
||||||
|
.type(URLConnection.guessContentTypeFromStream(new FileInputStream(file)))
|
||||||
|
.data(Files.readAllBytes(file.toPath()))
|
||||||
|
.build());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addFile(Attachment file) {
|
||||||
|
files.add(file);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedEncoding build() {
|
||||||
|
return new ExtendedEncoding(new Message(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Unpacker implements ExtendedEncoding.Unpacker<Message> {
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message unpack(MessageUnpacker unpacker, int size) {
|
||||||
|
Message.Builder builder = new Message.Builder();
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
String key = unpacker.unpackString();
|
||||||
|
switch (key) {
|
||||||
|
case "subject":
|
||||||
|
builder.subject(unpacker.unpackString());
|
||||||
|
break;
|
||||||
|
case "body":
|
||||||
|
builder.body(unpacker.unpackString());
|
||||||
|
break;
|
||||||
|
case "parents":
|
||||||
|
builder.parents = unpackParents(unpacker);
|
||||||
|
break;
|
||||||
|
case "files":
|
||||||
|
builder.files = unpackFiles(unpacker);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.error("Unexpected data with key: " + key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return new Message(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InventoryVector> unpackParents(MessageUnpacker unpacker) throws IOException {
|
||||||
|
int size = unpacker.unpackArrayHeader();
|
||||||
|
List<InventoryVector> parents = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int binarySize = unpacker.unpackBinaryHeader();
|
||||||
|
parents.add(new InventoryVector(unpacker.readPayload(binarySize)));
|
||||||
|
}
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Attachment> unpackFiles(MessageUnpacker unpacker) throws IOException {
|
||||||
|
int size = unpacker.unpackArrayHeader();
|
||||||
|
List<Attachment> files = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Attachment.Builder attachment = new Attachment.Builder();
|
||||||
|
int mapSize = unpacker.unpackMapHeader();
|
||||||
|
for (int j = 0; j < mapSize; j++) {
|
||||||
|
String key = unpacker.unpackString();
|
||||||
|
switch (key) {
|
||||||
|
case "name":
|
||||||
|
attachment.name(unpacker.unpackString());
|
||||||
|
break;
|
||||||
|
case "data":
|
||||||
|
int binarySize = unpacker.unpackBinaryHeader();
|
||||||
|
attachment.data(unpacker.readPayload(binarySize));
|
||||||
|
break;
|
||||||
|
case "type":
|
||||||
|
attachment.type(unpacker.unpackString());
|
||||||
|
break;
|
||||||
|
case "disposition":
|
||||||
|
String disposition = unpacker.unpackString();
|
||||||
|
switch (disposition) {
|
||||||
|
case "inline":
|
||||||
|
attachment.inline();
|
||||||
|
break;
|
||||||
|
case "attachment":
|
||||||
|
attachment.attachment();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.debug("Unknown disposition: " + disposition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.debug("Unknown file info '" + key + "' with data: " + unpacker.unpackValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files.add(attachment.build());
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
package ch.dissem.bitmessage.entity.valueobject.extended;
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||||
|
import org.msgpack.core.MessagePacker;
|
||||||
|
import org.msgpack.core.MessageUnpacker;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended encoding type 'vote'. Specification still outstanding, so this will need some work.
|
||||||
|
*/
|
||||||
|
public class Vote implements ExtendedEncoding.ExtendedType {
|
||||||
|
private static final long serialVersionUID = -8427038604209964837L;
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Vote.class);
|
||||||
|
|
||||||
|
public static final String TYPE = "vote";
|
||||||
|
|
||||||
|
private InventoryVector msgId;
|
||||||
|
private String vote;
|
||||||
|
|
||||||
|
private Vote(Builder builder) {
|
||||||
|
msgId = builder.msgId;
|
||||||
|
vote = builder.vote;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InventoryVector getMsgId() {
|
||||||
|
return msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVote() {
|
||||||
|
return vote;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Vote vote1 = (Vote) o;
|
||||||
|
return Objects.equals(msgId, vote1.msgId) &&
|
||||||
|
Objects.equals(vote, vote1.vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(msgId, vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pack(MessagePacker packer) throws IOException {
|
||||||
|
packer.packMapHeader(3);
|
||||||
|
packer.packString("");
|
||||||
|
packer.packString("vote");
|
||||||
|
packer.packString("msgId");
|
||||||
|
packer.packBinaryHeader(msgId.getHash().length);
|
||||||
|
packer.writePayload(msgId.getHash());
|
||||||
|
packer.packString("vote");
|
||||||
|
packer.packString(vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private InventoryVector msgId;
|
||||||
|
private String vote;
|
||||||
|
|
||||||
|
public ExtendedEncoding up(Plaintext message) {
|
||||||
|
msgId = message.getInventoryVector();
|
||||||
|
vote = "1";
|
||||||
|
return new ExtendedEncoding(new Vote(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedEncoding down(Plaintext message) {
|
||||||
|
msgId = message.getInventoryVector();
|
||||||
|
vote = "1";
|
||||||
|
return new ExtendedEncoding(new Vote(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder msgId(InventoryVector iv) {
|
||||||
|
this.msgId = iv;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder vote(String vote) {
|
||||||
|
this.vote = vote;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedEncoding build() {
|
||||||
|
return new ExtendedEncoding(new Vote(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Unpacker implements ExtendedEncoding.Unpacker<Vote> {
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vote unpack(MessageUnpacker unpacker, int size) {
|
||||||
|
Vote.Builder builder = new Vote.Builder();
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
String key = unpacker.unpackString();
|
||||||
|
switch (key) {
|
||||||
|
case "msgId":
|
||||||
|
int binarySize = unpacker.unpackBinaryHeader();
|
||||||
|
builder.msgId(new InventoryVector(unpacker.readPayload(binarySize)));
|
||||||
|
break;
|
||||||
|
case "vote":
|
||||||
|
builder.vote(unpacker.unpackString());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.error("Unexpected data with key: " + key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return new Vote(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package ch.dissem.bitmessage.factory;
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Vote;
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||||
|
import org.msgpack.core.MessagePack;
|
||||||
|
import org.msgpack.core.MessageUnpacker;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory that creates {@link ExtendedEncoding} objects from byte arrays. You can register your own types by adding a
|
||||||
|
* {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}.
|
||||||
|
*/
|
||||||
|
public class ExtendedEncodingFactory {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class);
|
||||||
|
private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory();
|
||||||
|
private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>();
|
||||||
|
|
||||||
|
private ExtendedEncodingFactory() {
|
||||||
|
registerFactory(new Message.Unpacker());
|
||||||
|
registerFactory(new Vote.Unpacker());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerFactory(ExtendedEncoding.Unpacker<?> factory) {
|
||||||
|
factories.put(factory.getType(), factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ExtendedEncoding unzip(byte[] zippedData) {
|
||||||
|
try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) {
|
||||||
|
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(unzipper);
|
||||||
|
int mapSize = unpacker.unpackMapHeader();
|
||||||
|
String key = unpacker.unpackString();
|
||||||
|
if (!"".equals(key)) {
|
||||||
|
LOG.error("Unexpected content: " + key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String type = unpacker.unpackString();
|
||||||
|
ExtendedEncoding.Unpacker<?> factory = factories.get(type);
|
||||||
|
return new ExtendedEncoding(factory.unpack(unpacker, mapSize - 1));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ApplicationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExtendedEncodingFactory getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,11 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
package ch.dissem.bitmessage.utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class for working with byte arrays interpreted as unsigned big endian integers.
|
* A helper class for working with byte arrays interpreted as unsigned big endian integers.
|
||||||
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
||||||
|
File diff suppressed because one or more lines are too long
@ -17,9 +17,9 @@
|
|||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity;
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
import ch.dissem.bitmessage.entity.payload.*;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
import ch.dissem.bitmessage.factory.Factory;
|
||||||
import ch.dissem.bitmessage.utils.TestBase;
|
import ch.dissem.bitmessage.utils.TestBase;
|
||||||
import ch.dissem.bitmessage.utils.TestUtils;
|
import ch.dissem.bitmessage.utils.TestUtils;
|
||||||
@ -31,7 +31,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class SerializationTest extends TestBase {
|
public class SerializationTest extends TestBase {
|
||||||
@ -104,7 +103,7 @@ public class SerializationTest extends TestBase {
|
|||||||
Plaintext p1 = new Plaintext.Builder(MSG)
|
Plaintext p1 = new Plaintext.Builder(MSG)
|
||||||
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||||
.to(TestUtils.loadContact())
|
.to(TestUtils.loadContact())
|
||||||
.message(new ExtendedEncoding.Builder().message()
|
.message(new Message.Builder()
|
||||||
.subject("Subject")
|
.subject("Subject")
|
||||||
.body("Message")
|
.body("Message")
|
||||||
.build())
|
.build())
|
||||||
@ -154,7 +153,7 @@ public class SerializationTest extends TestBase {
|
|||||||
public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception {
|
public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception {
|
||||||
ArrayList<InventoryVector> ivs = new ArrayList<>(50000);
|
ArrayList<InventoryVector> ivs = new ArrayList<>(50000);
|
||||||
for (int i = 0; i < 50000; i++) {
|
for (int i = 0; i < 50000; i++) {
|
||||||
ivs.add(new InventoryVector(cryptography().randomBytes(32)));
|
ivs.add(TestUtils.randomInventoryVector());
|
||||||
}
|
}
|
||||||
|
|
||||||
Inv inv = new Inv.Builder().inventory(ivs).build();
|
Inv inv = new Inv.Builder().inventory(ivs).build();
|
||||||
|
@ -20,6 +20,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
import ch.dissem.bitmessage.factory.Factory;
|
||||||
@ -28,6 +29,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@ -35,6 +37,8 @@ import static org.junit.Assert.assertEquals;
|
|||||||
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
|
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
|
||||||
*/
|
*/
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
public static final Random RANDOM = new Random();
|
||||||
|
|
||||||
public static byte[] int16(int number) throws IOException {
|
public static byte[] int16(int number) throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Encode.int16(number, out);
|
Encode.int16(number, out);
|
||||||
@ -59,6 +63,12 @@ public class TestUtils {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InventoryVector randomInventoryVector() {
|
||||||
|
byte[] bytes = new byte[32];
|
||||||
|
RANDOM.nextBytes(bytes);
|
||||||
|
return new InventoryVector(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public static InputStream getResource(String resourceName) {
|
public static InputStream getResource(String resourceName) {
|
||||||
return TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
|
return TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
|
|||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||||
|
import ch.dissem.bitmessage.utils.TestUtils;
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
import ch.dissem.bitmessage.utils.UnixTime;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -162,7 +163,7 @@ public class JdbcMessageRepositoryTest extends TestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void testSave() throws Exception {
|
public void testSave() throws Exception {
|
||||||
Plaintext message = new Plaintext.Builder(MSG)
|
Plaintext message = new Plaintext.Builder(MSG)
|
||||||
.IV(new InventoryVector(cryptography().randomBytes(32)))
|
.IV(TestUtils.randomInventoryVector())
|
||||||
.from(identity)
|
.from(identity)
|
||||||
.to(contactA)
|
.to(contactA)
|
||||||
.message("Subject", "Message")
|
.message("Subject", "Message")
|
||||||
@ -185,7 +186,7 @@ public class JdbcMessageRepositoryTest extends TestBase {
|
|||||||
public void testUpdate() throws Exception {
|
public void testUpdate() throws Exception {
|
||||||
List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
|
List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
|
||||||
Plaintext message = messages.get(0);
|
Plaintext message = messages.get(0);
|
||||||
message.setInventoryVector(new InventoryVector(cryptography().randomBytes(32)));
|
message.setInventoryVector(TestUtils.randomInventoryVector());
|
||||||
repo.save(message);
|
repo.save(message);
|
||||||
|
|
||||||
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
|
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
|
||||||
@ -206,7 +207,7 @@ public class JdbcMessageRepositoryTest extends TestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void ensureUnacknowledgedMessagesAreFoundForResend() throws Exception {
|
public void ensureUnacknowledgedMessagesAreFoundForResend() throws Exception {
|
||||||
Plaintext message = new Plaintext.Builder(MSG)
|
Plaintext message = new Plaintext.Builder(MSG)
|
||||||
.IV(new InventoryVector(cryptography().randomBytes(32)))
|
.IV(TestUtils.randomInventoryVector())
|
||||||
.from(identity)
|
.from(identity)
|
||||||
.to(contactA)
|
.to(contactA)
|
||||||
.message("Subject", "Message")
|
.message("Subject", "Message")
|
||||||
|
Loading…
Reference in New Issue
Block a user