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 {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
test {

View File

@ -25,7 +25,7 @@ artifacts {
dependencies {
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 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'

View File

@ -1,9 +1,9 @@
package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.exception.ApplicationException;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessagePacker;
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.LoggerFactory;
@ -39,13 +39,10 @@ public class ExtendedEncoding implements Serializable {
}
public byte[] zip() {
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
DeflaterOutputStream zipper = new DeflaterOutputStream(out)) {
MessagePacker packer = MessagePack.newDefaultPacker(zipper);
content.pack(packer);
packer.close();
zipper.close();
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
try (DeflaterOutputStream zipper = new DeflaterOutputStream(out)) {
content.pack().pack(zipper);
}
return out.toByteArray();
} catch (IOException e) {
throw new ApplicationException(e);
@ -68,12 +65,12 @@ public class ExtendedEncoding implements Serializable {
public interface Unpacker<T extends ExtendedType> {
String getType();
T unpack(MessageUnpacker unpacker, int size);
T unpack(MPMap<MPString, MPType<?>> map);
}
public interface ExtendedType extends Serializable {
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.valueobject.ExtendedEncoding;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import org.msgpack.core.MessagePacker;
import org.msgpack.core.MessageUnpacker;
import ch.dissem.msgpack.types.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,6 +14,10 @@ import java.net.URLConnection;
import java.nio.file.Files;
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
* properly with future PyBitmessage implementations.
@ -74,45 +77,33 @@ public class Message implements ExtendedEncoding.ExtendedType {
return Objects.hash(subject, body, parents, files);
}
public void pack(MessagePacker packer) throws IOException {
int size = 3;
@Override
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()) {
size++;
}
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());
MPArray<MPMap<MPString, MPType<?>>> items = new MPArray<>();
result.put(mp("files"), items);
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());
MPMap<MPString, MPType<?>> item = new MPMap<>();
item.put(mp("name"), mp(file.getName()));
item.put(mp("data"), mp(file.getData()));
item.put(mp("type"), mp(file.getType()));
item.put(mp("disposition"), mp(file.getDisposition().name()));
items.add(item);
}
}
if (!parents.isEmpty()) {
packer.packString("parents");
packer.packArrayHeader(parents.size());
MPArray<MPBinary> items = new MPArray<>();
result.put(mp("parents"), items);
for (InventoryVector parent : parents) {
packer.packBinaryHeader(parent.getHash().length);
packer.writePayload(parent.getHash());
items.add(mp(parent.getHash()));
}
}
return result;
}
public static class Builder {
@ -185,86 +176,44 @@ public class Message implements ExtendedEncoding.ExtendedType {
}
@Override
public Message unpack(MessageUnpacker unpacker, int size) {
public Message unpack(MPMap<MPString, MPType<?>> map) {
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;
}
builder.subject(str(map.get(mp("subject"))));
builder.body(str(map.get(mp("body"))));
@SuppressWarnings("unchecked")
MPArray<MPBinary> parents = (MPArray<MPBinary>) map.get(mp("parents"));
if (parents != null) {
for (MPBinary parent : parents) {
builder.addParent(new InventoryVector(parent.getValue()));
}
} 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);
}
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)));
private byte[] bin(MPType data) {
if (data instanceof MPBinary) {
return ((MPBinary) data).getValue();
} else {
return null;
}
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.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 ch.dissem.msgpack.types.*;
import java.io.IOException;
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.
*/
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";
@ -55,15 +54,13 @@ public class Vote implements ExtendedEncoding.ExtendedType {
return Objects.hash(msgId, vote);
}
public void pack(MessagePacker packer) throws IOException {
packer.packMapHeader(3);
packer.packString("");
packer.packString(TYPE);
packer.packString("msgId");
packer.packBinaryHeader(msgId.getHash().length);
packer.writePayload(msgId.getHash());
packer.packString("vote");
packer.packString(vote);
@Override
public MPMap<MPString, MPType<?>> pack() throws IOException {
MPMap<MPString, MPType<?>> result = new MPMap<>();
result.put(mp(""), mp(TYPE));
result.put(mp("msgId"), mp(msgId.getHash()));
result.put(mp("vote"), mp(vote));
return result;
}
public static class Builder {
@ -104,27 +101,13 @@ public class Vote implements ExtendedEncoding.ExtendedType {
}
@Override
public Vote unpack(MessageUnpacker unpacker, int size) {
public Vote unpack(MPMap<MPString, MPType<?>> map) {
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);
MPType<?> msgId = map.get(mp("msgId"));
if (msgId instanceof MPBinary) {
builder.msgId(new InventoryVector(((MPBinary) msgId).getValue()));
}
builder.vote(str(map.get(mp("vote"))));
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.Vote;
import ch.dissem.bitmessage.exception.ApplicationException;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessageUnpacker;
import ch.dissem.msgpack.Reader;
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.LoggerFactory;
@ -15,6 +17,8 @@ import java.util.HashMap;
import java.util.Map;
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
* {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}.
@ -22,7 +26,7 @@ import java.util.zip.InflaterInputStream;
public class ExtendedEncodingFactory {
private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class);
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 ExtendedEncodingFactory() {
@ -37,17 +41,17 @@ public class ExtendedEncodingFactory {
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 (!KEY_MESSAGE_TYPE.equals(key)) {
LOG.error("Unexpected content: " + key);
Reader reader = Reader.getInstance();
@SuppressWarnings("unchecked")
MPMap<MPString, MPType<?>> map = (MPMap<MPString, MPType<?>>) reader.read(unzipper);
MPType<?> messageType = map.get(KEY_MESSAGE_TYPE);
if (messageType == null) {
LOG.error("Missing message type");
return null;
}
String type = unpacker.unpackString();
ExtendedEncoding.Unpacker<?> factory = factories.get(type);
return new ExtendedEncoding(factory.unpack(unpacker, mapSize - 1));
} catch (IOException e) {
ExtendedEncoding.Unpacker<?> factory = factories.get(str(messageType));
return new ExtendedEncoding(factory.unpack(map));
} catch (ClassCastException | IOException e) {
throw new ApplicationException(e);
}
}

View File

@ -37,4 +37,8 @@ public class Strings {
}
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.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import org.apache.commons.lang3.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static ch.dissem.bitmessage.demo.CommandLine.COMMAND_BACK;
import static ch.dissem.bitmessage.demo.CommandLine.ERROR_UNKNOWN_COMMAND;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
/**
* A simple command line Bitmessage application
*/
public class Application {
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 BitmessageContext ctx;
@ -342,7 +346,7 @@ public class Application {
System.out.println();
System.out.println("c) compose message");
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(COMMAND_BACK);
@ -392,7 +396,7 @@ public class Application {
command = commandLine.nextCommand();
switch (command) {
case "r":
compose(message.getTo(), message.getFrom(), "RE: " + message.getSubject());
compose(message.getTo(), message.getFrom(), message);
break;
case "d":
ctx.labeler().delete(message);
@ -442,14 +446,20 @@ public class Application {
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);
String subject;
System.out.println();
System.out.println("From: " + from);
if (!broadcast) {
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);
} else {
System.out.print("Subject: ");
@ -462,10 +472,20 @@ public class Application {
line = commandLine.nextLine();
message.append(line).append('\n');
} while (line.length() > 0 || !commandLine.yesNo("Send message?"));
if (broadcast) {
ctx.broadcast(from, subject, message.toString());
Plaintext.Type type = broadcast ? Plaintext.Type.BROADCAST : Plaintext.Type.MSG;
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 {
ctx.send(from, to, subject, message.toString());
builder.message(subject, message.toString());
}
ctx.send(builder.build());
}
}