mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-27 14:32:06 +01:00
Refactor JET in order to match spec
This commit is contained in:
parent
5edd630fd0
commit
eb413f30b1
9 changed files with 78 additions and 70 deletions
|
@ -50,8 +50,8 @@ public final class JetManager extends Manager implements JingleDescriptionManage
|
|||
private static final Logger LOGGER = Logger.getLogger(JetManager.class.getName());
|
||||
|
||||
private static final WeakHashMap<XMPPConnection, JetManager> INSTANCES = new WeakHashMap<>();
|
||||
private static final HashMap<String, JingleEncryptionMethod> encryptionMethods = new HashMap<>();
|
||||
private static final HashMap<String, ExtensionElementProvider<?>> encryptionMethodProviders = new HashMap<>();
|
||||
private static final HashMap<String, JingleEnvelopeManager> envelopeManagers = new HashMap<>();
|
||||
private static final HashMap<String, ExtensionElementProvider<?>> envelopeProviders = new HashMap<>();
|
||||
|
||||
private final JingleManager jingleManager;
|
||||
|
||||
|
@ -78,17 +78,17 @@ public final class JetManager extends Manager implements JingleDescriptionManage
|
|||
return manager;
|
||||
}
|
||||
|
||||
public OutgoingFileOfferController sendEncryptedFile(File file, FullJid recipient, JingleEncryptionMethod method) throws Exception {
|
||||
return sendEncryptedFile(file, null, recipient, method);
|
||||
public OutgoingFileOfferController sendEncryptedFile(File file, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
|
||||
return sendEncryptedFile(file, null, recipient, envelopeManager);
|
||||
}
|
||||
|
||||
public OutgoingFileOfferController sendEncryptedFile(File file, String filename, FullJid recipient, JingleEncryptionMethod method) throws Exception {
|
||||
public OutgoingFileOfferController sendEncryptedFile(File file, String filename, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
|
||||
if (file == null || !file.exists()) {
|
||||
throw new IllegalArgumentException("File MUST NOT be null and MUST exist.");
|
||||
}
|
||||
|
||||
ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||
if (!disco.supportsFeature(recipient, getNamespace()) || !disco.supportsFeature(recipient, method.getNamespace())) {
|
||||
if (!disco.supportsFeature(recipient, getNamespace()) || !disco.supportsFeature(recipient, envelopeManager.getJingleEnvelopeNamespace())) {
|
||||
throw new SmackException.FeatureNotSupportedException(getNamespace(), recipient);
|
||||
}
|
||||
|
||||
|
@ -106,35 +106,35 @@ public final class JetManager extends Manager implements JingleDescriptionManage
|
|||
JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient);
|
||||
content.setTransport(transportManager.createTransportForInitiator(content));
|
||||
|
||||
JetSecurity security = new JetSecurity(method, recipient, content.getName(), Aes256GcmNoPadding.NAMESPACE);
|
||||
JetSecurity security = new JetSecurity(envelopeManager, recipient, content.getName(), Aes256GcmNoPadding.NAMESPACE);
|
||||
content.setSecurity(security);
|
||||
session.sendInitiate(connection());
|
||||
|
||||
return offer;
|
||||
}
|
||||
|
||||
public void registerEncryptionMethod(JingleEncryptionMethod method) {
|
||||
encryptionMethods.put(method.getNamespace(), method);
|
||||
public void registerEnvelopeManager(JingleEnvelopeManager method) {
|
||||
envelopeManagers.put(method.getJingleEnvelopeNamespace(), method);
|
||||
}
|
||||
|
||||
public void unregisterEncryptionMethod(String namespace) {
|
||||
encryptionMethods.remove(namespace);
|
||||
public void unregisterEnvelopeManager(String namespace) {
|
||||
envelopeManagers.remove(namespace);
|
||||
}
|
||||
|
||||
public JingleEncryptionMethod getEncryptionMethod(String namespace) {
|
||||
return encryptionMethods.get(namespace);
|
||||
public JingleEnvelopeManager getEnvelopeManager(String namespace) {
|
||||
return envelopeManagers.get(namespace);
|
||||
}
|
||||
|
||||
public static void registerEncryptionMethodProvider(String namespace, ExtensionElementProvider<?> provider) {
|
||||
encryptionMethodProviders.put(namespace, provider);
|
||||
public static void registerEnvelopeProvider(String namespace, ExtensionElementProvider<?> provider) {
|
||||
envelopeProviders.put(namespace, provider);
|
||||
}
|
||||
|
||||
public static void removeEncryptionMethodProvider(String namespace) {
|
||||
encryptionMethodProviders.remove(namespace);
|
||||
public static void unregisterEnvelopeProvider(String namespace) {
|
||||
envelopeProviders.remove(namespace);
|
||||
}
|
||||
|
||||
public static ExtensionElementProvider<?> getEncryptionMethodProvider(String namespace) {
|
||||
return encryptionMethodProviders.get(namespace);
|
||||
public static ExtensionElementProvider<?> getEnvelopeProvider(String namespace) {
|
||||
return envelopeProviders.get(namespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -28,11 +28,15 @@ import org.jxmpp.jid.FullJid;
|
|||
/**
|
||||
* Classes that implement this interface can be used to encrypt Jingle File Transfers.
|
||||
*/
|
||||
public interface JingleEncryptionMethod {
|
||||
public interface JingleEnvelopeManager {
|
||||
|
||||
ExtensionElement encryptJingleTransfer(FullJid recipient, byte[] keyData) throws JingleEncryptionException, InterruptedException, NoSuchAlgorithmException, SmackException.NotConnectedException, SmackException.NoResponseException;
|
||||
ExtensionElement encryptJingleTransfer(FullJid recipient, byte[] keyData)
|
||||
throws JingleEncryptionException, InterruptedException, NoSuchAlgorithmException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException;
|
||||
|
||||
byte[] decryptJingleTransfer(FullJid sender, ExtensionElement encryptionElement) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException;
|
||||
byte[] decryptJingleTransfer(FullJid sender, ExtensionElement envelope)
|
||||
throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException;
|
||||
|
||||
class JingleEncryptionException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -44,5 +48,5 @@ public interface JingleEncryptionMethod {
|
|||
|
||||
XMPPConnection getConnection();
|
||||
|
||||
String getNamespace();
|
||||
String getJingleEnvelopeNamespace();
|
||||
}
|
|
@ -31,7 +31,7 @@ import org.jivesoftware.smack.packet.ExtensionElement;
|
|||
import org.jivesoftware.smackx.bytestreams.BytestreamSession;
|
||||
import org.jivesoftware.smackx.ciphers.AesGcmNoPadding;
|
||||
import org.jivesoftware.smackx.jet.JetManager;
|
||||
import org.jivesoftware.smackx.jet.JingleEncryptionMethod;
|
||||
import org.jivesoftware.smackx.jet.JingleEnvelopeManager;
|
||||
import org.jivesoftware.smackx.jet.element.JetSecurityElement;
|
||||
import org.jivesoftware.smackx.jingle.callbacks.JingleSecurityCallback;
|
||||
import org.jivesoftware.smackx.jingle.component.JingleSecurity;
|
||||
|
@ -49,7 +49,7 @@ public class JetSecurity extends JingleSecurity<JetSecurityElement> {
|
|||
public static final String NAMESPACE_V0 = "urn:xmpp:jingle:jet:0";
|
||||
public static final String NAMESPACE = NAMESPACE_V0;
|
||||
|
||||
private final String methodNamespace;
|
||||
private final String envelopeNamespace;
|
||||
|
||||
private AesGcmNoPadding aesKey;
|
||||
private final ExtensionElement child;
|
||||
|
@ -59,26 +59,26 @@ public class JetSecurity extends JingleSecurity<JetSecurityElement> {
|
|||
public JetSecurity(JetSecurityElement element) {
|
||||
super();
|
||||
this.child = element.getChild();
|
||||
this.methodNamespace = element.getMethodNamespace();
|
||||
this.envelopeNamespace = element.getEnvelopeNamespace();
|
||||
this.contentName = element.getContentName();
|
||||
this.cipherName = element.getCipherName();
|
||||
}
|
||||
|
||||
public JetSecurity(JingleEncryptionMethod method, FullJid recipient, String contentName, String cipherName)
|
||||
public JetSecurity(JingleEnvelopeManager envelopeManager, FullJid recipient, String contentName, String cipherName)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
|
||||
InvalidAlgorithmParameterException, InvalidKeyException, InterruptedException,
|
||||
JingleEncryptionMethod.JingleEncryptionException, SmackException.NotConnectedException,
|
||||
JingleEnvelopeManager.JingleEncryptionException, SmackException.NotConnectedException,
|
||||
SmackException.NoResponseException {
|
||||
|
||||
this.methodNamespace = method.getNamespace();
|
||||
this.envelopeNamespace = envelopeManager.getJingleEnvelopeNamespace();
|
||||
this.aesKey = AesGcmNoPadding.createEncryptionKey(cipherName);
|
||||
this.child = method.encryptJingleTransfer(recipient, aesKey.getKeyAndIv());
|
||||
this.child = envelopeManager.encryptJingleTransfer(recipient, aesKey.getKeyAndIv());
|
||||
this.contentName = contentName;
|
||||
this.cipherName = cipherName;
|
||||
}
|
||||
|
||||
private void decryptEncryptionKey(JingleEncryptionMethod method, FullJid sender)
|
||||
throws InterruptedException, JingleEncryptionMethod.JingleEncryptionException, XMPPException.XMPPErrorException,
|
||||
private void decryptEncryptionKey(JingleEnvelopeManager method, FullJid sender)
|
||||
throws InterruptedException, JingleEnvelopeManager.JingleEncryptionException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException, NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, NoSuchProviderException, InvalidKeyException, NoSuchPaddingException {
|
||||
byte[] keyAndIv = method.decryptJingleTransfer(sender, child);
|
||||
|
@ -125,18 +125,18 @@ public class JetSecurity extends JingleSecurity<JetSecurityElement> {
|
|||
return;
|
||||
}
|
||||
|
||||
JingleEncryptionMethod method = JetManager.getInstanceFor(connection).getEncryptionMethod(getMethodNamespace());
|
||||
JingleEnvelopeManager method = JetManager.getInstanceFor(connection).getEnvelopeManager(getEnvelopeNamespace());
|
||||
if (method == null) {
|
||||
throw new AssertionError("No JingleEncryptionMethodManager found for " + getMethodNamespace());
|
||||
throw new AssertionError("No JingleEncryptionMethodManager found for " + getEnvelopeNamespace());
|
||||
}
|
||||
try {
|
||||
decryptEncryptionKey(method, sender);
|
||||
} catch (InterruptedException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | JingleEncryptionMethod.JingleEncryptionException e) {
|
||||
} catch (InterruptedException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | JingleEnvelopeManager.JingleEncryptionException e) {
|
||||
LOGGER.log(Level.SEVERE, "Could not decrypt security key: " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMethodNamespace() {
|
||||
return methodNamespace;
|
||||
public String getEnvelopeNamespace() {
|
||||
return envelopeNamespace;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,16 +32,16 @@ import org.jivesoftware.smackx.jingle.element.JingleContentSecurityElement;
|
|||
* </jingle>
|
||||
*/
|
||||
public class JetSecurityElement extends JingleContentSecurityElement {
|
||||
public static final String ATTR_NAME = "name";
|
||||
public static final String ATTR_TYPE = "type";
|
||||
public static final String ATTR_CIPHER = "cipher";
|
||||
public static final String ATTR_CONTENT_NAME = "name";
|
||||
public static final String ATTR_ENVELOPE_TYPE = "type";
|
||||
public static final String ATTR_CIPHER_TYPE = "cipher";
|
||||
|
||||
private final ExtensionElement child;
|
||||
private final String name;
|
||||
private final String contentName;
|
||||
private final String cipherName;
|
||||
|
||||
public JetSecurityElement(String name, String cipherName, ExtensionElement child) {
|
||||
this.name = name;
|
||||
public JetSecurityElement(String contentName, String cipherName, ExtensionElement child) {
|
||||
this.contentName = contentName;
|
||||
this.child = child;
|
||||
this.cipherName = cipherName;
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ public class JetSecurityElement extends JingleContentSecurityElement {
|
|||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.attribute(ATTR_NAME, name)
|
||||
.attribute(ATTR_CIPHER, cipherName)
|
||||
.attribute(ATTR_TYPE, child.getNamespace());
|
||||
xml.attribute(ATTR_CONTENT_NAME, contentName)
|
||||
.attribute(ATTR_CIPHER_TYPE, cipherName)
|
||||
.attribute(ATTR_ENVELOPE_TYPE, child.getNamespace());
|
||||
xml.rightAngleBracket();
|
||||
xml.element(child);
|
||||
xml.closeElement(this);
|
||||
|
@ -63,7 +63,7 @@ public class JetSecurityElement extends JingleContentSecurityElement {
|
|||
return JetSecurity.NAMESPACE;
|
||||
}
|
||||
|
||||
public String getMethodNamespace() {
|
||||
public String getEnvelopeNamespace() {
|
||||
return child.getNamespace();
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ public class JetSecurityElement extends JingleContentSecurityElement {
|
|||
}
|
||||
|
||||
public String getContentName() {
|
||||
return name;
|
||||
return contentName;
|
||||
}
|
||||
|
||||
public String getCipherName() {
|
||||
|
|
|
@ -37,16 +37,16 @@ public class JetSecurityProvider extends JingleContentSecurityProvider<JetSecuri
|
|||
|
||||
@Override
|
||||
public JetSecurityElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
String name = parser.getAttributeValue("", JetSecurityElement.ATTR_NAME);
|
||||
String cipher = parser.getAttributeValue("", JetSecurityElement.ATTR_CIPHER);
|
||||
String type = parser.getAttributeValue("", JetSecurityElement.ATTR_TYPE);
|
||||
String name = parser.getAttributeValue("", JetSecurityElement.ATTR_CONTENT_NAME);
|
||||
String cipher = parser.getAttributeValue("", JetSecurityElement.ATTR_CIPHER_TYPE);
|
||||
String type = parser.getAttributeValue("", JetSecurityElement.ATTR_ENVELOPE_TYPE);
|
||||
ExtensionElement child;
|
||||
|
||||
Objects.requireNonNull(type);
|
||||
Objects.requireNonNull(cipher);
|
||||
|
||||
ExtensionElementProvider<?> encryptionElementProvider =
|
||||
JetManager.getEncryptionMethodProvider(type);
|
||||
JetManager.getEnvelopeProvider(type);
|
||||
|
||||
if (encryptionElementProvider != null) {
|
||||
child = encryptionElementProvider.parse(parser);
|
||||
|
|
|
@ -38,13 +38,13 @@ import org.xml.sax.SAXException;
|
|||
public class JetElementTest extends SmackTestSuite {
|
||||
|
||||
@Test
|
||||
public void jetTest() throws InterruptedException, JingleEncryptionMethod.JingleEncryptionException, NoSuchAlgorithmException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException, SAXException {
|
||||
public void jetTest() throws InterruptedException, JingleEnvelopeManager.JingleEncryptionException, NoSuchAlgorithmException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException, SAXException {
|
||||
ExtensionElement child = new SecurityStub().encryptJingleTransfer(null, null);
|
||||
JetSecurityElement element = new JetSecurityElement("content1", Aes128GcmNoPadding.NAMESPACE, child);
|
||||
JetSecurity security = new JetSecurity(element);
|
||||
assertEquals(SecurityStub.NAMESPACE, security.getMethodNamespace());
|
||||
assertEquals(SecurityStub.NAMESPACE, security.getEnvelopeNamespace());
|
||||
assertEquals(Aes128GcmNoPadding.NAMESPACE, element.getCipherName());
|
||||
assertEquals(SecurityStub.NAMESPACE, element.getMethodNamespace());
|
||||
assertEquals(SecurityStub.NAMESPACE, element.getEnvelopeNamespace());
|
||||
assertEquals("content1", element.getContentName());
|
||||
|
||||
String xml = "<security xmlns='" + JetSecurity.NAMESPACE + "' " +
|
||||
|
@ -56,7 +56,7 @@ public class JetElementTest extends SmackTestSuite {
|
|||
assertXMLEqual(xml, security.getElement().toXML().toString());
|
||||
}
|
||||
|
||||
private static class SecurityStub implements JingleEncryptionMethod {
|
||||
private static class SecurityStub implements JingleEnvelopeManager {
|
||||
public static final String NAMESPACE = "urn:xmpp:security-stub";
|
||||
|
||||
@Override
|
||||
|
@ -80,7 +80,7 @@ public class JetElementTest extends SmackTestSuite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public byte[] decryptJingleTransfer(FullJid sender, ExtensionElement encryptionElement) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
public byte[] decryptJingleTransfer(FullJid sender, ExtensionElement envelope) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public class JetElementTest extends SmackTestSuite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
public String getJingleEnvelopeNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,7 +298,7 @@ public class JingleContent implements JingleTransportCallback, JingleSecurityCal
|
|||
.setName(name)
|
||||
.setCreator(creator)
|
||||
.setSenders(senders)
|
||||
.setDisposition(disposition);;
|
||||
.setDisposition(disposition);
|
||||
|
||||
if (description != null) {
|
||||
builder.setDescription(description.getElement());
|
||||
|
|
|
@ -80,7 +80,7 @@ public class JetIntegrationTest extends AbstractOmemoIntegrationTest {
|
|||
jb = JetManager.getInstanceFor(conTwo);
|
||||
ia = JingleIBBTransportManager.getInstanceFor(conOne);
|
||||
ib = JingleIBBTransportManager.getInstanceFor(conTwo);
|
||||
JetManager.registerEncryptionMethodProvider(OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL, new OmemoVAxolotlProvider());
|
||||
JetManager.registerEnvelopeProvider(OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL, new OmemoVAxolotlProvider());
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
|
@ -97,8 +97,8 @@ public class JetIntegrationTest extends AbstractOmemoIntegrationTest {
|
|||
unidirectionalTrust(oa, ob);
|
||||
unidirectionalTrust(ob, oa);
|
||||
|
||||
ja.registerEncryptionMethod(oa);
|
||||
jb.registerEncryptionMethod(ob);
|
||||
ja.registerEnvelopeManager(oa);
|
||||
jb.registerEnvelopeManager(ob);
|
||||
|
||||
File source = prepareNewTestFile("source");
|
||||
final File target = new File(tempDir, "target");
|
||||
|
|
|
@ -46,7 +46,7 @@ import org.jivesoftware.smackx.carbons.CarbonManager;
|
|||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
|
||||
import org.jivesoftware.smackx.hints.element.StoreHint;
|
||||
import org.jivesoftware.smackx.jet.JingleEncryptionMethod;
|
||||
import org.jivesoftware.smackx.jet.JingleEnvelopeManager;
|
||||
import org.jivesoftware.smackx.mam.MamManager;
|
||||
import org.jivesoftware.smackx.muc.MultiUserChat;
|
||||
import org.jivesoftware.smackx.muc.MultiUserChatManager;
|
||||
|
@ -91,7 +91,7 @@ import org.jxmpp.stringprep.XmppStringprepException;
|
|||
* @author Paul Schaub
|
||||
*/
|
||||
|
||||
public final class OmemoManager extends Manager implements JingleEncryptionMethod {
|
||||
public final class OmemoManager extends Manager implements JingleEnvelopeManager {
|
||||
private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName());
|
||||
|
||||
private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
|
||||
|
@ -760,11 +760,15 @@ public final class OmemoManager extends Manager implements JingleEncryptionMetho
|
|||
return connection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
public static String getNamespace() {
|
||||
return OMEMO_NAMESPACE_V_AXOLOTL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJingleEnvelopeNamespace() {
|
||||
return getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OMEMO service object.
|
||||
*
|
||||
|
@ -870,13 +874,13 @@ public final class OmemoManager extends Manager implements JingleEncryptionMetho
|
|||
}
|
||||
|
||||
@Override
|
||||
public byte[] decryptJingleTransfer(FullJid sender, ExtensionElement encryptionElement) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
if (!encryptionElement.getNamespace().equals(OMEMO_NAMESPACE_V_AXOLOTL)
|
||||
|| !encryptionElement.getElementName().equals(OmemoElement.ENCRYPTED)) {
|
||||
public byte[] decryptJingleTransfer(FullJid sender, ExtensionElement envelope) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
if (!envelope.getNamespace().equals(OMEMO_NAMESPACE_V_AXOLOTL)
|
||||
|| !envelope.getElementName().equals(OmemoElement.ENCRYPTED)) {
|
||||
throw new IllegalArgumentException("Passed ExtensionElement MUST be an OmemoElement!");
|
||||
}
|
||||
|
||||
OmemoElement omemoElement = (OmemoElement) encryptionElement;
|
||||
OmemoElement omemoElement = (OmemoElement) envelope;
|
||||
Message pseudoMessage = new Message();
|
||||
pseudoMessage.setFrom(sender.asBareJid());
|
||||
pseudoMessage.addExtension(omemoElement);
|
||||
|
|
Loading…
Reference in a new issue