Moved to own MsgPack implementation

This commit is contained in:
Christian Basler 2017-02-03 07:29:51 +01:00
parent 3f8980e236
commit 10a45cc79c
8 changed files with 129 additions and 171 deletions

View File

@ -10,6 +10,7 @@ subprojects {
repositories { repositories {
mavenCentral() mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
} }
test { test {

View File

@ -25,7 +25,7 @@ artifacts {
dependencies { dependencies {
compile 'org.slf4j:slf4j-api:1.7.12' compile 'org.slf4j:slf4j-api:1.7.12'
compile 'org.msgpack:msgpack-core:0.8.11' compile 'ch.dissem.msgpack:msgpack:development-SNAPSHOT'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3' testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'org.mockito:mockito-core:1.10.19'

View File

@ -1,9 +1,9 @@
package ch.dissem.bitmessage.entity.valueobject; package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.ApplicationException;
import org.msgpack.core.MessagePack; import ch.dissem.msgpack.types.MPMap;
import org.msgpack.core.MessagePacker; import ch.dissem.msgpack.types.MPString;
import org.msgpack.core.MessageUnpacker; import ch.dissem.msgpack.types.MPType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -39,13 +39,10 @@ public class ExtendedEncoding implements Serializable {
} }
public byte[] zip() { public byte[] zip() {
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
DeflaterOutputStream zipper = new DeflaterOutputStream(out)) { try (DeflaterOutputStream zipper = new DeflaterOutputStream(out)) {
content.pack().pack(zipper);
MessagePacker packer = MessagePack.newDefaultPacker(zipper); }
content.pack(packer);
packer.close();
zipper.close();
return out.toByteArray(); return out.toByteArray();
} catch (IOException e) { } catch (IOException e) {
throw new ApplicationException(e); throw new ApplicationException(e);
@ -68,12 +65,12 @@ public class ExtendedEncoding implements Serializable {
public interface Unpacker<T extends ExtendedType> { public interface Unpacker<T extends ExtendedType> {
String getType(); String getType();
T unpack(MessageUnpacker unpacker, int size); T unpack(MPMap<MPString, MPType<?>> map);
} }
public interface ExtendedType extends Serializable { public interface ExtendedType extends Serializable {
String getType(); String getType();
void pack(MessagePacker packer) throws IOException; MPMap<MPString, MPType<?>> pack() throws IOException;
} }
} }

View File

@ -3,8 +3,7 @@ package ch.dissem.bitmessage.entity.valueobject.extended;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
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 org.msgpack.core.MessagePacker; import ch.dissem.msgpack.types.*;
import org.msgpack.core.MessageUnpacker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -15,6 +14,10 @@ import java.net.URLConnection;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
import static ch.dissem.bitmessage.entity.valueobject.extended.Attachment.Disposition.attachment;
import static ch.dissem.bitmessage.utils.Strings.str;
import static ch.dissem.msgpack.types.Utils.mp;
/** /**
* Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work * Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work
* properly with future PyBitmessage implementations. * properly with future PyBitmessage implementations.
@ -74,45 +77,33 @@ public class Message implements ExtendedEncoding.ExtendedType {
return Objects.hash(subject, body, parents, files); return Objects.hash(subject, body, parents, files);
} }
public void pack(MessagePacker packer) throws IOException { @Override
int size = 3; public MPMap<MPString, MPType<?>> pack() throws IOException {
MPMap<MPString, MPType<?>> result = new MPMap<>();
result.put(mp(""), mp(TYPE));
result.put(mp("subject"), mp(subject));
result.put(mp("body"), mp(body));
if (!files.isEmpty()) { if (!files.isEmpty()) {
size++; MPArray<MPMap<MPString, MPType<?>>> items = new MPArray<>();
} result.put(mp("files"), items);
if (!parents.isEmpty()) {
size++;
}
packer.packMapHeader(size);
packer.packString("");
packer.packString(TYPE);
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) { for (Attachment file : files) {
packer.packMapHeader(4); MPMap<MPString, MPType<?>> item = new MPMap<>();
packer.packString("name"); item.put(mp("name"), mp(file.getName()));
packer.packString(file.getName()); item.put(mp("data"), mp(file.getData()));
packer.packString("data"); item.put(mp("type"), mp(file.getType()));
packer.packBinaryHeader(file.getData().length); item.put(mp("disposition"), mp(file.getDisposition().name()));
packer.writePayload(file.getData()); items.add(item);
packer.packString("type");
packer.packString(file.getType());
packer.packString("disposition");
packer.packString(file.getDisposition().name());
} }
} }
if (!parents.isEmpty()) { if (!parents.isEmpty()) {
packer.packString("parents"); MPArray<MPBinary> items = new MPArray<>();
packer.packArrayHeader(parents.size()); result.put(mp("parents"), items);
for (InventoryVector parent : parents) { for (InventoryVector parent : parents) {
packer.packBinaryHeader(parent.getHash().length); items.add(mp(parent.getHash()));
packer.writePayload(parent.getHash());
} }
} }
return result;
} }
public static class Builder { public static class Builder {
@ -185,86 +176,44 @@ public class Message implements ExtendedEncoding.ExtendedType {
} }
@Override @Override
public Message unpack(MessageUnpacker unpacker, int size) { public Message unpack(MPMap<MPString, MPType<?>> map) {
Message.Builder builder = new Message.Builder(); Message.Builder builder = new Message.Builder();
try { builder.subject(str(map.get(mp("subject"))));
for (int i = 0; i < size; i++) { builder.body(str(map.get(mp("body"))));
String key = unpacker.unpackString(); @SuppressWarnings("unchecked")
switch (key) { MPArray<MPBinary> parents = (MPArray<MPBinary>) map.get(mp("parents"));
case "subject": if (parents != null) {
builder.subject(unpacker.unpackString()); for (MPBinary parent : parents) {
break; builder.addParent(new InventoryVector(parent.getValue()));
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);
} }
@SuppressWarnings("unchecked")
MPArray<MPMap<MPString, MPType<?>>> files = (MPArray<MPMap<MPString, MPType<?>>>) map.get(mp("files"));
if (files != null) {
for (MPMap<MPString, MPType<?>> item : files) {
Attachment.Builder b = new Attachment.Builder();
b.name(str(item.get(mp("name"))));
b.data(bin(item.get(mp("data"))));
b.type(str(item.get(mp("type"))));
String disposition = str(item.get(mp("disposition")));
if ("inline".equals(disposition)) {
b.inline();
} else if ("attachment".equals(disposition)) {
b.attachment();
}
builder.addFile(b.build());
}
}
return new Message(builder); return new Message(builder);
} }
private static List<InventoryVector> unpackParents(MessageUnpacker unpacker) throws IOException { private byte[] bin(MPType data) {
int size = unpacker.unpackArrayHeader(); if (data instanceof MPBinary) {
List<InventoryVector> parents = new ArrayList<>(size); return ((MPBinary) data).getValue();
for (int i = 0; i < size; i++) { } else {
int binarySize = unpacker.unpackBinaryHeader(); return null;
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;
} }
} }
} }

View File

@ -3,20 +3,19 @@ package ch.dissem.bitmessage.entity.valueobject.extended;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
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 org.msgpack.core.MessagePacker; import ch.dissem.msgpack.types.*;
import org.msgpack.core.MessageUnpacker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import static ch.dissem.bitmessage.utils.Strings.str;
import static ch.dissem.msgpack.types.Utils.mp;
/** /**
* Extended encoding type 'vote'. Specification still outstanding, so this will need some work. * Extended encoding type 'vote'. Specification still outstanding, so this will need some work.
*/ */
public class Vote implements ExtendedEncoding.ExtendedType { public class Vote implements ExtendedEncoding.ExtendedType {
private static final long serialVersionUID = -8427038604209964837L; private static final long serialVersionUID = -8427038604209964837L;
private static final Logger LOG = LoggerFactory.getLogger(Vote.class);
public static final String TYPE = "vote"; public static final String TYPE = "vote";
@ -55,15 +54,13 @@ public class Vote implements ExtendedEncoding.ExtendedType {
return Objects.hash(msgId, vote); return Objects.hash(msgId, vote);
} }
public void pack(MessagePacker packer) throws IOException { @Override
packer.packMapHeader(3); public MPMap<MPString, MPType<?>> pack() throws IOException {
packer.packString(""); MPMap<MPString, MPType<?>> result = new MPMap<>();
packer.packString(TYPE); result.put(mp(""), mp(TYPE));
packer.packString("msgId"); result.put(mp("msgId"), mp(msgId.getHash()));
packer.packBinaryHeader(msgId.getHash().length); result.put(mp("vote"), mp(vote));
packer.writePayload(msgId.getHash()); return result;
packer.packString("vote");
packer.packString(vote);
} }
public static class Builder { public static class Builder {
@ -104,27 +101,13 @@ public class Vote implements ExtendedEncoding.ExtendedType {
} }
@Override @Override
public Vote unpack(MessageUnpacker unpacker, int size) { public Vote unpack(MPMap<MPString, MPType<?>> map) {
Vote.Builder builder = new Vote.Builder(); Vote.Builder builder = new Vote.Builder();
try { MPType<?> msgId = map.get(mp("msgId"));
for (int i = 0; i < size; i++) { if (msgId instanceof MPBinary) {
String key = unpacker.unpackString(); builder.msgId(new InventoryVector(((MPBinary) msgId).getValue()));
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);
} }
builder.vote(str(map.get(mp("vote"))));
return new Vote(builder); return new Vote(builder);
} }
} }

View File

@ -4,8 +4,10 @@ import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
import ch.dissem.bitmessage.entity.valueobject.extended.Message; import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import ch.dissem.bitmessage.entity.valueobject.extended.Vote; import ch.dissem.bitmessage.entity.valueobject.extended.Vote;
import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.ApplicationException;
import org.msgpack.core.MessagePack; import ch.dissem.msgpack.Reader;
import org.msgpack.core.MessageUnpacker; import ch.dissem.msgpack.types.MPMap;
import ch.dissem.msgpack.types.MPString;
import ch.dissem.msgpack.types.MPType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -15,6 +17,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
import static ch.dissem.bitmessage.utils.Strings.str;
/** /**
* Factory that creates {@link ExtendedEncoding} objects from byte arrays. You can register your own types by adding a * 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)}. * {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}.
@ -22,7 +26,7 @@ import java.util.zip.InflaterInputStream;
public class ExtendedEncodingFactory { public class ExtendedEncodingFactory {
private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class); private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class);
private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory(); private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory();
private static final String KEY_MESSAGE_TYPE = ""; private static final MPString KEY_MESSAGE_TYPE = new MPString("");
private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>(); private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>();
private ExtendedEncodingFactory() { private ExtendedEncodingFactory() {
@ -37,17 +41,17 @@ public class ExtendedEncodingFactory {
public ExtendedEncoding unzip(byte[] zippedData) { public ExtendedEncoding unzip(byte[] zippedData) {
try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) { try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) {
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(unzipper); Reader reader = Reader.getInstance();
int mapSize = unpacker.unpackMapHeader(); @SuppressWarnings("unchecked")
String key = unpacker.unpackString(); MPMap<MPString, MPType<?>> map = (MPMap<MPString, MPType<?>>) reader.read(unzipper);
if (!KEY_MESSAGE_TYPE.equals(key)) { MPType<?> messageType = map.get(KEY_MESSAGE_TYPE);
LOG.error("Unexpected content: " + key); if (messageType == null) {
LOG.error("Missing message type");
return null; return null;
} }
String type = unpacker.unpackString(); ExtendedEncoding.Unpacker<?> factory = factories.get(str(messageType));
ExtendedEncoding.Unpacker<?> factory = factories.get(type); return new ExtendedEncoding(factory.unpack(map));
return new ExtendedEncoding(factory.unpack(unpacker, mapSize - 1)); } catch (ClassCastException | IOException e) {
} catch (IOException e) {
throw new ApplicationException(e); throw new ApplicationException(e);
} }
} }

View File

@ -37,4 +37,8 @@ public class Strings {
} }
return hex; return hex;
} }
public static String str(Object o) {
return o == null ? null : o.toString();
}
} }

View File

@ -21,22 +21,26 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.text.WordUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static ch.dissem.bitmessage.demo.CommandLine.COMMAND_BACK; import static ch.dissem.bitmessage.demo.CommandLine.COMMAND_BACK;
import static ch.dissem.bitmessage.demo.CommandLine.ERROR_UNKNOWN_COMMAND; import static ch.dissem.bitmessage.demo.CommandLine.ERROR_UNKNOWN_COMMAND;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
/** /**
* A simple command line Bitmessage application * A simple command line Bitmessage application
*/ */
public class Application { public class Application {
private final static Logger LOG = LoggerFactory.getLogger(Application.class); private final static Logger LOG = LoggerFactory.getLogger(Application.class);
private final static Pattern RESPONSE_PATTERN = Pattern.compile("^RE:.*$", CASE_INSENSITIVE);
private final CommandLine commandLine; private final CommandLine commandLine;
private BitmessageContext ctx; private BitmessageContext ctx;
@ -342,7 +346,7 @@ public class Application {
System.out.println(); System.out.println();
System.out.println("c) compose message"); System.out.println("c) compose message");
System.out.println("s) compose broadcast"); System.out.println("s) compose broadcast");
if (label.getType() == Label.Type.TRASH) { if (label != null && label.getType() == Label.Type.TRASH) {
System.out.println("e) empty trash"); System.out.println("e) empty trash");
} }
System.out.println(COMMAND_BACK); System.out.println(COMMAND_BACK);
@ -392,7 +396,7 @@ public class Application {
command = commandLine.nextCommand(); command = commandLine.nextCommand();
switch (command) { switch (command) {
case "r": case "r":
compose(message.getTo(), message.getFrom(), "RE: " + message.getSubject()); compose(message.getTo(), message.getFrom(), message);
break; break;
case "d": case "d":
ctx.labeler().delete(message); ctx.labeler().delete(message);
@ -442,14 +446,20 @@ public class Application {
return commandLine.selectAddress(addresses, "To:"); return commandLine.selectAddress(addresses, "To:");
} }
private void compose(BitmessageAddress from, BitmessageAddress to, String subject) { private void compose(BitmessageAddress from, BitmessageAddress to, Plaintext parent) {
boolean broadcast = (to == null); boolean broadcast = (to == null);
String subject;
System.out.println(); System.out.println();
System.out.println("From: " + from); System.out.println("From: " + from);
if (!broadcast) { if (!broadcast) {
System.out.println("To: " + to); System.out.println("To: " + to);
} }
if (subject != null) { if (parent != null) {
if (RESPONSE_PATTERN.matcher(parent.getSubject()).matches()) {
subject = parent.getSubject();
} else {
subject = "RE: " + parent.getSubject();
}
System.out.println("Subject: " + subject); System.out.println("Subject: " + subject);
} else { } else {
System.out.print("Subject: "); System.out.print("Subject: ");
@ -462,10 +472,20 @@ public class Application {
line = commandLine.nextLine(); line = commandLine.nextLine();
message.append(line).append('\n'); message.append(line).append('\n');
} while (line.length() > 0 || !commandLine.yesNo("Send message?")); } while (line.length() > 0 || !commandLine.yesNo("Send message?"));
if (broadcast) { Plaintext.Type type = broadcast ? Plaintext.Type.BROADCAST : Plaintext.Type.MSG;
ctx.broadcast(from, subject, message.toString()); Plaintext.Builder builder = new Plaintext.Builder(type);
builder.from(from);
builder.to(to);
if (commandLine.yesNo("Use extended encoding?")) {
Message.Builder extended = new Message.Builder();
extended.subject(subject).body(message.toString());
if (parent != null) {
extended.addParent(parent);
}
builder.message(extended.build());
} else { } else {
ctx.send(from, to, subject, message.toString()); builder.message(subject, message.toString());
} }
ctx.send(builder.build());
} }
} }