Some code for sending acknowledgements
- some of it isn't tested - somehow the ack part seems to be empty, even though the flag should be set
This commit is contained in:
		| @@ -156,6 +156,7 @@ public class BitmessageContext { | |||||||
|                 } else { |                 } else { | ||||||
|                     LOG.info("Sending message."); |                     LOG.info("Sending message."); | ||||||
|                     msg.setStatus(DOING_PROOF_OF_WORK); |                     msg.setStatus(DOING_PROOF_OF_WORK); | ||||||
|  |                     msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX)); | ||||||
|                     ctx.getMessageRepository().save(msg); |                     ctx.getMessageRepository().save(msg); | ||||||
|                     ctx.send( |                     ctx.send( | ||||||
|                             from, |                             from, | ||||||
| @@ -165,9 +166,6 @@ public class BitmessageContext { | |||||||
|                             ctx.getNonceTrialsPerByte(to), |                             ctx.getNonceTrialsPerByte(to), | ||||||
|                             ctx.getExtraBytes(to) |                             ctx.getExtraBytes(to) | ||||||
|                     ); |                     ); | ||||||
|                     msg.setStatus(SENT); |  | ||||||
|                     msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT)); |  | ||||||
|                     ctx.getMessageRepository().save(msg); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -118,15 +118,24 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { | |||||||
|         for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) { |         for (BitmessageAddress identity : ctx.getAddressRepo().getIdentities()) { | ||||||
|             try { |             try { | ||||||
|                 msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); |                 msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); | ||||||
|                 msg.getPlaintext().setTo(identity); |                 Plaintext plaintext = msg.getPlaintext(); | ||||||
|                 if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) { |                 plaintext.setTo(identity); | ||||||
|  |                 if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) { | ||||||
|                     LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); |                     LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); | ||||||
|                 } else { |                 } else { | ||||||
|                     msg.getPlaintext().setStatus(RECEIVED); |                     plaintext.setStatus(RECEIVED); | ||||||
|                     msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); |                     plaintext.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); | ||||||
|                     msg.getPlaintext().setInventoryVector(object.getInventoryVector()); |                     plaintext.setInventoryVector(object.getInventoryVector()); | ||||||
|                     ctx.getMessageRepository().save(msg.getPlaintext()); |                     ctx.getMessageRepository().save(plaintext); | ||||||
|                     listener.receive(msg.getPlaintext()); |                     listener.receive(plaintext); | ||||||
|  |  | ||||||
|  |                     if (identity.has(Pubkey.Feature.DOES_ACK)) { | ||||||
|  |                         ObjectMessage ack = plaintext.getAckMessage(); | ||||||
|  |                         if (ack != null) { | ||||||
|  |                             ctx.getInventory().storeObject(ack); | ||||||
|  |                             ctx.getNetworkHandler().offer(ack.getInventoryVector()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             } catch (DecryptionFailedException ignore) { |             } catch (DecryptionFailedException ignore) { | ||||||
|   | |||||||
| @@ -17,9 +17,8 @@ | |||||||
| package ch.dissem.bitmessage; | package ch.dissem.bitmessage; | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.*; | import ch.dissem.bitmessage.entity.*; | ||||||
| import ch.dissem.bitmessage.entity.payload.Broadcast; | import ch.dissem.bitmessage.entity.payload.*; | ||||||
| import ch.dissem.bitmessage.entity.payload.GetPubkey; | import ch.dissem.bitmessage.entity.valueobject.Label; | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectPayload; |  | ||||||
| import ch.dissem.bitmessage.ports.*; | import ch.dissem.bitmessage.ports.*; | ||||||
| import ch.dissem.bitmessage.utils.Singleton; | import ch.dissem.bitmessage.utils.Singleton; | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; | import ch.dissem.bitmessage.utils.UnixTime; | ||||||
| @@ -29,6 +28,7 @@ import org.slf4j.LoggerFactory; | |||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.util.TreeSet; | import java.util.TreeSet; | ||||||
|  |  | ||||||
|  | import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT; | ||||||
| import static ch.dissem.bitmessage.utils.UnixTime.DAY; | import static ch.dissem.bitmessage.utils.UnixTime.DAY; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -162,39 +162,35 @@ public class InternalContext { | |||||||
|     public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload, |     public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload, | ||||||
|                      final long timeToLive, final long nonceTrialsPerByte, final long extraBytes) { |                      final long timeToLive, final long nonceTrialsPerByte, final long extraBytes) { | ||||||
|         try { |         try { | ||||||
|             if (to == null) to = from; |             final BitmessageAddress recipient = (to != null ? to : from); | ||||||
|             long expires = UnixTime.now(+timeToLive); |             long expires = UnixTime.now(+timeToLive); | ||||||
|             LOG.info("Expires at " + expires); |             LOG.info("Expires at " + expires); | ||||||
|             final ObjectMessage object = new ObjectMessage.Builder() |             final ObjectMessage object = new ObjectMessage.Builder() | ||||||
|                     .stream(to.getStream()) |                     .stream(recipient.getStream()) | ||||||
|                     .expiresTime(expires) |                     .expiresTime(expires) | ||||||
|                     .payload(payload) |                     .payload(payload) | ||||||
|                     .build(); |                     .build(); | ||||||
|             if (object.isSigned()) { |             if (object.isSigned()) { | ||||||
|                 object.sign(from.getPrivateKey()); |                 object.sign(from.getPrivateKey()); | ||||||
|             } |             } | ||||||
|  |             if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) { | ||||||
|  |                 ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage(); | ||||||
|  |                 messageCallback.proofOfWorkStarted(payload); | ||||||
|  |                 security.doProofOfWork(ackMessage, networkNonceTrialsPerByte, networkExtraBytes, new ProofOfWorkEngine.Callback() { | ||||||
|  |                     @Override | ||||||
|  |                     public void onNonceCalculated(byte[] nonce) { | ||||||
|  |                         object.encrypt(recipient.getPubkey()); | ||||||
|  |                         security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, new ProofOfWorkCallback(object, payload)); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } else { | ||||||
|                 if (payload instanceof Broadcast) { |                 if (payload instanceof Broadcast) { | ||||||
|                     ((Broadcast) payload).encrypt(); |                     ((Broadcast) payload).encrypt(); | ||||||
|                 } else if (payload instanceof Encrypted) { |                 } else if (payload instanceof Encrypted) { | ||||||
|                 object.encrypt(to.getPubkey()); |                     object.encrypt(recipient.getPubkey()); | ||||||
|                 } |                 } | ||||||
|             messageCallback.proofOfWorkStarted(payload); |                 security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, new ProofOfWorkCallback(object, payload)); | ||||||
|             security.doProofOfWork(object, nonceTrialsPerByte, extraBytes, |  | ||||||
|                     new ProofOfWorkEngine.Callback() { |  | ||||||
|                         @Override |  | ||||||
|                         public void onNonceCalculated(byte[] nonce) { |  | ||||||
|                             object.setNonce(nonce); |  | ||||||
|                             messageCallback.proofOfWorkCompleted(payload); |  | ||||||
|                             if (payload instanceof PlaintextHolder) { |  | ||||||
|                                 Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext(); |  | ||||||
|                                 plaintext.setInventoryVector(object.getInventoryVector()); |  | ||||||
|                                 messageRepository.save(plaintext); |  | ||||||
|             } |             } | ||||||
|                             inventory.storeObject(object); |  | ||||||
|                             networkHandler.offer(object.getInventoryVector()); |  | ||||||
|                             messageCallback.messageOffered(payload, object.getInventoryVector()); |  | ||||||
|                         } |  | ||||||
|                     }); |  | ||||||
|         } catch (IOException e) { |         } catch (IOException e) { | ||||||
|             throw new RuntimeException(e); |             throw new RuntimeException(e); | ||||||
|         } |         } | ||||||
| @@ -266,4 +262,31 @@ public class InternalContext { | |||||||
|     public interface ContextHolder { |     public interface ContextHolder { | ||||||
|         void setContext(InternalContext context); |         void setContext(InternalContext context); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private class ProofOfWorkCallback implements ProofOfWorkEngine.Callback { | ||||||
|  |         private final ObjectMessage object; | ||||||
|  |         private final ObjectPayload payload; | ||||||
|  |  | ||||||
|  |         private ProofOfWorkCallback(ObjectMessage object, ObjectPayload payload) { | ||||||
|  |             this.object = object; | ||||||
|  |             this.payload = payload; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public void onNonceCalculated(byte[] nonce) { | ||||||
|  |             object.setNonce(nonce); | ||||||
|  |             messageCallback.proofOfWorkCompleted(payload); | ||||||
|  |             if (payload instanceof PlaintextHolder) { | ||||||
|  |                 Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext(); | ||||||
|  |                 plaintext.setInventoryVector(object.getInventoryVector()); | ||||||
|  |                 plaintext.setStatus(SENT); | ||||||
|  |                 plaintext.removeLabel(Label.Type.OUTBOX); | ||||||
|  |                 plaintext.addLabels(messageRepository.getLabels(Label.Type.SENT)); | ||||||
|  |                 messageRepository.save(plaintext); | ||||||
|  |             } | ||||||
|  |             inventory.storeObject(object); | ||||||
|  |             networkHandler.offer(object.getInventoryVector()); | ||||||
|  |             messageCallback.messageOffered(payload, object.getInventoryVector()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
| package ch.dissem.bitmessage.entity; | package ch.dissem.bitmessage.entity; | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; | import ch.dissem.bitmessage.entity.payload.Pubkey; | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; | ||||||
| import ch.dissem.bitmessage.entity.payload.V4Pubkey; | import ch.dissem.bitmessage.entity.payload.V4Pubkey; | ||||||
| import ch.dissem.bitmessage.entity.valueobject.PrivateKey; | import ch.dissem.bitmessage.entity.valueobject.PrivateKey; | ||||||
| import ch.dissem.bitmessage.utils.AccessCounter; | import ch.dissem.bitmessage.utils.AccessCounter; | ||||||
| @@ -220,4 +221,11 @@ public class BitmessageAddress implements Serializable { | |||||||
|     public void setSubscribed(boolean subscribed) { |     public void setSubscribed(boolean subscribed) { | ||||||
|         this.subscribed = subscribed; |         this.subscribed = subscribed; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public boolean has(Feature feature) { | ||||||
|  |         if (pubkey == null || feature == null) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return feature.isActive(pubkey.getBehaviorBitfield()); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; | package ch.dissem.bitmessage.entity; | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Pubkey; | ||||||
| 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.factory.Factory; | import ch.dissem.bitmessage.factory.Factory; | ||||||
| @@ -26,6 +27,8 @@ import ch.dissem.bitmessage.utils.UnixTime; | |||||||
| import java.io.*; | import java.io.*; | ||||||
| import java.util.*; | import java.util.*; | ||||||
|  |  | ||||||
|  | import static ch.dissem.bitmessage.utils.Singleton.security; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * The unencrypted message to be sent by 'msg' or 'broadcast'. |  * The unencrypted message to be sent by 'msg' or 'broadcast'. | ||||||
|  */ |  */ | ||||||
| @@ -34,7 +37,8 @@ public class Plaintext implements Streamable { | |||||||
|     private final BitmessageAddress from; |     private final BitmessageAddress from; | ||||||
|     private final long encoding; |     private final long encoding; | ||||||
|     private final byte[] message; |     private final byte[] message; | ||||||
|     private final byte[] ack; |     private final byte[] ackData; | ||||||
|  |     private ObjectMessage ackMessage; | ||||||
|     private Object id; |     private Object id; | ||||||
|     private InventoryVector inventoryVector; |     private InventoryVector inventoryVector; | ||||||
|     private BitmessageAddress to; |     private BitmessageAddress to; | ||||||
| @@ -53,7 +57,13 @@ public class Plaintext implements Streamable { | |||||||
|         to = builder.to; |         to = builder.to; | ||||||
|         encoding = builder.encoding; |         encoding = builder.encoding; | ||||||
|         message = builder.message; |         message = builder.message; | ||||||
|         ack = builder.ack; |         ackData = builder.ackData; | ||||||
|  |         if (builder.ackMessage != null) { | ||||||
|  |             ackMessage = Factory.getObjectMessage( | ||||||
|  |                     3, | ||||||
|  |                     new ByteArrayInputStream(builder.ackMessage), | ||||||
|  |                     builder.ackMessage.length); | ||||||
|  |         } | ||||||
|         signature = builder.signature; |         signature = builder.signature; | ||||||
|         status = builder.status; |         status = builder.status; | ||||||
|         sent = builder.sent; |         sent = builder.sent; | ||||||
| @@ -159,8 +169,15 @@ public class Plaintext implements Streamable { | |||||||
|         Encode.varInt(message.length, out); |         Encode.varInt(message.length, out); | ||||||
|         out.write(message); |         out.write(message); | ||||||
|         if (type == Type.MSG) { |         if (type == Type.MSG) { | ||||||
|             Encode.varInt(ack.length, out); |             if (to.has(Pubkey.Feature.DOES_ACK)) { | ||||||
|             out.write(ack); |                 ByteArrayOutputStream ack = new ByteArrayOutputStream(); | ||||||
|  |                 ackMessage.write(ack); | ||||||
|  |                 byte[] data = ack.toByteArray(); | ||||||
|  |                 Encode.varInt(data.length, out); | ||||||
|  |                 out.write(data); | ||||||
|  |             } else { | ||||||
|  |                 Encode.varInt(0, out); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         if (includeSignature) { |         if (includeSignature) { | ||||||
|             if (signature == null) { |             if (signature == null) { | ||||||
| @@ -234,7 +251,7 @@ public class Plaintext implements Streamable { | |||||||
|         return Objects.equals(encoding, plaintext.encoding) && |         return Objects.equals(encoding, plaintext.encoding) && | ||||||
|                 Objects.equals(from, plaintext.from) && |                 Objects.equals(from, plaintext.from) && | ||||||
|                 Arrays.equals(message, plaintext.message) && |                 Arrays.equals(message, plaintext.message) && | ||||||
|                 Arrays.equals(ack, plaintext.ack) && |                 Arrays.equals(ackData, plaintext.ackData) && | ||||||
|                 Arrays.equals(to.getRipe(), plaintext.to.getRipe()) && |                 Arrays.equals(to.getRipe(), plaintext.to.getRipe()) && | ||||||
|                 Arrays.equals(signature, plaintext.signature) && |                 Arrays.equals(signature, plaintext.signature) && | ||||||
|                 Objects.equals(status, plaintext.status) && |                 Objects.equals(status, plaintext.status) && | ||||||
| @@ -245,20 +262,45 @@ public class Plaintext implements Streamable { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public int hashCode() { |     public int hashCode() { | ||||||
|         return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels); |         return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addLabels(Label... labels) { |     public void addLabels(Label... labels) { | ||||||
|         if (labels != null) { |         if (labels != null) { | ||||||
|             Collections.addAll(this.labels, labels); |             for (Label label : labels) { | ||||||
|  |                 this.labels.add(label); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addLabels(Collection<Label> labels) { |     public void addLabels(Collection<Label> labels) { | ||||||
|         if (labels != null) { |         if (labels != null) { | ||||||
|             this.labels.addAll(labels); |             for (Label label : labels) { | ||||||
|  |                 this.labels.add(label); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void removeLabel(Label.Type type) { | ||||||
|  |         Iterator<Label> iterator = labels.iterator(); | ||||||
|  |         while (iterator.hasNext()) { | ||||||
|  |             Label label = iterator.next(); | ||||||
|  |             if (label.getType() == type) { | ||||||
|  |                 iterator.remove(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public byte[] getAckData() { | ||||||
|  |         return ackData; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public ObjectMessage getAckMessage() { | ||||||
|  |         if (ackMessage == null) { | ||||||
|  |             ackMessage = Factory.createAck(this); | ||||||
|  |         } | ||||||
|  |         return ackMessage; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public enum Encoding { |     public enum Encoding { | ||||||
|         IGNORE(0), TRIVIAL(1), SIMPLE(2); |         IGNORE(0), TRIVIAL(1), SIMPLE(2); | ||||||
| @@ -304,7 +346,8 @@ public class Plaintext implements Streamable { | |||||||
|         private byte[] destinationRipe; |         private byte[] destinationRipe; | ||||||
|         private long encoding; |         private long encoding; | ||||||
|         private byte[] message = new byte[0]; |         private byte[] message = new byte[0]; | ||||||
|         private byte[] ack = new byte[0]; |         private byte[] ackData; | ||||||
|  |         private byte[] ackMessage; | ||||||
|         private byte[] signature; |         private byte[] signature; | ||||||
|         private long sent; |         private long sent; | ||||||
|         private long received; |         private long received; | ||||||
| @@ -405,7 +448,13 @@ public class Plaintext implements Streamable { | |||||||
|  |  | ||||||
|         public Builder ack(byte[] ack) { |         public Builder ack(byte[] ack) { | ||||||
|             if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg"); |             if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg"); | ||||||
|             this.ack = ack; |             this.ackMessage = ack; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Builder ackData(byte[] ackData) { | ||||||
|  |             if (type != Type.MSG && ackData != null) throw new IllegalArgumentException("ack only allowed for msg"); | ||||||
|  |             this.ackData = ackData; | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -449,6 +498,9 @@ public class Plaintext implements Streamable { | |||||||
|             if (to == null && type != Type.BROADCAST) { |             if (to == null && type != Type.BROADCAST) { | ||||||
|                 to = new BitmessageAddress(0, 0, destinationRipe); |                 to = new BitmessageAddress(0, 0, destinationRipe); | ||||||
|             } |             } | ||||||
|  |             if (type == Type.MSG && ackMessage == null && ackData == null) { | ||||||
|  |                 ackData = security().randomBytes(32); | ||||||
|  |             } | ||||||
|             return new Plaintext(this); |             return new Plaintext(this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | package ch.dissem.bitmessage.entity.payload; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.OutputStream; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Created by chrigu on 06.11.15. | ||||||
|  |  */ | ||||||
|  | public class Ack extends ObjectPayload { | ||||||
|  |     private final long stream; | ||||||
|  |     private final byte[] data; | ||||||
|  |  | ||||||
|  |     public Ack(long version, long stream, byte[] data) { | ||||||
|  |         super(version); | ||||||
|  |         this.stream = stream; | ||||||
|  |         this.data = data; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public ObjectType getType() { | ||||||
|  |         return ObjectType.MSG; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long getStream() { | ||||||
|  |         return stream; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void write(OutputStream out) throws IOException { | ||||||
|  |         out.write(data); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -103,5 +103,9 @@ public abstract class Pubkey extends ObjectPayload { | |||||||
|             } |             } | ||||||
|             return features.toArray(new Feature[features.size()]); |             return features.toArray(new Feature[features.size()]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public boolean isActive(int bitfield) { | ||||||
|  |             return (bitfield & bit) != 0; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -79,6 +79,7 @@ public class Label implements Serializable { | |||||||
|         INBOX, |         INBOX, | ||||||
|         BROADCAST, |         BROADCAST, | ||||||
|         DRAFT, |         DRAFT, | ||||||
|  |         OUTBOX, | ||||||
|         SENT, |         SENT, | ||||||
|         UNREAD, |         UNREAD, | ||||||
|         TRASH |         TRASH | ||||||
|   | |||||||
| @@ -203,4 +203,11 @@ public class Factory { | |||||||
|             return new V5Broadcast(sendingAddress, plaintext); |             return new V5Broadcast(sendingAddress, plaintext); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static ObjectMessage createAck(Plaintext plaintext) { | ||||||
|  |         if (plaintext == null || plaintext.getAckData() == null) | ||||||
|  |             return null; | ||||||
|  |         Ack ack = new Ack(3, plaintext.getFrom().getStream(), plaintext.getAckData()); | ||||||
|  |         return new ObjectMessage.Builder().payload(ack).build(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user