diff --git a/build.gradle b/build.gradle index 311c3f9..13b3ad0 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ subprojects { repositories { mavenCentral() + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } test { diff --git a/core/build.gradle b/core/build.gradle index 1c2d07f..2fe6d50 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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' diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java index f5a15c7..3f3d1ec 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java @@ -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 { String getType(); - T unpack(MessageUnpacker unpacker, int size); + T unpack(MPMap> map); } public interface ExtendedType extends Serializable { String getType(); - void pack(MessagePacker packer) throws IOException; + MPMap> pack() throws IOException; } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java index b738d42..5bba98b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java @@ -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> pack() throws IOException { + MPMap> 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>> 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> 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 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> 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 parents = (MPArray) 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>> files = (MPArray>>) map.get(mp("files")); + if (files != null) { + for (MPMap> 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 unpackParents(MessageUnpacker unpacker) throws IOException { - int size = unpacker.unpackArrayHeader(); - List 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 unpackFiles(MessageUnpacker unpacker) throws IOException { - int size = unpacker.unpackArrayHeader(); - List 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; } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java index d0beddd..6ea174f 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java @@ -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> pack() throws IOException { + MPMap> 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> 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); } } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java index 29595da..b94b5f9 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java +++ b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java @@ -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> 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> map = (MPMap>) 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); } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java index 7c9e13f..96383f7 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java @@ -37,4 +37,8 @@ public class Strings { } return hex; } + + public static String str(Object o) { + return o == null ? null : o.toString(); + } } diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java index 3861831..d915215 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java @@ -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()); } }