mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 05:09:53 +01:00
Separate the sync layer from its clients. #112
This commit is contained in:
@@ -2,88 +2,206 @@ package org.briarproject.messaging;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.messaging.MessagingManager;
|
||||
import org.briarproject.api.messaging.PrivateConversation;
|
||||
import org.briarproject.api.messaging.PrivateMessage;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupFactory;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageHeader;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
// Temporary facade during sync protocol refactoring
|
||||
class MessagingManagerImpl implements MessagingManager {
|
||||
|
||||
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||
"6bcdc006c0910b0f44e40644c3b31f1a"
|
||||
+ "8bf9a6d6021d40d219c86b731b903070"));
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(MessagingManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent crypto;
|
||||
private final GroupFactory groupFactory;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MetadataParser metadataParser;
|
||||
|
||||
@Inject
|
||||
MessagingManagerImpl(DatabaseComponent db, CryptoComponent crypto,
|
||||
GroupFactory groupFactory) {
|
||||
MessagingManagerImpl(DatabaseComponent db, GroupFactory groupFactory,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
|
||||
MetadataParser metadataParser) {
|
||||
this.db = db;
|
||||
this.crypto = crypto;
|
||||
this.groupFactory = groupFactory;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.metadataParser = metadataParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addContact(ContactId c, SecretKey master) throws DbException {
|
||||
byte[] salt = crypto.deriveGroupSalt(master);
|
||||
Group inbox = groupFactory.createGroup("Inbox", salt);
|
||||
db.addGroup(inbox);
|
||||
db.setInboxGroup(c, inbox);
|
||||
public ClientId getClientId() {
|
||||
return CLIENT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalMessage(Message m) throws DbException {
|
||||
db.addLocalMessage(m);
|
||||
public void addContact(ContactId c) throws DbException {
|
||||
// Create the conversation group
|
||||
Group conversation = createConversationGroup(db.getContact(c));
|
||||
// Subscribe to the group and share it with the contact
|
||||
db.addGroup(conversation);
|
||||
db.addGroup(c, conversation);
|
||||
db.setVisibility(conversation.getId(), Collections.singletonList(c));
|
||||
}
|
||||
|
||||
private Group createConversationGroup(Contact c) {
|
||||
AuthorId local = c.getLocalAuthorId();
|
||||
AuthorId remote = c.getAuthor().getId();
|
||||
byte[] descriptor = createGroupDescriptor(local, remote);
|
||||
return groupFactory.createGroup(CLIENT_ID, descriptor);
|
||||
}
|
||||
|
||||
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
w.writeListStart();
|
||||
if (UniqueId.IdComparator.INSTANCE.compare(local, remote) < 0) {
|
||||
w.writeRaw(local.getBytes());
|
||||
w.writeRaw(remote.getBytes());
|
||||
} else {
|
||||
w.writeRaw(remote.getBytes());
|
||||
w.writeRaw(local.getBytes());
|
||||
}
|
||||
w.writeListEnd();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateConversation getConversation(GroupId g) throws DbException {
|
||||
return new PrivateConversationImpl(db.getGroup(g));
|
||||
public void addLocalMessage(PrivateMessage m) throws DbException {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("timestamp", m.getMessage().getTimestamp());
|
||||
if (m.getParent() != null) d.put("parent", m.getParent().getBytes());
|
||||
d.put("contentType", m.getContentType());
|
||||
d.put("local", true);
|
||||
d.put("read", true);
|
||||
try {
|
||||
Metadata meta = metadataEncoder.encode(d);
|
||||
db.addLocalMessage(m.getMessage(), CLIENT_ID, meta);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId getContactId(GroupId g) throws DbException {
|
||||
// TODO: Make this more efficient
|
||||
for (Contact c : db.getContacts()) {
|
||||
Group conversation = createConversationGroup(c);
|
||||
if (conversation.getId().equals(g)) return c.getId();
|
||||
}
|
||||
throw new NoSuchContactException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupId getConversationId(ContactId c) throws DbException {
|
||||
return db.getInboxGroupId(c);
|
||||
// TODO: Make this more efficient
|
||||
return createConversationGroup(db.getContact(c)).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
|
||||
throws DbException {
|
||||
Collection<MessageHeader> headers = db.getInboxMessageHeaders(c);
|
||||
List<PrivateMessageHeader> privateHeaders =
|
||||
new ArrayList<PrivateMessageHeader>(headers.size());
|
||||
for (MessageHeader m : headers)
|
||||
privateHeaders.add(new PrivateMessageHeaderImpl(m));
|
||||
return Collections.unmodifiableList(privateHeaders);
|
||||
GroupId groupId = getConversationId(c);
|
||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(groupId);
|
||||
Collection<MessageStatus> statuses = db.getMessageStatus(c, groupId);
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
new ArrayList<PrivateMessageHeader>();
|
||||
for (MessageStatus s : statuses) {
|
||||
MessageId id = s.getMessageId();
|
||||
Metadata m = metadata.get(id);
|
||||
if (m == null) continue;
|
||||
try {
|
||||
BdfDictionary d = metadataParser.parse(m);
|
||||
long timestamp = d.getInteger("timestamp");
|
||||
String contentType = d.getString("contentType");
|
||||
boolean local = d.getBoolean("local");
|
||||
boolean read = d.getBoolean("read");
|
||||
headers.add(new PrivateMessageHeader(id, timestamp, contentType,
|
||||
local, read, s.isSent(), s.isSeen()));
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMessageBody(MessageId m) throws DbException {
|
||||
return db.getMessageBody(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(ContactId c, PrivateConversation p)
|
||||
throws DbException {
|
||||
db.setInboxGroup(c, ((PrivateConversationImpl) p).getGroup());
|
||||
byte[] raw = db.getRawMessage(m);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
// Extract the private message body
|
||||
r.readListStart();
|
||||
if (r.hasRaw()) r.skipRaw(); // Parent ID
|
||||
else r.skipNull(); // No parent
|
||||
r.skipString(); // Content type
|
||||
return r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
} catch (FormatException e) {
|
||||
// Not a valid private message
|
||||
throw new IllegalArgumentException();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||
db.setReadFlag(m, read);
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("read", read);
|
||||
try {
|
||||
db.mergeMessageMetadata(m, metadataEncoder.encode(d));
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.messaging.MessagingManager;
|
||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class MessagingModule extends AbstractModule {
|
||||
|
||||
@@ -12,4 +20,15 @@ public class MessagingModule extends AbstractModule {
|
||||
bind(MessagingManager.class).to(MessagingManagerImpl.class);
|
||||
bind(PrivateMessageFactory.class).to(PrivateMessageFactoryImpl.class);
|
||||
}
|
||||
|
||||
@Provides @Singleton
|
||||
PrivateMessageValidator getValidator(LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager,
|
||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
||||
Clock clock) {
|
||||
PrivateMessageValidator validator = new PrivateMessageValidator(
|
||||
validationManager, bdfReaderFactory, metadataEncoder, clock);
|
||||
lifecycleManager.register(validator);
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import org.briarproject.api.messaging.PrivateConversation;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
// Temporary facade during sync protocol refactoring
|
||||
class PrivateConversationImpl implements PrivateConversation {
|
||||
|
||||
private final Group group;
|
||||
|
||||
PrivateConversationImpl(Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupId getId() {
|
||||
return group.getId();
|
||||
}
|
||||
|
||||
Group getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return group.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof PrivateConversationImpl
|
||||
&& group.equals(((PrivateConversationImpl) o).group);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,56 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.briarproject.api.messaging.PrivateConversation;
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.messaging.PrivateMessage;
|
||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageFactory;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
// Temporary facade during sync protocol refactoring
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
|
||||
class PrivateMessageFactoryImpl implements PrivateMessageFactory {
|
||||
|
||||
private final MessageFactory messageFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
|
||||
@Inject
|
||||
PrivateMessageFactoryImpl(MessageFactory messageFactory) {
|
||||
PrivateMessageFactoryImpl(MessageFactory messageFactory,
|
||||
BdfWriterFactory bdfWriterFactory) {
|
||||
this.messageFactory = messageFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createPrivateMessage(MessageId parent,
|
||||
PrivateConversation conversation, String contentType,
|
||||
long timestamp, byte[] body)
|
||||
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||
MessageId parent, String contentType, byte[] body)
|
||||
throws IOException, GeneralSecurityException {
|
||||
return messageFactory.createAnonymousMessage(parent,
|
||||
((PrivateConversationImpl) conversation).getGroup(),
|
||||
contentType, timestamp, body);
|
||||
// Validate the arguments
|
||||
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (body.length > MAX_PRIVATE_MESSAGE_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
// Serialise the message
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
w.writeListStart();
|
||||
if (parent == null) w.writeNull();
|
||||
else w.writeRaw(parent.getBytes());
|
||||
w.writeString(contentType);
|
||||
w.writeRaw(body);
|
||||
w.writeListEnd();
|
||||
Message m = messageFactory.createMessage(groupId, timestamp,
|
||||
out.toByteArray());
|
||||
return new PrivateMessage(m, parent, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.api.sync.MessageHeader;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
// Temporary facade during sync protocol refactoring
|
||||
public class PrivateMessageHeaderImpl implements PrivateMessageHeader {
|
||||
|
||||
private final MessageHeader messageHeader;
|
||||
|
||||
PrivateMessageHeaderImpl(MessageHeader messageHeader) {
|
||||
this.messageHeader = messageHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageId getId() {
|
||||
return messageHeader.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Author getAuthor() {
|
||||
return messageHeader.getAuthor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return messageHeader.getContentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return messageHeader.getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return messageHeader.isLocal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRead() {
|
||||
return messageHeader.isRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
switch (messageHeader.getStatus()) {
|
||||
case STORED:
|
||||
return Status.STORED;
|
||||
case SENT:
|
||||
return Status.SENT;
|
||||
case DELIVERED:
|
||||
return Status.DELIVERED;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static org.briarproject.messaging.MessagingManagerImpl.CLIENT_ID;
|
||||
|
||||
class PrivateMessageValidator implements MessageValidator {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PrivateMessageValidator.class.getName());
|
||||
|
||||
private final ValidationManager validationManager;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
PrivateMessageValidator(ValidationManager validationManager,
|
||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
||||
Clock clock) {
|
||||
this.validationManager = validationManager;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
validationManager.setMessageValidator(CLIENT_ID, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
LOG.info("Timestamp is too far in the future");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Parse the message body
|
||||
byte[] raw = m.getRaw();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
MessageId parent = null;
|
||||
String contentType;
|
||||
r.readListStart();
|
||||
// Read the parent ID, if any
|
||||
if (r.hasRaw()) {
|
||||
byte[] id = r.readRaw(UniqueId.LENGTH);
|
||||
if (id.length < UniqueId.LENGTH) throw new FormatException();
|
||||
parent = new MessageId(id);
|
||||
} else {
|
||||
r.readNull();
|
||||
}
|
||||
// Read the content type
|
||||
contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
||||
// Read the private message body
|
||||
r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("timestamp", m.getTimestamp());
|
||||
if (parent != null) d.put("parent", parent.getBytes());
|
||||
d.put("contentType", contentType);
|
||||
d.put("local", false);
|
||||
d.put("read", false);
|
||||
return metadataEncoder.encode(d);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Invalid private message");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user