Compare commits

...

31 Commits

Author SHA1 Message Date
Florian Schmaus b9be45ae2c [xdata] Fix ProtectedMembersInFinalClass Error Prone warning 2021-08-03 22:07:04 +02:00
Florian Schmaus 59d3d75a71 Merge branch '4.4' 2021-08-03 21:54:50 +02:00
Florian Schmaus b47225c2c1 [caps] Use the raw character data of form fields when caclulating the hash 2021-07-18 17:22:06 +02:00
Florian Schmaus 097d245358 [xdata] Safe the raw character data of form field values 2021-07-18 17:21:50 +02:00
Florian Schmaus 4643d07ef4 [xdata] Add missing ensureAtMostSingleValue() to parseBooleanFormField 2021-07-18 17:20:50 +02:00
Florian Schmaus a057db107e [ox-im] Annotate sporadically failing assertTrue() 2021-07-16 08:56:48 +02:00
Florian Schmaus f64288ba6d [sinttest] Treat NPE as test failure not as abort condition 2021-07-16 08:56:48 +02:00
Florian Schmaus c9af25c674
Merge pull request #483 from guusdk/SMACK-908_debugger-tabs
SMACK-908: Don't use components to count tabs in Debugger
2021-07-08 12:03:31 +02:00
Guus der Kinderen 0a8da1a07f SMACK-908: Don't use components to count tabs in Debugger
Although the amount of components in a JTabbedPane apparently is often equal to the amount of tabs in it, that need not be the case.
2021-07-08 10:32:19 +02:00
Florian Schmaus 56507a761f Smack 4.4.3
-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAmDkTYtfFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFJ5lwf/eOMA+WdaddTsn/uQj0dq7Qhtt89Zj02bW1zIF6isYs8boGGtOqaQkcqa
 Cc1hLXVg725f8UU6wTy+5MRTUG/vj8/SH/1zbrgI1oizOv0Ug+ipBy5wzM1i5Om7
 1PVpy2yH9UHVXljt9ejUo+WcC/UQSy2REQ4gzm+aTox74lW/rC99NAFa98vr7uHf
 YZAmEp/vAfI0mRymrYxgir/M0aeCY20Dlo2xvUZbugq7Nm6tIHbELm8g6M52XYkw
 7T6VB5W/2p+IZH4itgtfuiQk4jTgkn9T6oVAZzw3kufv0DCuNN9dMtIIfJwXwOv9
 jgv76LBZPzL18lhBDfxyyG0ws97O+A==
 =EZ8x
 -----END PGP SIGNATURE-----

Merge tag '4.4.3'

Smack 4.4.3
2021-07-06 14:51:29 +02:00
Florian Schmaus 524e4b1d9b Smack 4.4.3 2021-07-06 14:33:15 +02:00
Florian Schmaus d33a5a23c3 [core] Introduce Builder.failOnUnknownStates() and unit tests
The previous approach of emitting a severe log message when a
state (descriptor) was unknown was misleading. There are valid cases
where some states are not known, if, for example, a module was
explicitly disabled.

Using Builder.failOnUnknownStates() in unit tests is far cleaner, as
the existence of unknown states is tested in a controlled environment:
one where are states are supposed to be known.
2021-07-06 14:06:39 +02:00
Florian Schmaus b6c8754012 Merge branch '4.4' 2021-07-06 13:38:56 +02:00
Florian Schmaus 31e4e1faf2 Do not call XmlPullParser.getName() when the event is unknown
XmlPullParser.getName() only returns a result if the current parser
event is START_ELEMENT or END_ELEMENT. If this is not the case, then
the method may throw (if StAX is used).
2021-07-06 13:37:57 +02:00
Florian Schmaus 5eef31e49c Do not call XmlPullParser.getName() when the event is unknown
XmlPullParser.getName() only returns a result if the current parser
event is START_ELEMENT or END_ELEMENT. If this is not the case, then
the method may throw (if StAX is used).
2021-07-06 12:33:11 +02:00
Florian Schmaus 4120b42761 [omemo] Add OmemoManager.purgeEveryting()
This is basically the already existing method from
OmemoManagerSetupHelper.cleanUpPubSub() moved over into OmemoManager
as purgeEverything().
2021-07-06 12:31:47 +02:00
Florian Schmaus 3fde4830e4 Merge branch '4.4' 2021-06-25 15:08:36 +02:00
Florian Schmaus 2762325639 [repl] Bump Scala to 2.13.6 and Ammonite to 2.4.0 2021-06-21 15:04:26 +02:00
Florian Schmaus 6434e77336 [sinttest] Use correct camel case in method name: s/MUC/muc/
See also https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
2021-06-21 14:52:18 +02:00
Florian Schmaus 2e18442b11
Merge pull request #475 from Fishbowler/xep-0045-coverage-part1
[sinttest] Refactor MUC tests ready to add more
2021-06-21 14:45:51 +02:00
Florian Schmaus b3e1082c09
Merge pull request #479 from Fishbowler/doc_updates
Various doc additions
2021-06-21 14:45:22 +02:00
Dan Caseley e0754df043 [doc] Initial IDE config guide 2021-06-16 17:48:59 +01:00
Dan Caseley 7b3bd6ff96 [doc] Fix config example in MUC doc 2021-06-16 16:34:11 +01:00
Dan Caseley a0b8ad98c9 [doc] Add debugging section to sinttest doc 2021-06-16 16:33:41 +01:00
Florian Schmaus 82d5ee6ac4
Merge pull request #476 from Fishbowler/doc_updates
[doc] Fix some wording in integrationtest.md
2021-06-15 16:26:46 +02:00
Dan Caseley cedced5b13 [doc] Fix some wording in integrationtest.md 2021-06-15 13:15:30 +01:00
Dan Caseley cd6ff363c6 [sinttest] Refactor MUC tests ready to add more 2021-06-15 13:03:46 +01:00
Florian Schmaus 899813a668
Merge pull request #467 from Flowdalic/4.4-data-forms
Proposed data form updates for 4.4
2021-06-04 20:58:57 +02:00
Florian Schmaus 26e6efa767 [address] Fix NPE in MultipleRecipientManager
The arguments 'to', 'cc, and 'bcc' may be null, hence ensure that they
are non-null when calling e.g. size() on them.

Fixes SMACK-907

Fixes: df96c57093 ("[address] Get rid of PacketCopy workaround")
2021-06-03 19:10:07 +02:00
Florian Schmaus 090858f467 [xdata] Automatically fill required fields with a default value 2021-04-01 12:55:28 +02:00
Florian Schmaus 68edc8b9f5 [xdata] Allow FillableForm.write() ot overwrite already filled fields 2021-04-01 12:54:52 +02:00
56 changed files with 1177 additions and 877 deletions

View File

@ -48,3 +48,16 @@ git clone git@github.com:igniterealtime/Smack.git
cd Smack
gradle assemble
```
IDE Config
----------
### Eclipse
Import IDE settings from `./resources/eclipse/` to configure proper ordering of imports and correct formatting that should pass the CheckStyle rules.
### IntelliJ IDEA
Import Java Code Style settings from `./resources/intellij/smack_formatter.xml` to configure import optimisation and code formatting to pass the CheckStyle rules when building or submitting PRs.
_We've noticed, at time of writing, that IntelliJ often requires a restart when applying new rules - no amount of OK/Apply will do the trick._

View File

@ -4,8 +4,8 @@ Smack's Integration Test Framework
Introduction
------------
Smack's Integration Test Framwork is used to run a set of tests against a real XMPP service.
The framework discovers on startup the available tests by reflection.
Smack's Integration Test Framework is used to run a set of tests against a real XMPP service.
The framework discovers on start-up the available tests by reflection.
Quickstart
----------
@ -120,7 +120,7 @@ The methods are supposed to throw an exception if their integration test fails.
### `TestNotPossibleException`
Can be thrown by test methods or constructors to signal that their test it no possible, e.g. because the service does not support the required feature.
Can be thrown by test methods or constructors to signal that their test is not possible, e.g. because the service does not support the required feature.
Running the integration tests
-----------------------------
@ -130,6 +130,7 @@ Smack's Gradle build system is configured with a special task called `integratio
```bash
$ gradle integrationTest -Dsinttest.service=my.xmppservice.org
```
If one of `accountOneUsername`, `accountOnePassword`, `accountTwoUsername` or `accountTwoPassword` is not configured, then the framework will automatically create the accounts on the service. Of course this requires account registration (IBR) to be enabled.
If the accounts got created automatically by the framework, then they will also be deleted at the end of the test.
@ -161,6 +162,19 @@ The test methods can declare as many parameters as they need to, but every param
The framework will automatically create, register and login the connections.
After the test is finished, the connections will be unregistered with the XMPP service and terminated.
Debugging Integration Tests
------------------------------
A test, like any other code, may not be perfect on the first attempt, and you may require more information in order to ascertain quite what's wrong.
### Smack Debugger options
As listed in the main Smack [Debugging](../debugging.md) doc, there are two built-in debuggers that could surface you more information. Using the 'enhanced' debugger config option listed above, you'll get the Smack Debug Window launching when your tests launch, and you'll get a stanza-by-stanza account of what happened on each connection, hopefully enough to diagnose what went wrong.
### Debugging in the IDE
If the output isn't enough, you may need to debug and inspect running code within the IDE. Depending on the IDE, in order to get execution to pause at your breakpoints, you may need to switch your configuration. Instead of running `gradle integrationTest`, instead run the `SmackIntegrationTestFramework` class directly with the same command-line options.
Running your own integration tests
----------------------------------

View File

@ -472,7 +472,7 @@ muc = manager.getMultiUserChat("myroom@conference.jabber.org");
muc.create("testbot");
// User1 (which is the room owner) configures the room as a moderated room
Form form = muc.getConfigurationForm();
Form answerForm = form.createAnswerForm();
FillableForm answerForm = configForm.getFillableForm();
answerForm.setAnswer("muc#roomconfig_moderatedroom", "1");
muc.sendConfigurationForm(answerForm);
@ -612,7 +612,7 @@ muc = manager.getMultiUserChat("myroom@conference.jabber.org");
muc.create("testbot");
// User1 (which is the room owner) configures the room as a moderated room
Form form = muc.getConfigurationForm();
Form answerForm = form.createAnswerForm();
FillableForm answerForm = configForm.getFillableForm();
answerForm.setAnswer("muc#roomconfig_moderatedroom", "1");
muc.sendConfigurationForm(answerForm);

View File

@ -1,18 +0,0 @@
<code_scheme name="Smack" version="173">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10000" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="1000" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.jivesoftware" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
</value>
</option>
</JavaCodeStyleSettings>
</code_scheme>

View File

@ -0,0 +1,51 @@
<code_scheme name="Project" version="173">
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.jivesoftware.smack" withSubpackages="true" static="false" />
<package name="org.jivesoftware.smackx" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
</value>
</option>
<option name="JD_KEEP_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="INDENT_CASE_FROM_SWITCH" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="5" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="16" />
</indentOptions>
</codeStyleSettings>
</code_scheme>

View File

@ -141,6 +141,17 @@ hr {
<div id="pageBody">
<h2>4.4.3 -- <span style="font-weight: normal;">2021-07-06</span></h2>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-905'>SMACK-905</a>] - The class org.jivesoftware.smackx.offline.packet.OfflineMessageInfo has no ELEMENT, NAMESPACE or QNAME member
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-907'>SMACK-907</a>] - Possible NPE in MultipleRecipientManager
</li>
</ul>
<h2>4.4.2 -- <span style="font-weight: normal;">2021-03-25</span></h2>
<h2> Bug

View File

@ -54,7 +54,7 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn
}
try {
initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors);
initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors, builder.failOnUnknownStates);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
@ -91,6 +91,8 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn
private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>();
private boolean failOnUnknownStates;
private Builder() {
SmackConfiguration.addAllKnownModulesTo(this);
}
@ -163,6 +165,17 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn
return getThis();
}
/**
* Fail if there are unknown states in Smack's state descriptor graph. This method is used mostly for testing
* the internals of Smack. Users can safely ignore it.
*
* @return a reference to this builder.
*/
public Builder failOnUnknownStates() {
failOnUnknownStates = true;
return getThis();
}
@Override
protected Builder getThis() {
return this;

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.DisconnectedStateDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
@ -47,8 +46,6 @@ import org.jivesoftware.smack.util.MultiMap;
*/
public class StateDescriptorGraph {
private static final Logger LOGGER = Logger.getLogger(StateDescriptorGraph.class.getName());
private static GraphVertex<StateDescriptor> addNewStateDescriptorGraphVertex(
Class<? extends StateDescriptor> stateDescriptorClass,
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes)
@ -102,7 +99,8 @@ public class StateDescriptorGraph {
}
private static void handleStateDescriptorGraphVertex(GraphVertex<StateDescriptor> node,
HandleStateDescriptorGraphVertexContext context)
HandleStateDescriptorGraphVertexContext context,
boolean failOnUnknownStates)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Class<? extends StateDescriptor> stateDescriptorClass = node.element.getClass();
boolean alreadyHandled = context.recurseInto(stateDescriptorClass);
@ -126,7 +124,7 @@ public class StateDescriptorGraph {
case 1:
GraphVertex<StateDescriptor> soleSuccessorNode = successorStateDescriptors.values().iterator().next();
node.addOutgoingEdge(soleSuccessorNode);
handleStateDescriptorGraphVertex(soleSuccessorNode, context);
handleStateDescriptorGraphVertex(soleSuccessorNode, context, failOnUnknownStates);
return;
}
@ -144,9 +142,8 @@ public class StateDescriptorGraph {
StateDescriptor successorStateDescriptor = successorStateDescriptorGraphNode.element;
Class<? extends StateDescriptor> successorStateDescriptorClass = successorStateDescriptor.getClass();
for (Class<? extends StateDescriptor> subordinateClass : successorStateDescriptor.getSubordinates()) {
if (!successorClasses.contains(subordinateClass)) {
LOGGER.severe(successorStateDescriptor + " points to a subordinate '" + subordinateClass + "' which is not part of the successor set");
continue;
if (failOnUnknownStates && !successorClasses.contains(subordinateClass)) {
throw new IllegalStateException(successorStateDescriptor + " points to a subordinate '" + subordinateClass + "' which is not part of the successor set");
}
GraphVertex<Class<? extends StateDescriptor>> superiorClassNode = lookupAndCreateIfRequired(
@ -157,10 +154,9 @@ public class StateDescriptorGraph {
superiorClassNode.addOutgoingEdge(subordinateClassNode);
}
for (Class<? extends StateDescriptor> superiorClass : successorStateDescriptor.getSuperiors()) {
if (!successorClasses.contains(superiorClass)) {
LOGGER.severe(successorStateDescriptor + " points to a superior '" + superiorClass
if (failOnUnknownStates && !successorClasses.contains(superiorClass)) {
throw new IllegalStateException(successorStateDescriptor + " points to a superior '" + superiorClass
+ "' which is not part of the successor set");
continue;
}
GraphVertex<Class<? extends StateDescriptor>> subordinateClassNode = lookupAndCreateIfRequired(
@ -193,11 +189,13 @@ public class StateDescriptorGraph {
node.addOutgoingEdge(successorVertex);
// Recurse further.
handleStateDescriptorGraphVertex(successorVertex, context);
handleStateDescriptorGraphVertex(successorVertex, context, failOnUnknownStates);
}
}
public static GraphVertex<StateDescriptor> constructStateDescriptorGraph(Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors)
public static GraphVertex<StateDescriptor> constructStateDescriptorGraph(
Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors,
boolean failOnUnknownStates)
throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes = new HashMap<>();
@ -219,7 +217,7 @@ public class StateDescriptorGraph {
}
HandleStateDescriptorGraphVertexContext context = new HandleStateDescriptorGraphVertexContext(graphVertexes, inferredForwardEdges);
handleStateDescriptorGraphVertex(initialNode, context);
handleStateDescriptorGraphVertex(initialNode, context, failOnUnknownStates);
return initialNode;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2020 Florian Schmaus
* Copyright 2015-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -82,4 +82,11 @@ public class CollectionUtil {
}
return new HashSet<>(collection);
}
public static <T> List<T> emptyOrSingletonListFrom(T element) {
if (element == null) {
return Collections.emptyList();
}
return Collections.singletonList(element);
}
}

View File

@ -140,12 +140,12 @@ public final class EnhancedDebuggerWindow {
if (frame == null) {
createDebug();
}
debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getComponentCount());
tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1);
debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getTabCount());
tabbedPane.add(debugger.tabbedPane, -1);
tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon);
tabbedPane.setSelectedIndex(tabbedPane.indexOfComponent(debugger.tabbedPane));
frame.setTitle(
"Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1));
"Smack Debug Window -- Total connections: " + (tabbedPane.getTabCount() - 1));
// Keep the added debugger for later access
debuggers.add(debugger);
}
@ -286,7 +286,7 @@ public final class EnhancedDebuggerWindow {
@Override
public void actionPerformed(ActionEvent e) {
// Remove the selected tab pane if it's not the Smack info pane
if (tabbedPane.getSelectedIndex() < tabbedPane.getComponentCount() - 1) {
if (tabbedPane.getSelectedIndex() < tabbedPane.getTabCount() - 1) {
int index = tabbedPane.getSelectedIndex();
// Notify to the debugger to stop debugging
EnhancedDebugger debugger = debuggers.get(index);
@ -297,7 +297,7 @@ public final class EnhancedDebuggerWindow {
// Update the root window title
frame.setTitle(
"Smack Debug Window -- Total connections: "
+ (tabbedPane.getComponentCount() - 1));
+ (tabbedPane.getTabCount() - 1));
}
}
});
@ -309,7 +309,7 @@ public final class EnhancedDebuggerWindow {
public void actionPerformed(ActionEvent e) {
ArrayList<EnhancedDebugger> debuggersToRemove = new ArrayList<>();
// Remove all the debuggers of which their connections are no longer valid
for (int index = 0; index < tabbedPane.getComponentCount() - 1; index++) {
for (int index = 0; index < tabbedPane.getTabCount() - 1; index++) {
EnhancedDebugger debugger = debuggers.get(index);
if (!debugger.isConnectionActive()) {
// Notify to the debugger to stop debugging
@ -325,7 +325,7 @@ public final class EnhancedDebuggerWindow {
// Update the root window title
frame.setTitle(
"Smack Debug Window -- Total connections: "
+ (tabbedPane.getComponentCount() - 1));
+ (tabbedPane.getTabCount() - 1));
}
});
menu.add(menuItem);

View File

@ -1,6 +1,6 @@
/**
*
* Copyright © 2016-2019 Florian Schmaus
* Copyright © 2016-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,9 +39,9 @@ public class IoTSetRequestProvider extends IQProvider<IoTSetRequest> {
List<SetData> data = new ArrayList<>(4);
outerloop: while (true) {
final XmlPullParser.Event eventType = parser.next();
final String name = parser.getName();
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
switch (name) {
case "bool": {
String valueName = parser.getAttributeValue(null, "name");

View File

@ -110,9 +110,9 @@ public class IoTFieldsExtensionProvider extends ExtensionElementProvider<IoTFiel
List<IoTDataField> fields = new ArrayList<>();
outerloop: while (true) {
final XmlPullParser.Event eventType = parser.next();
final String name = parser.getName();
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
IoTDataField field = null;
final String fieldName = parser.getAttributeValue(null, "name");
final String fieldValue = parser.getAttributeValue(null, "value");

View File

@ -59,9 +59,9 @@ public class MamPrefsIQProvider extends IQProvider<MamPrefsIQ> {
outerloop: while (true) {
final XmlPullParser.Event eventType = parser.next();
final String name = parser.getName();
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
switch (name) {
case "always":
alwaysJids = iterateJids(parser);
@ -92,9 +92,9 @@ public class MamPrefsIQProvider extends IQProvider<MamPrefsIQ> {
outerloop: while (true) {
final XmlPullParser.Event eventType = parser.next();
final String name = parser.getName();
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
switch (name) {
case "jid":
parser.next();

View File

@ -47,10 +47,10 @@ public class MamQueryIQProvider extends IQProvider<MamQueryIQ> {
outerloop: while (true) {
final XmlPullParser.Event eventType = parser.next();
final String name = parser.getName();
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
switch (name) {
case DataForm.ELEMENT:
dataForm = DataFormProvider.INSTANCE.parse(parser);

View File

@ -49,9 +49,9 @@ public class MamResultProvider extends ExtensionElementProvider<MamResultExtensi
outerloop: while (true) {
final XmlPullParser.Event eventType = parser.next();
final String name = parser.getName();
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
switch (name) {
case Forwarded.ELEMENT:
forwarded = ForwardedProvider.parseForwardedMessage(parser, xmlEnvironment);

View File

@ -50,10 +50,11 @@ public class MarkupElementProvider extends ExtensionElementProvider<MarkupElemen
while (true) {
XmlPullParser.Event tag = parser.next();
String name = parser.getName();
String name;
int start, end;
switch (tag) {
case START_ELEMENT:
name = parser.getName();
switch (name) {
case BlockQuoteElement.ELEMENT:
start = ParserUtils.getIntegerAttributeOrThrow(parser, MarkupChildElement.ATTR_START,
@ -108,6 +109,11 @@ public class MarkupElementProvider extends ExtensionElementProvider<MarkupElemen
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
return markup.build();
}
name = parser.getName();
switch (name) {
case SpanElement.ELEMENT:
markup.addSpan(spanStart, spanEnd, spanStyles);
@ -128,9 +134,6 @@ public class MarkupElementProvider extends ExtensionElementProvider<MarkupElemen
}
listBuilder.endList();
break;
case MarkupElement.ELEMENT:
return markup.build();
}
break;

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.address;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
@ -228,18 +229,16 @@ public class MultipleRecipientManager {
throw new AssertionError();
}
if (to == null) to = Collections.emptyList();
if (cc == null) cc = Collections.emptyList();
if (bcc == null) bcc = Collections.emptyList();
final int numRecipients = to.size() + cc.size() + bcc.size();
final List<Jid> recipients = new ArrayList<>(numRecipients);
if (to != null) {
recipients.addAll(to);
}
if (cc != null) {
recipients.addAll(cc);
}
if (bcc != null) {
recipients.addAll(bcc);
}
recipients.addAll(to);
recipients.addAll(cc);
recipients.addAll(bcc);
final List<Stanza> stanzasToSend = new ArrayList<>(numRecipients);
for (Jid recipient : recipients) {

View File

@ -55,8 +55,8 @@ public class BytestreamsProvider extends IQProvider<Bytestream> {
String elementName;
while (!done) {
eventType = parser.next();
elementName = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
elementName = parser.getName();
if (elementName.equals(Bytestream.StreamHost.ELEMENT)) {
JID = ParserUtils.getJidAttribute(parser);
host = parser.getAttributeValue("", "host");
@ -70,6 +70,7 @@ public class BytestreamsProvider extends IQProvider<Bytestream> {
}
}
else if (eventType == XmlPullParser.Event.END_ELEMENT) {
elementName = parser.getName();
if (elementName.equals("streamhost")) {
if (port == null) {
toReturn.addStreamHost(JID, host);

View File

@ -700,7 +700,7 @@ public final class EntityCapsManager extends Manager {
for (FormField f : fs) {
sb.append(f.getFieldName());
sb.append('<');
formFieldValuesToCaps(f.getValues(), sb);
formFieldValuesToCaps(f.getRawValues(), sb);
}
}

View File

@ -78,9 +78,9 @@ public class AdHocCommandDataProvider extends IQProvider<AdHocCommandData> {
}
while (!done) {
eventType = parser.next();
elementName = parser.getName();
namespace = parser.getNamespace();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
elementName = parser.getName();
if (parser.getName().equals("actions")) {
String execute = parser.getAttributeValue("", "execute");
if (execute != null) {

View File

@ -31,7 +31,6 @@ import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.jingle.element.JingleContentTransport;
import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidate;
import org.jivesoftware.smackx.jingle.provider.JingleContentTransportProvider;
import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport;
@ -62,9 +61,9 @@ public class JingleS5BTransportProvider extends JingleContentTransportProvider<J
JingleS5BTransportCandidate.Builder cb;
outerloop: while (true) {
XmlPullParser.TagEvent tag = parser.nextTag();
String name = parser.getName();
switch (tag) {
case START_ELEMENT: {
String name = parser.getName();
switch (name) {
case JingleContentTransportCandidate.ELEMENT:
@ -110,8 +109,7 @@ public class JingleS5BTransportProvider extends JingleContentTransportProvider<J
break;
case END_ELEMENT: {
switch (name) {
case JingleContentTransport.ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}

View File

@ -45,11 +45,11 @@ public class MoodProvider extends ExtensionElementProvider<MoodElement> {
outerloop: while (true) {
XmlPullParser.Event tag = parser.next();
String name = parser.getName();
String namespace = parser.getNamespace();
switch (tag) {
case START_ELEMENT:
String name = parser.getName();
String namespace = parser.getNamespace();
if (MoodElement.ELEM_TEXT.equals(name)) {
text = parser.nextText();
continue outerloop;
@ -74,7 +74,7 @@ public class MoodProvider extends ExtensionElementProvider<MoodElement> {
}
case END_ELEMENT:
if (MoodElement.ELEMENT.equals(name)) {
if (MoodElement.ELEMENT.equals(parser.getName())) {
MoodElement.MoodSubjectElement subjectElement = (mood == null && concretisation == null) ?
null : new MoodElement.MoodSubjectElement(mood, concretisation);
return new MoodElement(subjectElement, text);

View File

@ -46,8 +46,6 @@ public class StreamInitiationProvider extends IQProvider<StreamInitiation> {
@Override
public StreamInitiation parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
boolean done = false;
// si
String id = parser.getAttributeValue("", "id");
String mimeType = parser.getAttributeValue("", "mime-type");
@ -66,13 +64,11 @@ public class StreamInitiationProvider extends IQProvider<StreamInitiation> {
DataForm form = null;
DataFormProvider dataFormProvider = new DataFormProvider();
String elementName;
String namespace;
while (!done) {
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
elementName = parser.getName();
namespace = parser.getNamespace();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (elementName.equals("file")) {
name = parser.getAttributeValue("", "name");
size = parser.getAttributeValue("", "size");
@ -87,9 +83,10 @@ public class StreamInitiationProvider extends IQProvider<StreamInitiation> {
form = dataFormProvider.parse(parser);
}
} else if (eventType == XmlPullParser.Event.END_ELEMENT) {
if (elementName.equals("si")) {
done = true;
} else if (elementName.equals("file")) {
if (parser.getDepth() == initialDepth) {
break outerloop;
}
if (parser.getName().equals("file")) {
long fileSize = 0;
if (size != null && size.trim().length() != 0) {
try {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus
* Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,9 +29,12 @@ public class AbstractMultiFormField extends FormField {
private final List<String> values;
private final List<String> rawValues;
protected AbstractMultiFormField(Builder<?, ?> builder) {
super(builder);
values = CollectionUtil.cloneAndSeal(builder.values);
rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);
}
@Override
@ -39,11 +42,16 @@ public class AbstractMultiFormField extends FormField {
return values;
}
@Override
public final List<String> getRawValues() {
return rawValues;
}
public abstract static class Builder<F extends FormField, B extends FormField.Builder<F, B>>
public abstract static class Builder<F extends AbstractMultiFormField, B extends FormField.Builder<F, B>>
extends FormField.Builder<F, B> {
private List<String> values;
private List<String> rawValues;
protected Builder(AbstractMultiFormField formField) {
super(formField);
@ -57,6 +65,7 @@ public class AbstractMultiFormField extends FormField {
private void ensureValuesAreInitialized() {
if (values == null) {
values = new ArrayList<>();
rawValues = new ArrayList<>();
}
}
@ -70,7 +79,9 @@ public class AbstractMultiFormField extends FormField {
public B addValueVerbatim(CharSequence value) {
ensureValuesAreInitialized();
values.add(value.toString());
String valueString = value.toString();
values.add(valueString);
rawValues.add(valueString);
return getThis();
}
@ -83,7 +94,7 @@ public class AbstractMultiFormField extends FormField {
ensureValuesAreInitialized();
for (CharSequence value : values) {
this.values.add(value.toString());
addValueVerbatim(value);
}
return getThis();

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus.
* Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,7 +42,8 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField {
return Integer.valueOf(value);
}
public abstract static class Builder<F extends FormField, B extends FormField.Builder<F, B>> extends FormField.Builder<F, B> {
public abstract static class Builder<F extends SingleValueFormField, B extends SingleValueFormField.Builder<F, B>>
extends SingleValueFormField.Builder<F, B> {
private String value;
@ -74,18 +75,16 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField {
}
public B setValue(CharSequence value) {
this.value = value.toString();
this.rawValue = this.value = value.toString();
return getThis();
}
public B setValue(Enum<?> value) {
this.value = value.toString();
return getThis();
return setValue(value.toString());
}
public B setValue(int value) {
this.value = Integer.toString(value);
return getThis();
return setValue(Integer.toString(value));
}
public B setValue(URL value) {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus.
* Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -43,7 +43,7 @@ public class BooleanFormField extends SingleValueFormField {
return new Builder(this);
}
public static final class Builder extends FormField.Builder<BooleanFormField, BooleanFormField.Builder> {
public static final class Builder extends SingleValueFormField.Builder<BooleanFormField, BooleanFormField.Builder> {
private Boolean value;
private Builder(BooleanFormField booleanFormField) {
@ -57,6 +57,7 @@ public class BooleanFormField extends SingleValueFormField {
@Override
protected void resetInternal() {
super.resetInternal();
value = null;
}
@ -74,11 +75,13 @@ public class BooleanFormField extends SingleValueFormField {
}
public Builder setValue(CharSequence value) {
boolean valueBoolean = ParserUtils.parseXmlBoolean(value.toString());
rawValue = value.toString();
boolean valueBoolean = ParserUtils.parseXmlBoolean(rawValue);
return setValue(valueBoolean);
}
public Builder setValue(boolean value) {
rawValue = Boolean.toString(value);
this.value = value;
return this;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2019-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -271,6 +271,8 @@ public abstract class FormField implements XmlElement {
*/
public abstract List<? extends CharSequence> getValues();
public abstract List<String> getRawValues();
public boolean hasValueSet() {
List<?> values = getValues();
return !values.isEmpty();

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus.
* Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,14 +23,18 @@ import java.util.List;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.util.JidUtil;
public class JidMultiFormField extends FormField {
public final class JidMultiFormField extends FormField {
private final List<Jid> values;
protected JidMultiFormField(Builder builder) {
private final List<String> rawValues;
JidMultiFormField(Builder builder) {
super(builder);
values = CollectionUtil.cloneAndSeal(builder.values);
rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);
}
@Override
@ -38,6 +42,11 @@ public class JidMultiFormField extends FormField {
return values;
}
@Override
public List<String> getRawValues() {
return rawValues;
}
public Builder asBuilder() {
return new Builder(this);
}
@ -45,6 +54,8 @@ public class JidMultiFormField extends FormField {
public static final class Builder extends FormField.Builder<JidMultiFormField, JidMultiFormField.Builder> {
private List<Jid> values;
private List<String> rawValues;
private Builder(JidMultiFormField jidMultiFormField) {
super(jidMultiFormField);
values = CollectionUtil.newListWith(jidMultiFormField.getValues());
@ -57,18 +68,30 @@ public class JidMultiFormField extends FormField {
private void ensureValuesAreInitialized() {
if (values == null) {
values = new ArrayList<>();
rawValues = new ArrayList<>();
}
}
@Override
protected void resetInternal() {
values = null;
rawValues = null;
}
public Builder addValue(Jid jid) {
return addValue(jid, null);
}
public Builder addValue(Jid jid, String rawValue) {
if (rawValue == null) {
rawValue = jid.toString();
}
ensureValuesAreInitialized();
values.add(jid);
rawValues.add(rawValue);
return this;
}
@ -76,6 +99,7 @@ public class JidMultiFormField extends FormField {
ensureValuesAreInitialized();
values.addAll(jids);
rawValues.addAll(JidUtil.toStringList(jids));
return this;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus.
* Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -36,7 +36,7 @@ public class JidSingleFormField extends SingleValueFormField {
return new Builder(this);
}
public static final class Builder extends FormField.Builder<JidSingleFormField, JidSingleFormField.Builder> {
public static final class Builder extends SingleValueFormField.Builder<JidSingleFormField, JidSingleFormField.Builder> {
private Jid value;
private Builder(JidSingleFormField jidSingleFormField) {
@ -50,11 +50,21 @@ public class JidSingleFormField extends SingleValueFormField {
@Override
protected void resetInternal() {
super.resetInternal();
value = null;
}
public Builder setValue(Jid value) {
return setValue(value, null);
}
public Builder setValue(Jid value, String rawValue) {
this.value = value;
if (rawValue != null) {
this.rawValue = rawValue;
} else {
this.rawValue = value.toString();
}
return this;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus.
* Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,23 +19,35 @@ package org.jivesoftware.smackx.xdata;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.util.CollectionUtil;
public abstract class SingleValueFormField extends FormField {
private final String rawValue;
protected SingleValueFormField(Builder<?, ?> builder) {
super(builder);
rawValue = builder.rawValue;
}
@Override
public final List<CharSequence> getValues() {
CharSequence value = getValue();
if (value == null) {
return Collections.emptyList();
}
return Collections.singletonList(value);
return CollectionUtil.emptyOrSingletonListFrom(value);
}
public abstract CharSequence getValue();
public final String getRawValue() {
return rawValue;
}
@Override
public final List<String> getRawValues() {
String rawValue = getRawValue();
return CollectionUtil.emptyOrSingletonListFrom(rawValue);
}
@Override
protected void populateExtraXmlChildElements() {
CharSequence value = getValue();
@ -45,4 +57,24 @@ public abstract class SingleValueFormField extends FormField {
extraXmlChildElements = Collections.singletonList(new Value(value));
}
public abstract static class Builder<F extends SingleValueFormField, B extends Builder<F, B>>
extends FormField.Builder<F, B> {
protected Builder(String fieldName, Type type) {
super(fieldName, type);
}
protected Builder(FormField formField) {
super(formField);
}
protected String rawValue;
@Override
protected void resetInternal() {
rawValue = null;
};
}
}

View File

@ -58,7 +58,13 @@ public class FillableForm extends FilledForm {
if (formField.isRequired()) {
String fieldName = formField.getFieldName();
requiredFields.add(fieldName);
missingRequiredFields.add(fieldName);
if (formField.hasValueSet()) {
// This is a form field with a default value.
write(formField);
} else {
missingRequiredFields.add(fieldName);
}
}
}
this.requiredFields = Collections.unmodifiableSet(requiredFields);
@ -223,9 +229,6 @@ public class FillableForm extends FilledForm {
if (!getDataForm().hasField(fieldName)) {
throw new IllegalArgumentException();
}
if (filledFields.containsKey(fieldName)) {
throw new IllegalArgumentException();
}
// Perform validation, e.g. using XEP-0122.
// TODO: We could also perform list-* option validation, but this has to take xep122's <open/> into account.

View File

@ -237,8 +237,9 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
case jid_multi:
JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName);
for (FormField.Value value : values) {
Jid jid = JidCreate.from(value.getValue());
jidMultiBuilder.addValue(jid);
String rawValue = value.getValue().toString();
Jid jid = JidCreate.from(rawValue);
jidMultiBuilder.addValue(jid, rawValue);
}
builder = jidMultiBuilder;
break;
@ -246,9 +247,9 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
ensureAtMostSingleValue(type, values);
JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName);
if (!values.isEmpty()) {
CharSequence jidCharSequence = values.get(0).getValue();
Jid jid = JidCreate.from(jidCharSequence);
jidSingleBuilder.setValue(jid);
String rawValue = values.get(0).getValue().toString();
Jid jid = JidCreate.from(rawValue);
jidSingleBuilder.setValue(jid, rawValue);
}
builder = jidSingleBuilder;
break;
@ -300,6 +301,7 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
private static FormField.Builder<?, ?> parseBooleanFormField(String fieldName, List<FormField.Value> values) throws SmackParsingException {
BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName);
ensureAtMostSingleValue(builder.getType(), values);
if (values.size() == 1) {
String value = values.get(0).getValue().toString();
builder.setValue(value);
@ -321,7 +323,8 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
private static AbstractMultiFormField.Builder<?, ?> parseMultiKindFormField(AbstractMultiFormField.Builder<?, ?> builder,
List<FormField.Value> values) {
for (FormField.Value value : values) {
builder.addValue(value.getValue());
String rawValue = value.getValue().toString();
builder.addValue(rawValue);
}
return builder;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2014-2019 Florian Schmaus
* Copyright 2003-2007 Jive Software, 2014-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -40,8 +40,8 @@ public class XHTMLExtensionProvider extends ExtensionElementProvider<XHTMLExtens
while (true) {
XmlPullParser.Event eventType = parser.getEventType();
String name = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String name = parser.getName();
if (name.equals(Message.BODY)) {
xhtmlExtension.addBody(PacketParserUtils.parseElement(parser));
}

View File

@ -590,6 +590,14 @@ public class SmackIntegrationTestFramework {
if (e instanceof InterruptedException) {
throw (InterruptedException) e;
}
// We handle NullPointerException as a non fatal exception, as they are mostly caused by an invalid reply where
// a extension element is missing. Consider for example
// assertEquals(StanzaError.Condition.foo, response.getError().getCondition())
// Otherwise NPEs could be caused by an internal bug in Smack, e.g. missing null handling.
if (e instanceof NullPointerException) {
return (NullPointerException) e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}

View File

@ -0,0 +1,107 @@
/**
*
* Copyright 2021 Florian Schmaus
*
* 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.muc;
import java.util.List;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.xdata.form.FillableForm;
import org.jivesoftware.smackx.xdata.form.Form;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
final String randomString = StringUtils.insecureRandomString(6);
final MultiUserChatManager mucManagerOne;
final MultiUserChatManager mucManagerTwo;
final MultiUserChatManager mucManagerThree;
final DomainBareJid mucService;
public AbstractMultiUserChatIntegrationTest(SmackIntegrationTestEnvironment environment)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
InterruptedException, TestNotPossibleException {
super(environment);
mucManagerOne = MultiUserChatManager.getInstanceFor(conOne);
mucManagerTwo = MultiUserChatManager.getInstanceFor(conTwo);
mucManagerThree = MultiUserChatManager.getInstanceFor(conThree);
List<DomainBareJid> services = mucManagerOne.getMucServiceDomains();
if (services.isEmpty()) {
throw new TestNotPossibleException("No MUC (XEP-45) service found");
}
else {
mucService = services.get(0);
}
}
/**
* Gets a random room name.
*
* @param prefix A prefix to add to the room name for descriptive purposes
* @return the bare JID of a random room
* @throws XmppStringprepException if the prefix isn't a valid XMPP Localpart
*/
public EntityBareJid getRandomRoom(String prefix) throws XmppStringprepException {
final String roomNameLocal = String.join("-", prefix, testRunId, StringUtils.insecureRandomString(6));
return JidCreate.entityBareFrom(Localpart.from(roomNameLocal), mucService.getDomain());
}
/**
* Destroys a MUC room, ignoring any exceptions.
*
* @param muc The room to destroy (can be null).
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackException.NotConnectedException if the XMPP connection is not connected.
* @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
* @throws SmackException.NoResponseException if there was no response from the remote entity.
*/
static void tryDestroy(final MultiUserChat muc) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException {
if (muc == null) {
return;
}
muc.destroy("test fixture teardown", null);
}
static void createMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
MultiUserChat.MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName));
if (handle != null) {
handle.makeInstant();
}
}
static void createModeratedMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
muc.create(Resourcepart.from(resourceName));
Form configForm = muc.getConfigurationForm();
FillableForm answerForm = configForm.getFillableForm();
answerForm.setAnswer("muc#roomconfig_moderatedroom", true); //TODO Add this to the MucConfigFormManager?
muc.sendConfigurationForm(answerForm);
}
}

View File

@ -16,66 +16,35 @@
*/
package org.jivesoftware.smackx.muc;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle;
import org.jivesoftware.smackx.muc.packet.MUCUser;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
private final String randomString = StringUtils.insecureRandomString(6);
private final MultiUserChatManager mucManagerOne;
private final MultiUserChatManager mucManagerTwo;
private final MultiUserChatManager mucManagerThree;
private final DomainBareJid mucService;
public class MultiUserChatIntegrationTest extends AbstractMultiUserChatIntegrationTest {
public MultiUserChatIntegrationTest(SmackIntegrationTestEnvironment environment)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, TestNotPossibleException {
throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
InterruptedException, TestNotPossibleException {
super(environment);
mucManagerOne = MultiUserChatManager.getInstanceFor(conOne);
mucManagerTwo = MultiUserChatManager.getInstanceFor(conTwo);
mucManagerThree = MultiUserChatManager.getInstanceFor(conThree);
List<DomainBareJid> services = mucManagerOne.getMucServiceDomains();
if (services.isEmpty()) {
throw new TestNotPossibleException("No MUC (XEP-45) service found");
}
else {
mucService = services.get(0);
}
}
/**
@ -91,7 +60,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
*/
@SmackIntegrationTest
public void mucJoinTest() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest-join-leave");
EntityBareJid mucAddress = getRandomRoom("smack-inttest-join");
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
try {
@ -122,7 +91,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
*/
@SmackIntegrationTest
public void mucLeaveTest() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest-join-leave");
EntityBareJid mucAddress = getRandomRoom("smack-inttest-leave");
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
try {
@ -137,13 +106,14 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
assertEquals(mucAddress + "/nick-one", reflectedLeavePresence.getFrom().toString());
assertEquals(conOne.getUser().asEntityFullJidIfPossible().toString(), reflectedLeavePresence.getTo().toString());
} finally {
muc.join(Resourcepart.from("nick-one")); // We need to be in the room to destroy the room
tryDestroy(muc);
}
}
@SmackIntegrationTest
public void mucTest() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
EntityBareJid mucAddress = getRandomRoom("smack-inttest-message");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
@ -161,7 +131,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
createMuc(mucAsSeenByOne, "one-" + randomString);
mucAsSeenByTwo.join(Resourcepart.from("two-" + randomString));
mucAsSeenByOne.sendMessage(mucMessage);
try {
@ -173,581 +143,9 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
}
}
/**
* Asserts that a user who undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.6:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the addition of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForReceivingModerator() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void moderatorGranted() {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
// This implicitly tests "The service MUST add the user to the moderator list and then inform the admin of
// success" in §9.6, since it'll throw on either an error IQ or on no response.
mucAsSeenByOne.grantModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.6:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the addition of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForWitnessingModerator() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void moderatorGranted(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.7:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForRemovingModerator() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void moderatorRevoked() {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByOne.grantModerator(nicknameTwo);
mucAsSeenByOne.revokeModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.7:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForWitnessingModeratorRemoval() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void moderatorRevoked(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantModerator(nicknameTwo);
mucAsSeenByOne.revokeModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user in an unmoderated room who undergoes an afilliation change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 8.4:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* voice privileges...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForRevokingVoice() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void voiceRevoked() {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByOne.revokeVoice(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 8.4:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* voice privileges...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForWitnessingRevokingVoice() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void voiceRevoked(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.revokeVoice(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who undergoes an affiliation change receives that change as a presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.6:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the granting of admin status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForReceivingAdmin() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void adminGranted() {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
// This implicitly tests "The service MUST add the user to the admin list and then inform the owner of success" in §10.6, since it'll throw on either an error IQ or on no response.
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes an affiliation change receives that change as a
* presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.6:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the granting of admin status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForWitnessingAdmin() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void adminGranted(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to
* all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.7:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the loss of admin status by sending a presence element...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForRemovingAdmin() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void adminRevoked() {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to
* all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.7:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the loss of admin status by sending a presence element...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForWitnessingAdminRemoval() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void adminRevoked(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who gets kicked receives that change as a presence update
*
* <p>From XEP-0045 § 8.2:</p>
* <blockquote>
* The kick is performed based on the occupant's room nickname and is completed by setting the role of a
* participant or visitor to a value of "none".
*
* The service MUST remove the kicked occupant by sending a presence stanza of type "unavailable" to each kicked
* occupant, including status code 307 in the extended presence information, optionally along with the reason (if
* provided) and the roomnick or bare JID of the user who initiated the kick.
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucPresenceTestForGettingKicked() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
final ResultSyncPoint<Presence, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
try {
Presence kickPresence = resultSyncPoint.waitForResult(timeout);
MUCUser mucUser = MUCUser.from(kickPresence);
assertNotNull(mucUser);
assertAll(
() -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Missing self-presence status code in kick presence"),
() -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"),
() -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence")
);
Jid itemJid = mucUser.getItem().getJid();
if (itemJid != null) {
assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence");
}
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user gets kicked receives that change as a presence update
*
* <p>From XEP-0045 § 8.2:</p>
* <blockquote>
* ...the service MUST then inform all of the remaining occupants that the kicked occupant is no longer in the room
* by sending presence stanzas of type "unavailable" from the individual's roomnick (&lt;room@service/nick&gt;) to all
* the remaining occupants (just as it does when occupants exit the room of their own volition), including the
* status code and optionally the reason and actor.
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucPresenceTestForWitnessingKick() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
createMUC(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
final ResultSyncPoint<Presence, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
try {
Presence kickPresence = resultSyncPoint.waitForResult(timeout);
MUCUser mucUser = MUCUser.from(kickPresence);
assertNotNull(mucUser);
assertAll(
() -> assertFalse(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Incorrect self-presence status code in kick presence"),
() -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"),
() -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence")
);
Jid itemJid = mucUser.getItem().getJid();
if (itemJid != null) {
assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence");
}
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
* Asserts that a user is notified when a room is destroyed
*
* <p>From XEP-0045 § 10.9:</p>
* <blockquote>
@ -760,7 +158,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
@SmackIntegrationTest
public void mucDestroyTest() throws TimeoutException, Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest-join-leave");
EntityBareJid mucAddress = getRandomRoom("smack-inttest-destroy");
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
muc.join(Resourcepart.from("nick-one"));
@ -793,37 +191,6 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
assertNull(muc.getNickname());
}
/**
* Gets a random room name
*
* @param prefix A prefix to add to the room name for descriptive purposes
*/
private EntityBareJid getRandomRoom(String prefix) throws XmppStringprepException {
final String roomNameLocal = String.join("-", prefix, testRunId, StringUtils.insecureRandomString(6));
return JidCreate.entityBareFrom(Localpart.from(roomNameLocal), mucService.getDomain());
}
/**
* Destroys a MUC room, ignoring any exceptions.
*
* @param muc The room to destroy (can be null).
* @throws InterruptedException if the calling thread was interrupted.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
*/
static void tryDestroy(final MultiUserChat muc) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
if (muc == null) {
return;
}
muc.destroy("test fixture teardown", null);
}
private static void createMUC(MultiUserChat muc, String resourceName) throws NoResponseException, XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName));
if (handle != null) {
handle.makeInstant();
}
}
}

View File

@ -0,0 +1,622 @@
/**
*
* Copyright 2021 Florian Schmaus
*
* 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.muc;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.muc.packet.MUCUser;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Resourcepart;
public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends AbstractMultiUserChatIntegrationTest{
public MultiUserChatRolesAffiliationsPrivilegesIntegrationTest(SmackIntegrationTestEnvironment environment)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
InterruptedException, TestNotPossibleException {
super(environment);
}
/**
* Asserts that a user who undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.6:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the addition of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForReceivingModerator() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void moderatorGranted() {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
// This implicitly tests "The service MUST add the user to the moderator list and then inform the admin of
// success" in §9.6, since it'll throw on either an error IQ or on no response.
mucAsSeenByOne.grantModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.6:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the addition of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForWitnessingModerator() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void moderatorGranted(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.7:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForRemovingModerator() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void moderatorRevoked() {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByOne.grantModerator(nicknameTwo);
mucAsSeenByOne.revokeModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 9.7:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* moderator status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForWitnessingModeratorRemoval() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void moderatorRevoked(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantModerator(nicknameTwo);
mucAsSeenByOne.revokeModerator(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user in an unmoderated room who undergoes an afilliation change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 8.4:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* voice privileges...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForRevokingVoice() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void voiceRevoked() {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByOne.revokeVoice(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes a role change receives that change as a presence update
*
* <p>From XEP-0045 § 5.1.3:</p>
* <blockquote>
* ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 8.4:</p>
* <blockquote>
* The service MUST then send updated presence from this individual to all occupants, indicating the removal of
* voice privileges...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucRoleTestForWitnessingRevokingVoice() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void voiceRevoked(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.revokeVoice(nicknameTwo);
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who undergoes an affiliation change receives that change as a presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.6:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the granting of admin status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForReceivingAdmin() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void adminGranted() {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
// This implicitly tests "The service MUST add the user to the admin list and then inform the owner of success" in §10.6, since it'll throw on either an error IQ or on no response.
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes an affiliation change receives that change as a
* presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that
* to all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.6:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the granting of admin status...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForWitnessingAdmin() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void adminGranted(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who undergoes an affiliation change receives that change as a presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to
* all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.7:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the loss of admin status by sending a presence element...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForRemovingAdmin() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() {
@Override
public void adminRevoked() {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user undergoes an affiliation change receives that change as a presence update
*
* <p>From XEP-0045 § 5.2.2:</p>
* <blockquote>
* ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to
* all occupants...
* </blockquote>
*
* <p>From XEP-0045 § 10.7:</p>
* <blockquote>
* If the user is in the room, the service MUST then send updated presence from this individual to all occupants,
* indicating the loss of admin status by sending a presence element...
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucAffiliationTestForWitnessingAdminRemoval() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
final ResultSyncPoint<String, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() {
@Override
public void adminRevoked(EntityFullJid participant) {
resultSyncPoint.signal("done");
}
});
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
try {
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who gets kicked receives that change as a presence update
*
* <p>From XEP-0045 § 8.2:</p>
* <blockquote>
* The kick is performed based on the occupant's room nickname and is completed by setting the role of a
* participant or visitor to a value of "none".
*
* The service MUST remove the kicked occupant by sending a presence stanza of type "unavailable" to each kicked
* occupant, including status code 307 in the extended presence information, optionally along with the reason (if
* provided) and the roomnick or bare JID of the user who initiated the kick.
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucPresenceTestForGettingKicked() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
final ResultSyncPoint<Presence, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByTwo.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
try {
Presence kickPresence = resultSyncPoint.waitForResult(timeout);
MUCUser mucUser = MUCUser.from(kickPresence);
assertNotNull(mucUser);
assertAll(
() -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Missing self-presence status code in kick presence"),
() -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"),
() -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence")
);
Jid itemJid = mucUser.getItem().getJid();
if (itemJid != null) {
assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence");
}
} finally {
tryDestroy(mucAsSeenByOne);
}
}
/**
* Asserts that a user who is present when another user gets kicked receives that change as a presence update
*
* <p>From XEP-0045 § 8.2:</p>
* <blockquote>
* ...the service MUST then inform all of the remaining occupants that the kicked occupant is no longer in the room
* by sending presence stanzas of type "unavailable" from the individual's roomnick (&lt;room@service/nick&gt;) to all
* the remaining occupants (just as it does when occupants exit the room of their own volition), including the
* status code and optionally the reason and actor.
* </blockquote>
*
* @throws Exception when errors occur
*/
@SmackIntegrationTest
public void mucPresenceTestForWitnessingKick() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest");
MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
createMuc(mucAsSeenByOne, "one-" + randomString);
final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
mucAsSeenByTwo.join(nicknameTwo);
mucAsSeenByThree.join(nicknameThree);
final ResultSyncPoint<Presence, Exception> resultSyncPoint = new ResultSyncPoint<>();
mucAsSeenByThree.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
try {
Presence kickPresence = resultSyncPoint.waitForResult(timeout);
MUCUser mucUser = MUCUser.from(kickPresence);
assertNotNull(mucUser);
assertAll(
() -> assertFalse(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Incorrect self-presence status code in kick presence"),
() -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"),
() -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence")
);
Jid itemJid = mucUser.getItem().getJid();
if (itemJid != null) {
assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence");
}
} finally {
tryDestroy(mucAsSeenByOne);
}
}
}

View File

@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
@ -63,7 +64,7 @@ public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemo
}
@AfterClass
public void cleanUp() throws IOException {
public void cleanUp() throws IOException, NotConnectedException, InterruptedException {
alice.stopStanzaAndPEPListeners();
bob.stopStanzaAndPEPListeners();
OmemoManagerSetupHelper.cleanUpPubSub(alice);

View File

@ -22,8 +22,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.roster.Roster;
@ -31,12 +33,9 @@ import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import com.google.common.collect.Maps;
@ -124,48 +123,10 @@ public class OmemoManagerSetupHelper {
}
}
public static void cleanUpPubSub(OmemoManager omemoManager) throws IOException {
PubSubManager pm = PubSubManager.getInstanceFor(omemoManager.getConnection(), omemoManager.getOwnJid());
try {
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) {
// ignore
}
OmemoCachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend()
.loadCachedDeviceList(omemoManager.getOwnDevice(), omemoManager.getOwnJid());
for (int id : deviceList.getAllDevices()) {
try {
pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException |
PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException |
PubSubException.NotAPubSubNodeException e) {
// Silent
}
try {
pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException
| XMPPException.XMPPErrorException e) {
// Silent
}
}
try {
pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException |
PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException |
PubSubException.NotAPubSubNodeException e) {
// Silent
}
try {
pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException |
XMPPException.XMPPErrorException e) {
// Silent
}
public static void cleanUpPubSub(OmemoManager omemoManager)
throws IOException, NotConnectedException, InterruptedException {
List<Exception> exceptions = omemoManager.purgeEverything();
assertTrue(exceptions.isEmpty(), "There where exceptions while purging OMEMO: " + exceptions);
}
public static void cleanUpRoster(OmemoManager omemoManager) {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus
* Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.jivesoftware.smack.full;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.io.PrintWriter;
@ -25,6 +26,8 @@ import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
@ -34,6 +37,7 @@ import org.jgrapht.graph.DirectedPseudograph;
import org.jgrapht.io.DOTImporter;
import org.jgrapht.io.ImportException;
import org.junit.jupiter.api.Test;
import org.jxmpp.stringprep.XmppStringprepException;
public class ModularXmppClientToServerConnectionStateGraphTest {
@ -80,4 +84,24 @@ public class ModularXmppClientToServerConnectionStateGraphTest {
assertEquals(expectedStateGraph, currentStateGraph);
}
@Test
public void testNoUnknownStates() throws XmppStringprepException {
ModularXmppClientToServerConnectionConfiguration.builder()
.setUsernameAndPassword("user", "password")
.setXmppDomain("example.org")
.failOnUnknownStates() // This is the actual option that enableds this test.
.build();
}
@Test
public void throwsOnUnknownStates() throws XmppStringprepException {
assertThrows(IllegalStateException.class, () ->
ModularXmppClientToServerConnectionConfiguration.builder()
.setUsernameAndPassword("user", "password")
.setXmppDomain("example.org")
.removeModule(CompressionModuleDescriptor.class)
.failOnUnknownStates() // This is the actual option that enableds this test.
.build()
);
}
}

View File

@ -340,7 +340,6 @@ public class RTPBridge extends IQ {
boolean done = false;
XmlPullParser.Event eventType;
String elementName;
if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
// TODO: Should be SmackParseException.
@ -356,9 +355,9 @@ public class RTPBridge extends IQ {
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String elementName = parser.getName();
if (elementName.equals("candidate")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))

View File

@ -66,14 +66,13 @@ public abstract class JingleContentDescriptionProvider extends ExtensionElementP
public JingleContentDescription parse(XmlPullParser parser,
int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException,
IOException {
boolean done = false;
JingleContentDescription desc = getInstance();
while (!done) {
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String name = parser.getName();
if (name.equals(JingleContentDescription.JinglePayloadType.NODENAME)) {
desc.addJinglePayloadType(parsePayload(parser));
} else {
@ -81,8 +80,8 @@ public abstract class JingleContentDescriptionProvider extends ExtensionElementP
throw new IOException("Unknow element \"" + name + "\" in content.");
}
} else if (eventType == XmlPullParser.Event.END_ELEMENT) {
if (name.equals(JingleContentDescription.NODENAME)) {
done = true;
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}

View File

@ -65,14 +65,12 @@ public abstract class JingleDescriptionProvider extends ExtensionElementProvider
*/
@Override
public JingleDescription parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
boolean done = false;
JingleDescription desc = getInstance();
while (!done) {
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String name = parser.getName();
if (name.equals(PayloadType.NODENAME)) {
desc.addPayloadType(parsePayload(parser));
} else {
@ -80,8 +78,8 @@ public abstract class JingleDescriptionProvider extends ExtensionElementProvider
throw new IOException("Unknow element \"" + name + "\" in content.");
}
} else if (eventType == XmlPullParser.Event.END_ELEMENT) {
if (name.equals(JingleDescription.NODENAME)) {
done = true;
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}

View File

@ -84,10 +84,10 @@ public class JingleProvider extends IQProvider<Jingle> {
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
namespace = parser.getNamespace();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
elementName = parser.getName();
namespace = parser.getNamespace();
// Parse some well know subelements, depending on the namespaces
// and element names...

View File

@ -54,14 +54,13 @@ public abstract class JingleTransportProvider extends ExtensionElementProvider<J
*/
@Override
public JingleTransport parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
boolean done = false;
JingleTransport trans = getInstance();
while (!done) {
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String name = parser.getName();
if (name.equals(JingleTransportCandidate.NODENAME)) {
JingleTransportCandidate jtc = parseCandidate(parser);
if (jtc != null) trans.addCandidate(jtc);
@ -72,8 +71,8 @@ public abstract class JingleTransportProvider extends ExtensionElementProvider<J
}
}
else if (eventType == XmlPullParser.Event.END_ELEMENT) {
if (name.equals(JingleTransport.NODENAME)) {
done = true;
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}

View File

@ -71,14 +71,13 @@ public class OfferConfirmation extends SimpleIQ {
boolean done = false;
while (!done) {
parser.next();
String elementName = parser.getName();
if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "user-jid".equals(elementName)) {
if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "user-jid".equals(parser.getName())) {
confirmation.setUserJID(parser.nextText());
}
else if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "session-id".equals(elementName)) {
else if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "session-id".equals(parser.getName())) {
confirmation.setSessionID(Long.valueOf(parser.nextText()));
}
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && "offer-confirmation".equals(elementName)) {
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && "offer-confirmation".equals(parser.getName())) {
done = true;
}
}

View File

@ -108,14 +108,13 @@ public class QueueUpdate implements ExtensionElement {
int timeRemaining = -1;
while (!done) {
parser.next();
String elementName = parser.getName();
if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "position".equals(elementName)) {
if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "position".equals(parser.getName())) {
position = Integer.parseInt(parser.nextText());
}
else if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "time".equals(elementName)) {
else if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT && "time".equals(parser.getName())) {
timeRemaining = Integer.parseInt(parser.nextText());
}
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && "queue-status".equals(elementName)) {
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && "queue-status".equals(parser.getName())) {
done = true;
}
}

View File

@ -179,11 +179,10 @@ public class RoomInvitation implements ExtensionElement {
final RoomInvitation invitation = new RoomInvitation();
invitation.type = Type.valueOf(parser.getAttributeValue("", "type"));
boolean done = false;
while (!done) {
outerloop: while (true) {
parser.next();
String elementName = parser.getName();
if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT) {
String elementName = parser.getName();
if ("session".equals(elementName)) {
invitation.sessionID = parser.getAttributeValue("", "id");
}
@ -203,8 +202,8 @@ public class RoomInvitation implements ExtensionElement {
invitation.room = JidCreate.entityBareFrom(roomString);
}
}
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && ELEMENT_NAME.equals(elementName)) {
done = true;
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == initialDepth) {
break outerloop;
}
}
return invitation;

View File

@ -174,11 +174,10 @@ public class RoomTransfer implements ExtensionElement {
final RoomTransfer invitation = new RoomTransfer();
invitation.type = RoomTransfer.Type.valueOf(parser.getAttributeValue("", "type"));
boolean done = false;
while (!done) {
outerloop: while (true) {
parser.next();
String elementName = parser.getName();
if (parser.getEventType() == XmlPullParser.Event.START_ELEMENT) {
String elementName = parser.getName();
if ("session".equals(elementName)) {
invitation.sessionID = parser.getAttributeValue("", "id");
}
@ -195,8 +194,8 @@ public class RoomTransfer implements ExtensionElement {
invitation.room = parser.nextText();
}
}
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && ELEMENT_NAME.equals(elementName)) {
done = true;
else if (parser.getEventType() == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == initialDepth) {
break outerloop;
}
}
return invitation;

View File

@ -36,6 +36,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
@ -73,6 +74,7 @@ import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pep.PepEventListener;
import org.jivesoftware.smackx.pep.PepManager;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.jxmpp.jid.BareJid;
@ -727,6 +729,50 @@ public final class OmemoManager extends Manager {
getOmemoService().purgeDeviceList(new LoggedInOmemoManager(this));
}
public List<Exception> purgeEverything() throws NotConnectedException, InterruptedException, IOException {
List<Exception> exceptions = new ArrayList<>(5);
PubSubManager pm = PubSubManager.getInstanceFor(getConnection(), getOwnJid());
try {
requestDeviceListUpdateFor(getOwnJid());
} catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException
| XMPPException.XMPPErrorException e) {
exceptions.add(e);
}
OmemoCachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend()
.loadCachedDeviceList(getOwnDevice(), getOwnJid());
for (int id : deviceList.getAllDevices()) {
try {
pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
} catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException
| XMPPException.XMPPErrorException | PubSubException.NotAPubSubNodeException e) {
exceptions.add(e);
}
try {
pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException e) {
exceptions.add(e);
}
}
try {
pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
} catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException
| XMPPException.XMPPErrorException | PubSubException.NotAPubSubNodeException e) {
exceptions.add(e);
}
try {
pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException e) {
exceptions.add(e);
}
return exceptions;
}
/**
* Rotate the signedPreKey published in our OmemoBundle and republish it. This should be done every now and
* then (7-14 days). The old signedPreKey should be kept for some more time (a month or so) to enable decryption

View File

@ -16,7 +16,6 @@
*/
package org.jivesoftware.smackx.omemo.provider;
import static org.jivesoftware.smackx.omemo.element.OmemoBundleElement.BUNDLE;
import static org.jivesoftware.smackx.omemo.element.OmemoBundleElement.IDENTITY_KEY;
import static org.jivesoftware.smackx.omemo.element.OmemoBundleElement.PRE_KEYS;
import static org.jivesoftware.smackx.omemo.element.OmemoBundleElement.PRE_KEY_ID;
@ -43,7 +42,6 @@ import org.jivesoftware.smackx.omemo.element.OmemoBundleElement_VAxolotl;
public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider<OmemoBundleElement_VAxolotl> {
@Override
public OmemoBundleElement_VAxolotl parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
boolean stop = false;
boolean inPreKeys = false;
int signedPreKeyId = -1;
@ -52,11 +50,11 @@ public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider<OmemoB
String identityKey = null;
HashMap<Integer, String> preKeys = new HashMap<>();
while (!stop) {
outerloop: while (true) {
XmlPullParser.Event tag = parser.next();
String name = parser.getName();
switch (tag) {
case START_ELEMENT:
String name = parser.getName();
final int attributeCount = parser.getAttributeCount();
// <signedPreKeyPublic>
if (name.equals(SIGNED_PRE_KEY_PUB)) {
@ -91,8 +89,8 @@ public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider<OmemoB
}
break;
case END_ELEMENT:
if (name.equals(BUNDLE)) {
stop = true;
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
default:

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub
* Copyright 2017 Paul Schaub, 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.jivesoftware.smackx.omemo.provider;
import static org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement.DEVICE;
import static org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement.ID;
import static org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement.LIST;
import java.io.IOException;
import java.util.HashSet;
@ -41,12 +40,11 @@ public class OmemoDeviceListVAxolotlProvider extends ExtensionElementProvider<Om
@Override
public OmemoDeviceListElement_VAxolotl parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
Set<Integer> deviceListIds = new HashSet<>();
boolean stop = false;
while (!stop) {
outerloop: while (true) {
XmlPullParser.Event tag = parser.next();
String name = parser.getName();
switch (tag) {
case START_ELEMENT:
String name = parser.getName();
if (name.equals(DEVICE)) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals(ID)) {
@ -57,8 +55,8 @@ public class OmemoDeviceListVAxolotlProvider extends ExtensionElementProvider<Om
}
break;
case END_ELEMENT:
if (name.equals(LIST)) {
stop = true;
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
default:

View File

@ -17,7 +17,6 @@
package org.jivesoftware.smackx.omemo.provider;
import static org.jivesoftware.smackx.omemo.element.OmemoElement.ATTR_PAYLOAD;
import static org.jivesoftware.smackx.omemo.element.OmemoElement.NAME_ENCRYPTED;
import java.io.IOException;
import java.util.ArrayList;
@ -42,17 +41,16 @@ public class OmemoVAxolotlProvider extends ExtensionElementProvider<OmemoElement
@Override
public OmemoElement_VAxolotl parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
boolean inEncrypted = true;
int sid = -1;
ArrayList<OmemoKeyElement> keys = new ArrayList<>();
byte[] iv = null;
byte[] payload = null;
while (inEncrypted) {
outerloop: while (true) {
XmlPullParser.Event tag = parser.next();
String name = parser.getName();
switch (tag) {
case START_ELEMENT:
String name = parser.getName();
switch (name) {
case OmemoHeaderElement.ELEMENT:
for (int i = 0; i < parser.getAttributeCount(); i++) {
@ -82,8 +80,8 @@ public class OmemoVAxolotlProvider extends ExtensionElementProvider<OmemoElement
}
break;
case END_ELEMENT:
if (name.equals(NAME_ENCRYPTED)) {
inEncrypted = false;
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
default:

View File

@ -42,8 +42,8 @@ public class PubkeyElementProvider extends ExtensionElementProvider<PubkeyElemen
Date date = ParserUtils.getDateFromOptionalXep82String(dateString);
while (true) {
XmlPullParser.Event tag = parser.next();
String name = parser.getName();
if (tag == XmlPullParser.Event.START_ELEMENT) {
String name = parser.getName();
switch (name) {
case PubkeyElement.PubkeyDataElement.ELEMENT:
String base64EncodedOpenPgpPubKey = parser.nextText();

View File

@ -50,6 +50,7 @@ import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
import org.jivesoftware.smackx.ox.store.filebased.FileBasedOpenPgpStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.jupiter.api.Test;
@ -156,9 +157,13 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite {
// Check, if one of Bobs keys was used for decryption
assertNotNull(bobSelf.getSigningKeyRing().getPublicKey(metadata.getDecryptionFingerprint().getKeyId()));
// TODO: I observed this assertTrue() to fail sporadically. As a first attempt to diagnose this, a message was
// added to the assertion. However since most (all?) objects used in the message do not implement a proper
// toString() this is probably not really helpful as it is.
PGPPublicKeyRingCollection pubKeys = aliceForBob.getTrustedAnnouncedKeys();
// Check if one of Alice' keys was used for signing
assertTrue(metadata.containsVerifiedSignatureFrom(
aliceForBob.getTrustedAnnouncedKeys().iterator().next()));
pubKeys.iterator().next()), metadata + " did not contain one of alice' keys " + pubKeys);
}
@AfterClass

View File

@ -9,7 +9,7 @@ apply plugin: 'scala'
apply plugin: 'com.github.alisiikh.scalastyle_2.12'
ext {
scalaVersion = '2.12.1'
scalaVersion = '2.13.6'
}
dependencies {
@ -20,7 +20,7 @@ dependencies {
api project(':smack-omemo-signal')
implementation "org.scala-lang:scala-library:$scalaVersion"
implementation "com.lihaoyi:ammonite_$scalaVersion:1.3.2"
implementation "com.lihaoyi:ammonite_$scalaVersion:2.4.0"
}
scalaStyle {