OmemoManager.java

  1. /**
  2.  *
  3.  * Copyright 2017 Paul Schaub, 2020 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.omemo;

  18. import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL;

  19. import java.io.IOException;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.util.ArrayList;
  22. import java.util.Arrays;
  23. import java.util.HashMap;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Random;
  27. import java.util.Set;
  28. import java.util.SortedSet;
  29. import java.util.TreeMap;
  30. import java.util.WeakHashMap;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;

  33. import org.jivesoftware.smack.ConnectionListener;
  34. import org.jivesoftware.smack.Manager;
  35. import org.jivesoftware.smack.SmackException;
  36. import org.jivesoftware.smack.SmackException.NotConnectedException;
  37. import org.jivesoftware.smack.XMPPConnection;
  38. import org.jivesoftware.smack.XMPPException;
  39. import org.jivesoftware.smack.packet.Message;
  40. import org.jivesoftware.smack.packet.MessageBuilder;
  41. import org.jivesoftware.smack.packet.Stanza;
  42. import org.jivesoftware.smack.util.Async;

  43. import org.jivesoftware.smackx.carbons.CarbonManager;
  44. import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
  45. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  46. import org.jivesoftware.smackx.hints.element.StoreHint;
  47. import org.jivesoftware.smackx.mam.MamManager;
  48. import org.jivesoftware.smackx.muc.MultiUserChat;
  49. import org.jivesoftware.smackx.muc.MultiUserChatManager;
  50. import org.jivesoftware.smackx.muc.RoomInfo;
  51. import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
  52. import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
  53. import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl;
  54. import org.jivesoftware.smackx.omemo.element.OmemoElement;
  55. import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
  56. import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
  57. import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
  58. import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException;
  59. import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
  60. import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
  61. import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
  62. import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
  63. import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
  64. import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
  65. import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
  66. import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
  67. import org.jivesoftware.smackx.omemo.trust.TrustState;
  68. import org.jivesoftware.smackx.omemo.util.MessageOrOmemoMessage;
  69. import org.jivesoftware.smackx.omemo.util.OmemoConstants;
  70. import org.jivesoftware.smackx.pep.PepEventListener;
  71. import org.jivesoftware.smackx.pep.PepManager;
  72. import org.jivesoftware.smackx.pubsub.PubSubException;
  73. import org.jivesoftware.smackx.pubsub.PubSubManager;
  74. import org.jivesoftware.smackx.pubsub.packet.PubSub;

  75. import org.jxmpp.jid.BareJid;
  76. import org.jxmpp.jid.DomainBareJid;
  77. import org.jxmpp.jid.EntityBareJid;
  78. import org.jxmpp.jid.EntityFullJid;

  79. /**
  80.  * Manager that allows sending messages encrypted with OMEMO.
  81.  * This class also provides some methods useful for a client that implements OMEMO.
  82.  *
  83.  * @author Paul Schaub
  84.  */

  85. public final class OmemoManager extends Manager {
  86.     private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName());

  87.     private static final Integer UNKNOWN_DEVICE_ID = -1;

  88.     private static final WeakHashMap<XMPPConnection, TreeMap<Integer, OmemoManager>> INSTANCES = new WeakHashMap<>();
  89.     private final OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> service;

  90.     private final HashSet<OmemoMessageListener> omemoMessageListeners = new HashSet<>();
  91.     private final HashSet<OmemoMucMessageListener> omemoMucMessageListeners = new HashSet<>();

  92.     private final PepManager pepManager;

  93.     private OmemoTrustCallback trustCallback;

  94.     private BareJid ownJid;
  95.     private Integer deviceId;

  96.     /**
  97.      * Private constructor.
  98.      *
  99.      * @param connection connection
  100.      * @param deviceId deviceId
  101.      */
  102.     private OmemoManager(XMPPConnection connection, Integer deviceId) {
  103.         super(connection);

  104.         service = OmemoService.getInstance();
  105.         pepManager = PepManager.getInstanceFor(connection);

  106.         this.deviceId = deviceId;

  107.         if (connection.isAuthenticated()) {
  108.             initBareJidAndDeviceId(this);
  109.         } else {
  110.             connection.addConnectionListener(new ConnectionListener() {
  111.                 @Override
  112.                 public void authenticated(XMPPConnection connection, boolean resumed) {
  113.                     initBareJidAndDeviceId(OmemoManager.this);
  114.                 }
  115.             });
  116.         }

  117.         service.registerRatchetForManager(this);

  118.         // StanzaListeners
  119.         resumeStanzaAndPEPListeners();
  120.     }

  121.     /**
  122.      * Return an OmemoManager instance for the given connection and deviceId.
  123.      * If there was an OmemoManager for the connection and id before, return it. Otherwise create a new OmemoManager
  124.      * instance and return it.
  125.      *
  126.      * @param connection XmppConnection.
  127.      * @param deviceId MUST NOT be null and MUST be greater than 0.
  128.      *
  129.      * @return OmemoManager instance for the given connection and deviceId.
  130.      */
  131.     public static synchronized OmemoManager getInstanceFor(XMPPConnection connection, Integer deviceId) {
  132.         if (deviceId == null || deviceId < 1) {
  133.             throw new IllegalArgumentException("DeviceId MUST NOT be null and MUST be greater than 0.");
  134.         }

  135.         TreeMap<Integer, OmemoManager> managersOfConnection = INSTANCES.get(connection);
  136.         if (managersOfConnection == null) {
  137.             managersOfConnection = new TreeMap<>();
  138.             INSTANCES.put(connection, managersOfConnection);
  139.         }

  140.         OmemoManager manager = managersOfConnection.get(deviceId);
  141.         if (manager == null) {
  142.             manager = new OmemoManager(connection, deviceId);
  143.             managersOfConnection.put(deviceId, manager);
  144.         }

  145.         return manager;
  146.     }

  147.     /**
  148.      * Returns an OmemoManager instance for the given connection. If there was one manager for the connection before,
  149.      * return it. If there were multiple managers before, return the one with the lowest deviceId.
  150.      * If there was no manager before, return a new one. As soon as the connection gets authenticated, the manager
  151.      * will look for local deviceIDs and select the lowest one as its id. If there are not local deviceIds, the manager
  152.      * will assign itself a random id.
  153.      *
  154.      * @param connection XmppConnection.
  155.      *
  156.      * @return OmemoManager instance for the given connection and a determined deviceId.
  157.      */
  158.     public static synchronized OmemoManager getInstanceFor(XMPPConnection connection) {
  159.         TreeMap<Integer, OmemoManager> managers = INSTANCES.get(connection);
  160.         if (managers == null) {
  161.             managers = new TreeMap<>();
  162.             INSTANCES.put(connection, managers);
  163.         }

  164.         OmemoManager manager;
  165.         if (managers.size() == 0) {

  166.             manager = new OmemoManager(connection, UNKNOWN_DEVICE_ID);
  167.             managers.put(UNKNOWN_DEVICE_ID, manager);

  168.         } else {
  169.             manager = managers.get(managers.firstKey());
  170.         }

  171.         return manager;
  172.     }

  173.     /**
  174.      * Set a TrustCallback for this particular OmemoManager.
  175.      * TrustCallbacks are used to query and modify trust decisions.
  176.      *
  177.      * @param callback trustCallback.
  178.      */
  179.     public void setTrustCallback(OmemoTrustCallback callback) {
  180.         if (trustCallback != null) {
  181.             throw new IllegalStateException("TrustCallback can only be set once.");
  182.         }
  183.         trustCallback = callback;
  184.     }

  185.     /**
  186.      * Return the TrustCallback of this manager.
  187.      *
  188.      * @return callback that is used for trust decisions.
  189.      */
  190.     OmemoTrustCallback getTrustCallback() {
  191.         return trustCallback;
  192.     }

  193.     /**
  194.      * Initializes the OmemoManager. This method must be called before the manager can be used.
  195.      *
  196.      * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
  197.      * @throws InterruptedException if the calling thread was interrupted.
  198.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  199.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  200.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  201.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  202.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  203.      * @throws IOException if an I/O error occurred.
  204.      */
  205.     public synchronized void initialize()
  206.             throws SmackException.NotLoggedInException, CorruptedOmemoKeyException, InterruptedException,
  207.             SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException,
  208.             PubSubException.NotALeafNodeException, IOException {
  209.         if (!connection().isAuthenticated()) {
  210.             throw new SmackException.NotLoggedInException();
  211.         }

  212.         if (getTrustCallback() == null) {
  213.             throw new IllegalStateException("No TrustCallback set.");
  214.         }

  215.         getOmemoService().init(new LoggedInOmemoManager(this));
  216.     }

  217.     /**
  218.      * Initialize the manager without blocking. Once the manager is successfully initialized, the finishedCallback will
  219.      * be notified. It will also get notified, if an error occurs.
  220.      *
  221.      * @param finishedCallback callback that gets called once the manager is initialized.
  222.      */
  223.     public void initializeAsync(final InitializationFinishedCallback finishedCallback) {
  224.         Async.go(new Runnable() {
  225.             @Override
  226.             public void run() {
  227.                 try {
  228.                     initialize();
  229.                     finishedCallback.initializationFinished(OmemoManager.this);
  230.                 } catch (Exception e) {
  231.                     finishedCallback.initializationFailed(e);
  232.                 }
  233.             }
  234.         });
  235.     }

  236.     /**
  237.      * Return a set of all OMEMO capable devices of a contact.
  238.      * Note, that this method does not explicitly refresh the device list of the contact, so it might be outdated.
  239.      *
  240.      * @see #requestDeviceListUpdateFor(BareJid)
  241.      *
  242.      * @param contact contact we want to get a set of device of.
  243.      * @return set of known devices of that contact.
  244.      *
  245.      * @throws IOException if an I/O error occurred.
  246.      */
  247.     public Set<OmemoDevice> getDevicesOf(BareJid contact) throws IOException {
  248.         OmemoCachedDeviceList list = getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(getOwnDevice(), contact);
  249.         HashSet<OmemoDevice> devices = new HashSet<>();

  250.         for (int deviceId : list.getActiveDevices()) {
  251.             devices.add(new OmemoDevice(contact, deviceId));
  252.         }

  253.         return devices;
  254.     }

  255.     /**
  256.      * OMEMO encrypt a cleartext message for a single recipient.
  257.      * Note that this method does NOT set the 'to' attribute of the message.
  258.      *
  259.      * @param recipient recipients bareJid
  260.      * @param message text to encrypt
  261.      * @return encrypted message
  262.      *
  263.      * @throws CryptoFailedException                when something crypto related fails
  264.      * @throws UndecidedOmemoIdentityException      When there are undecided devices
  265.      * @throws InterruptedException if the calling thread was interrupted.
  266.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  267.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  268.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  269.      * @throws IOException if an I/O error occurred.
  270.      */
  271.     public OmemoMessage.Sent encrypt(BareJid recipient, String message)
  272.             throws CryptoFailedException, UndecidedOmemoIdentityException,
  273.             InterruptedException, SmackException.NotConnectedException,
  274.             SmackException.NoResponseException, SmackException.NotLoggedInException, IOException {
  275.         Set<BareJid> recipients = new HashSet<>();
  276.         recipients.add(recipient);
  277.         return encrypt(recipients, message);
  278.     }

  279.     /**
  280.      * OMEMO encrypt a cleartext message for multiple recipients.
  281.      *
  282.      * @param recipients recipients barejids
  283.      * @param message text to encrypt
  284.      * @return encrypted message.
  285.      *
  286.      * @throws CryptoFailedException    When something crypto related fails
  287.      * @throws UndecidedOmemoIdentityException  When there are undecided devices.
  288.      * @throws InterruptedException if the calling thread was interrupted.
  289.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  290.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  291.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  292.      * @throws IOException if an I/O error occurred.
  293.      */
  294.     public synchronized OmemoMessage.Sent encrypt(Set<BareJid> recipients, String message)
  295.             throws CryptoFailedException, UndecidedOmemoIdentityException,
  296.             InterruptedException, SmackException.NotConnectedException,
  297.             SmackException.NoResponseException, SmackException.NotLoggedInException, IOException {
  298.         LoggedInOmemoManager guard = new LoggedInOmemoManager(this);
  299.         Set<OmemoDevice> devices = getDevicesOf(getOwnJid());
  300.         for (BareJid recipient : recipients) {
  301.             devices.addAll(getDevicesOf(recipient));
  302.         }
  303.         return service.createOmemoMessage(guard, devices, message);
  304.     }

  305.     /**
  306.      * Encrypt a message for all recipients in the MultiUserChat.
  307.      *
  308.      * @param muc multiUserChat
  309.      * @param message message to send
  310.      * @return encrypted message
  311.      *
  312.      * @throws UndecidedOmemoIdentityException when there are undecided devices.
  313.      * @throws CryptoFailedException if the OMEMO cryptography failed.
  314.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  315.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  316.      * @throws InterruptedException if the calling thread was interrupted.
  317.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  318.      * @throws NoOmemoSupportException When the muc doesn't support OMEMO.
  319.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  320.      * @throws IOException if an I/O error occurred.
  321.      */
  322.     public synchronized OmemoMessage.Sent encrypt(MultiUserChat muc, String message)
  323.             throws UndecidedOmemoIdentityException, CryptoFailedException,
  324.             XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  325.             SmackException.NoResponseException, NoOmemoSupportException,
  326.             SmackException.NotLoggedInException, IOException {
  327.         if (!multiUserChatSupportsOmemo(muc)) {
  328.             throw new NoOmemoSupportException();
  329.         }

  330.         Set<BareJid> recipients = new HashSet<>();

  331.         for (EntityFullJid e : muc.getOccupants()) {
  332.             recipients.add(muc.getOccupant(e).getJid().asBareJid());
  333.         }
  334.         return encrypt(recipients, message);
  335.     }

  336.     /**
  337.      * Manually decrypt an OmemoElement.
  338.      * This method should only be used for use-cases, where the internal listeners don't pick up on an incoming message.
  339.      * (for example MAM query results).
  340.      *
  341.      * @param sender bareJid of the message sender (must be the jid of the contact who sent the message)
  342.      * @param omemoElement omemoElement
  343.      * @return decrypted OmemoMessage
  344.      *
  345.      * @throws SmackException.NotLoggedInException if the Manager is not authenticated
  346.      * @throws CorruptedOmemoKeyException if our or their key is corrupted
  347.      * @throws NoRawSessionException if the message was not a preKeyMessage, but we had no session with the contact
  348.      * @throws CryptoFailedException if decryption fails
  349.      * @throws IOException if an I/O error occurred.
  350.      */
  351.     public OmemoMessage.Received decrypt(BareJid sender, OmemoElement omemoElement)
  352.             throws SmackException.NotLoggedInException, CorruptedOmemoKeyException, NoRawSessionException,
  353.             CryptoFailedException, IOException {
  354.         LoggedInOmemoManager managerGuard = new LoggedInOmemoManager(this);
  355.         return getOmemoService().decryptMessage(managerGuard, sender, omemoElement);
  356.     }

  357.     /**
  358.      * Decrypt messages from a MAM query.
  359.      *
  360.      * @param mamQuery The MAM query
  361.      * @return list of decrypted OmemoMessages
  362.      *
  363.      * @throws SmackException.NotLoggedInException if the Manager is not authenticated.
  364.      * @throws IOException if an I/O error occurred.
  365.      */
  366.     public List<MessageOrOmemoMessage> decryptMamQueryResult(MamManager.MamQuery mamQuery)
  367.             throws SmackException.NotLoggedInException, IOException {
  368.         return new ArrayList<>(getOmemoService().decryptMamQueryResult(new LoggedInOmemoManager(this), mamQuery));
  369.     }

  370.     /**
  371.      * Trust that a fingerprint belongs to an OmemoDevice.
  372.      * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
  373.      * be of length 64.
  374.      *
  375.      * @param device device
  376.      * @param fingerprint fingerprint
  377.      */
  378.     public void trustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
  379.         if (trustCallback == null) {
  380.             throw new IllegalStateException("No TrustCallback set.");
  381.         }

  382.         trustCallback.setTrust(device, fingerprint, TrustState.trusted);
  383.     }

  384.     /**
  385.      * Distrust the fingerprint/OmemoDevice tuple.
  386.      * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
  387.      * be of length 64.
  388.      *
  389.      * @param device device
  390.      * @param fingerprint fingerprint
  391.      */
  392.     public void distrustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
  393.         if (trustCallback == null) {
  394.             throw new IllegalStateException("No TrustCallback set.");
  395.         }

  396.         trustCallback.setTrust(device, fingerprint, TrustState.untrusted);
  397.     }

  398.     /**
  399.      * Returns true, if the fingerprint/OmemoDevice tuple is trusted, otherwise false.
  400.      * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
  401.      * be of length 64.
  402.      *
  403.      * @param device device
  404.      * @param fingerprint fingerprint
  405.      * @return <code>true</code> if this is a trusted OMEMO identity.
  406.      */
  407.     public boolean isTrustedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
  408.         if (trustCallback == null) {
  409.             throw new IllegalStateException("No TrustCallback set.");
  410.         }

  411.         return trustCallback.getTrust(device, fingerprint) == TrustState.trusted;
  412.     }

  413.     /**
  414.      * Returns true, if the fingerprint/OmemoDevice tuple is decided by the user.
  415.      * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
  416.      * be of length 64.
  417.      *
  418.      * @param device device
  419.      * @param fingerprint fingerprint
  420.      * @return <code>true</code> if the trust is decided for the identity.
  421.      */
  422.     public boolean isDecidedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
  423.         if (trustCallback == null) {
  424.             throw new IllegalStateException("No TrustCallback set.");
  425.         }

  426.         return trustCallback.getTrust(device, fingerprint) != TrustState.undecided;
  427.     }

  428.     /**
  429.      * Send a ratchet update message. This can be used to advance the ratchet of a session in order to maintain forward
  430.      * secrecy.
  431.      *
  432.      * @param recipient recipient
  433.      *
  434.      * @throws CorruptedOmemoKeyException           When the used identityKeys are corrupted
  435.      * @throws CryptoFailedException                When something fails with the crypto
  436.      * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient
  437.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  438.      * @throws InterruptedException if the calling thread was interrupted.
  439.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  440.      * @throws NoSuchAlgorithmException if no such algorithm is available.
  441.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  442.      * @throws IOException if an I/O error occurred.
  443.      */
  444.     public synchronized void sendRatchetUpdateMessage(OmemoDevice recipient)
  445.             throws SmackException.NotLoggedInException, CorruptedOmemoKeyException, InterruptedException,
  446.             SmackException.NoResponseException, NoSuchAlgorithmException, SmackException.NotConnectedException,
  447.             CryptoFailedException, CannotEstablishOmemoSessionException, IOException {
  448.         XMPPConnection connection = connection();
  449.         MessageBuilder message = connection.getStanzaFactory()
  450.                 .buildMessageStanza()
  451.                 .to(recipient.getJid());

  452.         OmemoElement element = getOmemoService().createRatchetUpdateElement(new LoggedInOmemoManager(this), recipient);
  453.         message.addExtension(element);

  454.         // Set MAM Storage hint
  455.         StoreHint.set(message);
  456.         connection.sendStanza(message.build());
  457.     }

  458.     /**
  459.      * Returns true, if the contact has any active devices published in a deviceList.
  460.      *
  461.      * @param contact contact
  462.      * @return true if contact has at least one OMEMO capable device.
  463.      *
  464.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  465.      * @throws InterruptedException if the calling thread was interrupted.
  466.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  467.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  468.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  469.      * @throws IOException if an I/O error occurred.
  470.      */
  471.     public synchronized boolean contactSupportsOmemo(BareJid contact)
  472.             throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
  473.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
  474.         OmemoCachedDeviceList deviceList = getOmemoService().refreshDeviceList(connection(), getOwnDevice(), contact);
  475.         return !deviceList.getActiveDevices().isEmpty();
  476.     }

  477.     /**
  478.      * Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite
  479.      * for OMEMO encryption in MUC).
  480.      *
  481.      * @param multiUserChat MUC
  482.      * @return true if chat supports OMEMO
  483.      *
  484.      * @throws XMPPException.XMPPErrorException     if there was an XMPP protocol level error
  485.      * @throws SmackException.NotConnectedException if the connection is not connected
  486.      * @throws InterruptedException                 if the thread is interrupted
  487.      * @throws SmackException.NoResponseException   if the server does not respond
  488.      */
  489.     public boolean multiUserChatSupportsOmemo(MultiUserChat multiUserChat)
  490.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  491.             SmackException.NoResponseException {
  492.         EntityBareJid jid = multiUserChat.getRoom();
  493.         RoomInfo roomInfo = MultiUserChatManager.getInstanceFor(connection()).getRoomInfo(jid);
  494.         return roomInfo.isNonanonymous() && roomInfo.isMembersOnly();
  495.     }

  496.     /**
  497.      * Returns true, if the Server supports PEP.
  498.      *
  499.      * @param connection XMPPConnection
  500.      * @param server domainBareJid of the server to test
  501.      * @return true if server supports pep
  502.      *
  503.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  504.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  505.      * @throws InterruptedException if the calling thread was interrupted.
  506.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  507.      */
  508.     public static boolean serverSupportsOmemo(XMPPConnection connection, DomainBareJid server)
  509.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  510.             SmackException.NoResponseException {
  511.         return ServiceDiscoveryManager.getInstanceFor(connection)
  512.                 .discoverInfo(server).containsFeature(PubSub.NAMESPACE);
  513.     }

  514.     /**
  515.      * Return the fingerprint of our identity key.
  516.      *
  517.      * @return our own OMEMO fingerprint
  518.      *
  519.      * @throws SmackException.NotLoggedInException if we don't know our bareJid yet.
  520.      * @throws CorruptedOmemoKeyException if our identityKey is corrupted.
  521.      * @throws IOException if an I/O error occurred.
  522.      */
  523.     public synchronized OmemoFingerprint getOwnFingerprint()
  524.             throws SmackException.NotLoggedInException, CorruptedOmemoKeyException, IOException {
  525.         if (getOwnJid() == null) {
  526.             throw new SmackException.NotLoggedInException();
  527.         }

  528.         return getOmemoService().getOmemoStoreBackend().getFingerprint(getOwnDevice());
  529.     }

  530.     /**
  531.      * Get the fingerprint of a contacts device.
  532.      *
  533.      * @param device contacts OmemoDevice
  534.      * @return fingerprint of the given OMEMO device.
  535.      *
  536.      * @throws CannotEstablishOmemoSessionException if we have no session yet, and are unable to create one.
  537.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  538.      * @throws CorruptedOmemoKeyException if the copy of the fingerprint we have is corrupted.
  539.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  540.      * @throws InterruptedException if the calling thread was interrupted.
  541.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  542.      * @throws IOException if an I/O error occurred.
  543.      */
  544.     public synchronized OmemoFingerprint getFingerprint(OmemoDevice device)
  545.             throws CannotEstablishOmemoSessionException, SmackException.NotLoggedInException,
  546.             CorruptedOmemoKeyException, SmackException.NotConnectedException, InterruptedException,
  547.             SmackException.NoResponseException, IOException {
  548.         if (getOwnJid() == null) {
  549.             throw new SmackException.NotLoggedInException();
  550.         }

  551.         if (device.equals(getOwnDevice())) {
  552.             return getOwnFingerprint();
  553.         }

  554.         return getOmemoService().getOmemoStoreBackend()
  555.                 .getFingerprintAndMaybeBuildSession(new LoggedInOmemoManager(this), device);
  556.     }

  557.     /**
  558.      * Return all OmemoFingerprints of active devices of a contact.
  559.      * TODO: Make more fail-safe
  560.      *
  561.      * @param contact contact
  562.      * @return Map of all active devices of the contact and their fingerprints.
  563.      *
  564.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  565.      * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
  566.      * @throws CannotEstablishOmemoSessionException if no OMEMO session could be established.
  567.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  568.      * @throws InterruptedException if the calling thread was interrupted.
  569.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  570.      * @throws IOException if an I/O error occurred.
  571.      */
  572.     public synchronized HashMap<OmemoDevice, OmemoFingerprint> getActiveFingerprints(BareJid contact)
  573.             throws SmackException.NotLoggedInException, CorruptedOmemoKeyException,
  574.             CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException,
  575.             SmackException.NoResponseException, IOException {
  576.         if (getOwnJid() == null) {
  577.             throw new SmackException.NotLoggedInException();
  578.         }

  579.         HashMap<OmemoDevice, OmemoFingerprint> fingerprints = new HashMap<>();
  580.         OmemoCachedDeviceList deviceList = getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(getOwnDevice(),
  581.                 contact);

  582.         for (int id : deviceList.getActiveDevices()) {
  583.             OmemoDevice device = new OmemoDevice(contact, id);
  584.             OmemoFingerprint fingerprint = getFingerprint(device);

  585.             if (fingerprint != null) {
  586.                 fingerprints.put(device, fingerprint);
  587.             }
  588.         }

  589.         return fingerprints;
  590.     }

  591.     /**
  592.      * Add an OmemoMessageListener. This listener will be informed about incoming OMEMO messages
  593.      * (as well as KeyTransportMessages) and OMEMO encrypted message carbons.
  594.      *
  595.      * @param listener OmemoMessageListener
  596.      */
  597.     public void addOmemoMessageListener(OmemoMessageListener listener) {
  598.         omemoMessageListeners.add(listener);
  599.     }

  600.     /**
  601.      * Remove an OmemoMessageListener.
  602.      *
  603.      * @param listener OmemoMessageListener
  604.      */
  605.     public void removeOmemoMessageListener(OmemoMessageListener listener) {
  606.         omemoMessageListeners.remove(listener);
  607.     }

  608.     /**
  609.      * Add an OmemoMucMessageListener. This listener will be informed about incoming OMEMO encrypted MUC messages.
  610.      *
  611.      * @param listener OmemoMessageListener.
  612.      */
  613.     public void addOmemoMucMessageListener(OmemoMucMessageListener listener) {
  614.         omemoMucMessageListeners.add(listener);
  615.     }

  616.     /**
  617.      * Remove an OmemoMucMessageListener.
  618.      *
  619.      * @param listener OmemoMucMessageListener
  620.      */
  621.     public void removeOmemoMucMessageListener(OmemoMucMessageListener listener) {
  622.         omemoMucMessageListeners.remove(listener);
  623.     }

  624.     /**
  625.      * Request a deviceList update from contact contact.
  626.      *
  627.      * @param contact contact we want to obtain the deviceList from.
  628.      *
  629.      * @throws InterruptedException if the calling thread was interrupted.
  630.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  631.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  632.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  633.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  634.      * @throws IOException if an I/O error occurred.
  635.      */
  636.     public synchronized void requestDeviceListUpdateFor(BareJid contact)
  637.             throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
  638.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
  639.         getOmemoService().refreshDeviceList(connection(), getOwnDevice(), contact);
  640.     }

  641.     /**
  642.      * Publish a new device list with just our own deviceId in it.
  643.      *
  644.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  645.      * @throws InterruptedException if the calling thread was interrupted.
  646.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  647.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  648.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  649.      * @throws IOException if an I/O error occurred.
  650.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  651.      */
  652.     public void purgeDeviceList()
  653.             throws SmackException.NotLoggedInException, InterruptedException, XMPPException.XMPPErrorException,
  654.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException, PubSubException.NotALeafNodeException {
  655.         getOmemoService().purgeDeviceList(new LoggedInOmemoManager(this));
  656.     }

  657.     public List<Exception> purgeEverything() throws NotConnectedException, InterruptedException, IOException {
  658.         List<Exception> exceptions = new ArrayList<>(5);
  659.         PubSubManager pm = PubSubManager.getInstanceFor(getConnection(), getOwnJid());
  660.         try {
  661.             requestDeviceListUpdateFor(getOwnJid());
  662.         } catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException
  663.                         | XMPPException.XMPPErrorException e) {
  664.             exceptions.add(e);
  665.         }

  666.         OmemoCachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend()
  667.                 .loadCachedDeviceList(getOwnDevice(), getOwnJid());

  668.         for (int id : deviceList.getAllDevices()) {
  669.             try {
  670.                 pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
  671.             } catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException
  672.                             | XMPPException.XMPPErrorException | PubSubException.NotAPubSubNodeException e) {
  673.                 exceptions.add(e);
  674.             }

  675.             try {
  676.                 pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
  677.             } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException e) {
  678.                 exceptions.add(e);
  679.             }
  680.         }

  681.         try {
  682.             pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
  683.         } catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException
  684.                         | XMPPException.XMPPErrorException | PubSubException.NotAPubSubNodeException e) {
  685.             exceptions.add(e);
  686.         }

  687.         try {
  688.             pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
  689.         } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException e) {
  690.             exceptions.add(e);
  691.         }

  692.         return exceptions;
  693.     }

  694.     /**
  695.      * Rotate the signedPreKey published in our OmemoBundle and republish it. This should be done every now and
  696.      * then (7-14 days). The old signedPreKey should be kept for some more time (a month or so) to enable decryption
  697.      * of messages that have been sent since the key was changed.
  698.      *
  699.      * @throws CorruptedOmemoKeyException When the IdentityKeyPair is damaged.
  700.      * @throws InterruptedException XMPP error
  701.      * @throws XMPPException.XMPPErrorException XMPP error
  702.      * @throws SmackException.NotConnectedException XMPP error
  703.      * @throws SmackException.NoResponseException XMPP error
  704.      * @throws SmackException.NotLoggedInException if the XMPP connection is not authenticated.
  705.      * @throws IOException if an I/O error occurred.
  706.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  707.      */
  708.     public synchronized void rotateSignedPreKey()
  709.             throws CorruptedOmemoKeyException, SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
  710.             SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
  711.             IOException, PubSubException.NotALeafNodeException {
  712.         if (!connection().isAuthenticated()) {
  713.             throw new SmackException.NotLoggedInException();
  714.         }

  715.         // generate key
  716.         getOmemoService().getOmemoStoreBackend().changeSignedPreKey(getOwnDevice());

  717.         // publish
  718.         OmemoBundleElement bundle = getOmemoService().getOmemoStoreBackend().packOmemoBundle(getOwnDevice());
  719.         OmemoService.publishBundle(connection(), getOwnDevice(), bundle);
  720.     }

  721.     /**
  722.      * Return true, if the given Stanza contains an OMEMO element 'encrypted'.
  723.      *
  724.      * @param stanza stanza
  725.      * @return true if stanza has extension 'encrypted'
  726.      */
  727.     static boolean stanzaContainsOmemoElement(Stanza stanza) {
  728.         return stanza.hasExtension(OmemoElement.NAME_ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
  729.     }

  730.     /**
  731.      * Throw an IllegalStateException if no OmemoService is set.
  732.      */
  733.     private void throwIfNoServiceSet() {
  734.         if (service == null) {
  735.             throw new IllegalStateException("No OmemoService set in OmemoManager.");
  736.         }
  737.     }

  738.     /**
  739.      * Returns a pseudo random number from the interval [1, Integer.MAX_VALUE].
  740.      *
  741.      * @return a random deviceId.
  742.      */
  743.     public static int randomDeviceId() {
  744.         return new Random().nextInt(Integer.MAX_VALUE - 1) + 1;
  745.     }

  746.     /**
  747.      * Return the BareJid of the user.
  748.      *
  749.      * @return our own bare JID.
  750.      */
  751.     public BareJid getOwnJid() {
  752.         if (ownJid == null && connection().isAuthenticated()) {
  753.             ownJid = connection().getUser().asBareJid();
  754.         }

  755.         return ownJid;
  756.     }

  757.     /**
  758.      * Return the deviceId of this OmemoManager.
  759.      *
  760.      * @return this OmemoManagers deviceId.
  761.      */
  762.     public synchronized Integer getDeviceId() {
  763.         return deviceId;
  764.     }

  765.     /**
  766.      * Return the OmemoDevice of the user.
  767.      *
  768.      * @return our own OmemoDevice
  769.      */
  770.     public synchronized OmemoDevice getOwnDevice() {
  771.         BareJid jid = getOwnJid();
  772.         if (jid == null) {
  773.             return null;
  774.         }
  775.         return new OmemoDevice(jid, getDeviceId());
  776.     }

  777.     /**
  778.      * Set the deviceId of the manager to nDeviceId.
  779.      *
  780.      * @param nDeviceId new deviceId
  781.      */
  782.     synchronized void setDeviceId(int nDeviceId) {
  783.         // Move this instance inside the HashMaps
  784.         INSTANCES.get(connection()).remove(getDeviceId());
  785.         INSTANCES.get(connection()).put(nDeviceId, this);

  786.         this.deviceId = nDeviceId;
  787.     }

  788.     /**
  789.      * Notify all registered OmemoMessageListeners about a received OmemoMessage.
  790.      *
  791.      * @param stanza original stanza
  792.      * @param decryptedMessage decrypted OmemoMessage.
  793.      */
  794.     void notifyOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage) {
  795.         for (OmemoMessageListener l : omemoMessageListeners) {
  796.             l.onOmemoMessageReceived(stanza, decryptedMessage);
  797.         }
  798.     }

  799.     /**
  800.      * Notify all registered OmemoMucMessageListeners of an incoming OmemoMessageElement in a MUC.
  801.      *
  802.      * @param muc               MultiUserChat the message was received in.
  803.      * @param stanza            Original Stanza.
  804.      * @param decryptedMessage  Decrypted OmemoMessage.
  805.      */
  806.     void notifyOmemoMucMessageReceived(MultiUserChat muc,
  807.                                        Stanza stanza,
  808.                                        OmemoMessage.Received decryptedMessage) {
  809.         for (OmemoMucMessageListener l : omemoMucMessageListeners) {
  810.             l.onOmemoMucMessageReceived(muc, stanza, decryptedMessage);
  811.         }
  812.     }

  813.     /**
  814.      * Notify all registered OmemoMessageListeners of an incoming OMEMO encrypted Carbon Copy.
  815.      * Remember: If you want to receive OMEMO encrypted carbon copies, you have to enable carbons using
  816.      * {@link CarbonManager#enableCarbons()}.
  817.      *
  818.      * @param direction             direction of the carbon copy
  819.      * @param carbonCopy            carbon copy itself
  820.      * @param wrappingMessage       wrapping message
  821.      * @param decryptedCarbonCopy   decrypted carbon copy OMEMO element
  822.      */
  823.     void notifyOmemoCarbonCopyReceived(CarbonExtension.Direction direction,
  824.                                        Message carbonCopy,
  825.                                        Message wrappingMessage,
  826.                                        OmemoMessage.Received decryptedCarbonCopy) {
  827.         for (OmemoMessageListener l : omemoMessageListeners) {
  828.             l.onOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage, decryptedCarbonCopy);
  829.         }
  830.     }

  831.     /**
  832.      * Register stanza listeners needed for OMEMO.
  833.      * This method is called automatically in the constructor and should only be used to restore the previous state
  834.      * after {@link #stopStanzaAndPEPListeners()} was called.
  835.      */
  836.     public void resumeStanzaAndPEPListeners() {
  837.         CarbonManager carbonManager = CarbonManager.getInstanceFor(connection());

  838.         // Remove listeners to avoid them getting added twice
  839.         connection().removeAsyncStanzaListener(this::internalOmemoMessageStanzaListener);
  840.         carbonManager.removeCarbonCopyReceivedListener(this::internalOmemoCarbonCopyListener);

  841.         // Add listeners
  842.         pepManager.addPepEventListener(OmemoConstants.PEP_NODE_DEVICE_LIST, OmemoDeviceListElement.class, pepOmemoDeviceListEventListener);
  843.         connection().addAsyncStanzaListener(this::internalOmemoMessageStanzaListener, OmemoManager::isOmemoMessage);
  844.         carbonManager.addCarbonCopyReceivedListener(this::internalOmemoCarbonCopyListener);
  845.     }

  846.     /**
  847.      * Remove active stanza listeners needed for OMEMO.
  848.      */
  849.     public void stopStanzaAndPEPListeners() {
  850.         pepManager.removePepEventListener(pepOmemoDeviceListEventListener);
  851.         connection().removeAsyncStanzaListener(this::internalOmemoMessageStanzaListener);
  852.         CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(this::internalOmemoCarbonCopyListener);
  853.     }

  854.     /**
  855.      * Build a fresh session with a contacts device.
  856.      * This might come in handy if a session is broken.
  857.      *
  858.      * @param contactsDevice OmemoDevice of a contact.
  859.      *
  860.      * @throws InterruptedException if the calling thread was interrupted.
  861.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  862.      * @throws CorruptedOmemoKeyException if our or their identityKey is corrupted.
  863.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  864.      * @throws CannotEstablishOmemoSessionException if no new session can be established.
  865.      * @throws SmackException.NotLoggedInException if the connection is not authenticated.
  866.      */
  867.     public void rebuildSessionWith(OmemoDevice contactsDevice)
  868.             throws InterruptedException, SmackException.NoResponseException, CorruptedOmemoKeyException,
  869.             SmackException.NotConnectedException, CannotEstablishOmemoSessionException,
  870.             SmackException.NotLoggedInException {
  871.         if (!connection().isAuthenticated()) {
  872.             throw new SmackException.NotLoggedInException();
  873.         }
  874.         getOmemoService().buildFreshSessionWithDevice(connection(), getOwnDevice(), contactsDevice);
  875.     }

  876.     /**
  877.      * Get our connection.
  878.      *
  879.      * @return the connection of this manager
  880.      */
  881.     XMPPConnection getConnection() {
  882.         return connection();
  883.     }

  884.     /**
  885.      * Return the OMEMO service object.
  886.      *
  887.      * @return the OmemoService object related to this OmemoManager.
  888.      */
  889.     OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> getOmemoService() {
  890.         throwIfNoServiceSet();
  891.         return service;
  892.     }

  893.     /**
  894.      * StanzaListener that listens for incoming Stanzas which contain OMEMO elements.
  895.      */
  896.     private void internalOmemoMessageStanzaListener(final Stanza packet) {
  897.         Async.go(new Runnable() {
  898.             @Override
  899.             public void run() {
  900.                 try {
  901.                     getOmemoService().onOmemoMessageStanzaReceived(packet,
  902.                             new LoggedInOmemoManager(OmemoManager.this));
  903.                 } catch (SmackException.NotLoggedInException | IOException e) {
  904.                     LOGGER.log(Level.SEVERE, "Exception while processing OMEMO stanza", e);
  905.                 }
  906.             }
  907.         });
  908.     }

  909.     /**
  910.      * CarbonCopyListener that listens for incoming carbon copies which contain OMEMO elements.
  911.      */
  912.     private void internalOmemoCarbonCopyListener(final CarbonExtension.Direction direction,
  913.                     final Message carbonCopy,
  914.                     final Message wrappingMessage) {
  915.         Async.go(new Runnable() {
  916.             @Override
  917.             public void run() {
  918.                 if (isOmemoMessage(carbonCopy)) {
  919.                     try {
  920.                         getOmemoService().onOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage,
  921.                                 new LoggedInOmemoManager(OmemoManager.this));
  922.                     } catch (SmackException.NotLoggedInException | IOException e) {
  923.                         LOGGER.log(Level.SEVERE, "Exception while processing OMEMO stanza", e);
  924.                     }
  925.                 }
  926.             }
  927.         });
  928.     }

  929.     @SuppressWarnings("UnnecessaryLambda")
  930.     private final PepEventListener<OmemoDeviceListElement> pepOmemoDeviceListEventListener =
  931.                     (from, receivedDeviceList, id, message) -> {
  932.         // Device List <list>
  933.         OmemoCachedDeviceList deviceList;
  934.         try {
  935.             getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(getOwnDevice(), from,
  936.                             receivedDeviceList);

  937.             if (!from.asBareJid().equals(getOwnJid())) {
  938.                 return;
  939.             }

  940.             deviceList = getOmemoService().cleanUpDeviceList(getOwnDevice());
  941.         } catch (IOException e) {
  942.             LOGGER.log(Level.SEVERE,
  943.                             "IOException while processing OMEMO PEP device updates. Message: " + message,
  944.                                 e);
  945.             return;
  946.         }
  947.         final OmemoDeviceListElement_VAxolotl newDeviceList = new OmemoDeviceListElement_VAxolotl(deviceList);

  948.         if (!newDeviceList.copyDeviceIds().equals(receivedDeviceList.copyDeviceIds())) {
  949.             LOGGER.log(Level.FINE, "Republish deviceList due to changes:" +
  950.                             " Received: " + Arrays.toString(receivedDeviceList.copyDeviceIds().toArray()) +
  951.                             " Published: " + Arrays.toString(newDeviceList.copyDeviceIds().toArray()));
  952.             Async.go(new Runnable() {
  953.                 @Override
  954.                 public void run() {
  955.                     try {
  956.                         OmemoService.publishDeviceList(connection(), newDeviceList);
  957.                     } catch (InterruptedException | XMPPException.XMPPErrorException |
  958.                                     SmackException.NotConnectedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException e) {
  959.                         LOGGER.log(Level.WARNING, "Could not publish our deviceList upon an received update.", e);
  960.                     }
  961.                 }
  962.             });
  963.         }
  964.     };

  965.     /**
  966.      * StanzaFilter that filters messages containing a OMEMO element.
  967.      */
  968.     private static boolean isOmemoMessage(Stanza stanza) {
  969.         return stanza instanceof Message && OmemoManager.stanzaContainsOmemoElement(stanza);
  970.     }

  971.     /**
  972.      * Guard class which ensures that the wrapped OmemoManager knows its BareJid.
  973.      */
  974.     public static class LoggedInOmemoManager {

  975.         private final OmemoManager manager;

  976.         public LoggedInOmemoManager(OmemoManager manager)
  977.                 throws SmackException.NotLoggedInException {

  978.             if (manager == null) {
  979.                 throw new IllegalArgumentException("OmemoManager cannot be null.");
  980.             }

  981.             if (manager.getOwnJid() == null) {
  982.                 if (manager.getConnection().isAuthenticated()) {
  983.                     manager.ownJid = manager.getConnection().getUser().asBareJid();
  984.                 } else {
  985.                     throw new SmackException.NotLoggedInException();
  986.                 }
  987.             }

  988.             this.manager = manager;
  989.         }

  990.         public OmemoManager get() {
  991.             return manager;
  992.         }
  993.     }

  994.     /**
  995.      * Callback which can be used to get notified, when the OmemoManager finished initializing.
  996.      */
  997.     public interface InitializationFinishedCallback {

  998.         void initializationFinished(OmemoManager manager);

  999.         void initializationFailed(Exception cause);
  1000.     }

  1001.     /**
  1002.      * Get the bareJid of the user from the authenticated XMPP connection.
  1003.      * If our deviceId is unknown, use the bareJid to look up deviceIds available in the omemoStore.
  1004.      * If there are ids available, choose the smallest one. Otherwise generate a random deviceId.
  1005.      *
  1006.      * @param manager OmemoManager
  1007.      */
  1008.     private static void initBareJidAndDeviceId(OmemoManager manager) {
  1009.         if (!manager.getConnection().isAuthenticated()) {
  1010.             throw new IllegalStateException("Connection MUST be authenticated.");
  1011.         }

  1012.         if (manager.ownJid == null) {
  1013.             manager.ownJid = manager.getConnection().getUser().asBareJid();
  1014.         }

  1015.         if (UNKNOWN_DEVICE_ID.equals(manager.deviceId)) {
  1016.             SortedSet<Integer> storedDeviceIds = manager.getOmemoService().getOmemoStoreBackend().localDeviceIdsOf(manager.ownJid);
  1017.             if (storedDeviceIds.size() > 0) {
  1018.                 manager.setDeviceId(storedDeviceIds.first());
  1019.             } else {
  1020.                 manager.setDeviceId(randomDeviceId());
  1021.             }
  1022.         }
  1023.     }
  1024. }