diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java
new file mode 100644
index 000000000..69d611ea4
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java
@@ -0,0 +1,72 @@
+/**
+ *
+ * Copyright 2018 Paul Schaub
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.omemo;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
+import org.jivesoftware.smackx.omemo.exceptions.ReadOnlyDeviceException;
+import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
+
+import org.igniterealtime.smack.inttest.SmackIntegrationTest;
+import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
+import org.igniterealtime.smack.inttest.TestNotPossibleException;
+
+public class ReadOnlyDeviceIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
+
+ public ReadOnlyDeviceIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
+ super(environment);
+ }
+
+ @SmackIntegrationTest
+ public void test() throws InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, SmackException.NotConnectedException, CryptoFailedException, UndecidedOmemoIdentityException {
+ boolean prevIgnoreReadOnlyConf = OmemoConfiguration.getIgnoreReadOnlyDevices();
+ int prevMaxMessageCounter = OmemoConfiguration.getMaxReadOnlyMessageCount();
+
+ OmemoConfiguration.setIgnoreReadOnlyDevices(true);
+ // Set the maxReadOnlyMessageCount to ridiculously low threshold of 5.
+ // This means that Alice will be able to encrypt 5 messages for Bob, while the 6th will not be encrypted for Bob.
+ OmemoConfiguration.setMaxReadOnlyMessageCount(5);
+
+ // Reset counter to begin test
+ alice.getOmemoService().getOmemoStoreBackend().storeOmemoMessageCounter(alice.getOwnDevice(), bob.getOwnDevice(), 0);
+
+ // Since the max threshold is 5, we must be able to encrypt 5 messages for Bob.
+ for (int i = 0; i < 5; i++) {
+ assertEquals(i, alice.getOmemoService().getOmemoStoreBackend().loadOmemoMessageCounter(alice.getOwnDevice(), bob.getOwnDevice()));
+ OmemoMessage.Sent message = alice.encrypt(bob.getOwnJid(), "Hello World!");
+ assertFalse(message.getSkippedDevices().containsKey(bob.getOwnDevice()));
+ }
+
+ // Now the message counter must be too high and Bobs device must be skipped.
+ OmemoMessage.Sent message = alice.encrypt(bob.getOwnJid(), "Hello World!");
+ Throwable exception = message.getSkippedDevices().get(bob.getOwnDevice());
+ assertTrue(exception instanceof ReadOnlyDeviceException);
+ assertEquals(bob.getOwnDevice(), ((ReadOnlyDeviceException) exception).getDevice());
+
+ // Reset the message counter
+ alice.getOmemoService().getOmemoStoreBackend().storeOmemoMessageCounter(alice.getOwnDevice(), bob.getOwnDevice(), 0);
+
+ // Reset the configuration to previous values
+ OmemoConfiguration.setMaxReadOnlyMessageCount(prevMaxMessageCounter);
+ OmemoConfiguration.setIgnoreReadOnlyDevices(prevIgnoreReadOnlyConf);
+ }
+}
diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoConfiguration.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoConfiguration.java
index 8756ef37e..c53f97dac 100644
--- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoConfiguration.java
+++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoConfiguration.java
@@ -30,7 +30,7 @@ public final class OmemoConfiguration {
* Set to true, in order to ignore read-only devices.
*
* @param ignore ignore read-only devices
- * @see Blog Post explaining the danger of read-only devices.
+ * @see Blog Post explaining the danger of read-only devices. TODO: Add URL
*/
public static void setIgnoreReadOnlyDevices(boolean ignore) {
IGNORE_READ_ONLY_DEVICES = ignore;
@@ -40,7 +40,7 @@ public final class OmemoConfiguration {
* Return true, if the client should stop encrypting messages to a read-only device.
*
* @return true if read-only devices should get ignored after a certain amount of unanswered messages.
- * @see Blog Post explaining the danger of read-only devices.
+ * @see Blog Post explaining the danger of read-only devices. TODO: Add URL
*/
public static boolean getIgnoreReadOnlyDevices() {
return IGNORE_READ_ONLY_DEVICES;
@@ -53,7 +53,7 @@ public final class OmemoConfiguration {
* This threshold is used to prevent read-only devices from weakening forward secrecy.
*
* @param maxReadOnlyMessageCount maximum number of allowed messages to a read-only device.
- * @see Blog Post explaining the danger of read-only devices.
+ * @see Blog Post explaining the danger of read-only devices. TODO: Add URL
*/
public static void setMaxReadOnlyMessageCount(int maxReadOnlyMessageCount) {
if (maxReadOnlyMessageCount <= 0) {
@@ -69,7 +69,7 @@ public final class OmemoConfiguration {
* This threshold is used to prevent read-only devices from weakening forward secrecy.
*
* @return maximum number of allowed messages to a read-only device.
- * @see Blog Post explaining the danger of read-only devices.
+ * @see Blog Post explaining the danger of read-only devices. TODO: Add URL
*/
public static int getMaxReadOnlyMessageCount() {
return MAX_READ_ONLY_MESSAGE_COUNT;
diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java
index f8d38706d..27f775f3e 100644
--- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java
+++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java
@@ -19,9 +19,6 @@ package org.jivesoftware.smackx.omemo;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
-import javax.crypto.BadPaddingException;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -39,6 +36,9 @@ import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;