Add support for XEP-0359: Stable and Unique Stanza IDs

Fixes SMACK-798
This commit is contained in:
Paul Schaub 2018-02-21 20:28:26 +01:00 committed by Florian Schmaus
parent ec4be1963a
commit b3b76b9ff4
13 changed files with 551 additions and 0 deletions

View File

@ -92,6 +92,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. |
| Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. |
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
| Stable and Unique Stanza IDs | [XEP-0359](http://xmpp.org/extensions/xep-0359.html) | This specification describes unique and stable IDs for messages. |
| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |

View File

@ -0,0 +1,118 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid;
import java.util.Map;
import java.util.WeakHashMap;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.ToTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
public final class StableUniqueStanzaIdManager extends Manager {
public static final String NAMESPACE = "urn:xmpp:sid:0";
private static final Map<XMPPConnection, StableUniqueStanzaIdManager> INSTANCES = new WeakHashMap<>();
// Filter for outgoing stanzas.
private static final StanzaFilter OUTGOING_FILTER = new AndFilter(
MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE,
ToTypeFilter.ENTITY_FULL_OR_BARE_JID);
private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE);
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
private final StanzaListener stanzaListener = new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
OriginIdElement.addOriginId((Message) stanza);
}
};
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
/**
* Private constructor.
* @param connection
*/
private StableUniqueStanzaIdManager(XMPPConnection connection) {
super(connection);
enable();
}
/**
* Return an instance of the StableUniqueStanzaIdManager for the given connection.
*
* @param connection xmpp-connection
* @return manager instance for the connection
*/
public static StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
StableUniqueStanzaIdManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new StableUniqueStanzaIdManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
/**
* Start appending origin-id elements to outgoing stanzas and add the feature to disco.
*/
public synchronized void enable() {
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(OUTGOING_FILTER));
connection().addPacketInterceptor(stanzaListener, filter);
}
/**
* Stop appending origin-id elements to outgoing stanzas and remove the feature from disco.
*/
public synchronized void disable() {
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
connection().removePacketInterceptor(stanzaListener);
}
/**
* Return true, if we automatically append origin-id elements to outgoing stanzas.
*
* @return true if functionality is enabled, otherwise false.
*/
public synchronized boolean isEnabled() {
ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection());
return disco.includesFeature(NAMESPACE);
}
}

View File

@ -0,0 +1,84 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid.element;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
public class OriginIdElement extends StableAndUniqueIdElement {
public static final String ELEMENT = "origin-id";
public OriginIdElement() {
super();
}
public OriginIdElement(String id) {
super(id);
}
/**
* Add an origin-id element to a message and set the stanzas id to the same id as in the origin-id element.
*
* @param message message.
*/
public static OriginIdElement addOriginId(Message message) {
OriginIdElement originId = new OriginIdElement();
message.addExtension(originId);
// TODO: Find solution to have both the originIds stanzaId and a nice to look at incremental stanzaID.
// message.setStanzaId(originId.getId());
return originId;
}
/**
* Return true, if the message contains a origin-id element.
*
* @param message message
* @return true if the message contains a origin-id, false otherwise.
*/
public static boolean hasOriginId(Message message) {
return getOriginId(message) != null;
}
/**
* Return the origin-id element of a message or null, if absent.
*
* @param message message
* @return origin-id element
*/
public static OriginIdElement getOriginId(Message message) {
return message.getExtension(OriginIdElement.ELEMENT, StableUniqueStanzaIdManager.NAMESPACE);
}
@Override
public String getNamespace() {
return StableUniqueStanzaIdManager.NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public CharSequence toXML() {
return new XmlStringBuilder(this)
.attribute(ATTR_ID, getId())
.closeEmptyElement();
}
}

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid.element;
import java.util.UUID;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.StringUtils;
public abstract class StableAndUniqueIdElement implements ExtensionElement {
public static final String ATTR_ID = "id";
private final String id;
public StableAndUniqueIdElement() {
this.id = UUID.randomUUID().toString();
}
public String getId() {
return id;
}
public StableAndUniqueIdElement(String id) {
if (StringUtils.isNullOrEmpty(id)) {
throw new IllegalArgumentException("Argument 'id' cannot be null or empty.");
}
this.id = id;
}
}

View File

@ -0,0 +1,81 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid.element;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
public class StanzaIdElement extends StableAndUniqueIdElement {
public static final String ELEMENT = "stanza-id";
public static final String ATTR_BY = "by";
private final String by;
public StanzaIdElement(String by) {
super();
this.by = by;
}
public StanzaIdElement(String id, String by) {
super(id);
this.by = by;
}
/**
* Return true, if a message contains a stanza-id element.
*
* @param message message
* @return true if message contains stanza-id element, otherwise false.
*/
public static boolean hasStanzaId(Message message) {
return getStanzaId(message) != null;
}
/**
* Return the stanza-id element of a message.
*
* @param message message
* @return stanza-id element of a jid, or null if absent.
*/
public static StanzaIdElement getStanzaId(Message message) {
return message.getExtension(StanzaIdElement.ELEMENT, StableUniqueStanzaIdManager.NAMESPACE);
}
public String getBy() {
return by;
}
@Override
public String getNamespace() {
return StableUniqueStanzaIdManager.NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
return new XmlStringBuilder(this)
.attribute(ATTR_ID, getId())
.attribute(ATTR_BY, getBy())
.closeEmptyElement();
}
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's API for XEP-0359: Stable and Unique Stanza IDs.
*/
package org.jivesoftware.smackx.sid.element;

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's API for XEP-0359: Stable and Unique Stanza IDs.
*/
package org.jivesoftware.smackx.sid;

View File

@ -0,0 +1,32 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.xmlpull.v1.XmlPullParser;
public class OriginIdProvider extends ExtensionElementProvider<OriginIdElement> {
public static final OriginIdProvider TEST_INSTANCE = new OriginIdProvider();
@Override
public OriginIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
return new OriginIdElement(parser.getAttributeValue(null, OriginIdElement.ATTR_ID));
}
}

View File

@ -0,0 +1,34 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
import org.xmlpull.v1.XmlPullParser;
public class StanzaIdProvider extends ExtensionElementProvider<StanzaIdElement> {
public static StanzaIdProvider TEST_INSTANCE = new StanzaIdProvider();
@Override
public StanzaIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
String id = parser.getAttributeValue(null, StanzaIdElement.ATTR_ID);
String by = parser.getAttributeValue(null, StanzaIdElement.ATTR_BY);
return new StanzaIdElement(id, by);
}
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's API for XEP-0359: Stable and Unique Stanza IDs.
*/
package org.jivesoftware.smackx.sid.provider;

View File

@ -211,6 +211,18 @@
<className>org.jivesoftware.smackx.push_notifications.provider.RemoteDisablingProvider</className>
</extensionProvider>
<!-- XEP-0359: Stable and Unique Stanza IDs -->
<extensionProvider>
<elementName>stanza-id</elementName>
<namespace>urn:xmpp:sid:0</namespace>
<className>org.jivesoftware.smackx.sid.provider.StanzaIdProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>origin-id</elementName>
<namespace>urn:xmpp:sid:0</namespace>
<className>org.jivesoftware.smackx.sid.provider.OriginIdProvider</className>
</extensionProvider>
<!-- XEP-0333: Chat Markers -->
<extensionProvider>
<elementName>markable</elementName>

View File

@ -6,5 +6,6 @@
<className>org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager</className>
<className>org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager</className>
<className>org.jivesoftware.smackx.eme.ExplicitMessageEncryptionManager</className>
<className>org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager</className>
</startupClasses>
</smack>

View File

@ -0,0 +1,84 @@
/**
*
* Copyright 2018 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.sid;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
import org.jivesoftware.smackx.sid.provider.OriginIdProvider;
import org.jivesoftware.smackx.sid.provider.StanzaIdProvider;
import org.junit.Test;
public class StableUniqueStanzaIdTest extends SmackTestSuite {
@Test
public void stanzaIdProviderTest() throws Exception {
String xml = "<stanza-id xmlns='urn:xmpp:sid:0' id='de305d54-75b4-431b-adb2-eb6b9e546013' by='alice@wonderland.lit' />";
StanzaIdElement element = new StanzaIdElement("de305d54-75b4-431b-adb2-eb6b9e546013", "alice@wonderland.lit");
assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId());
assertEquals("alice@wonderland.lit", element.getBy());
assertXMLEqual(xml, element.toXML().toString());
StanzaIdElement parsed = StanzaIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml));
assertEquals(element.getId(), parsed.getId());
assertEquals(element.getBy(), parsed.getBy());
}
@Test
public void originIdProviderTest() throws Exception {
String xml = "<origin-id xmlns='urn:xmpp:sid:0' id='de305d54-75b4-431b-adb2-eb6b9e546013' />";
OriginIdElement element = new OriginIdElement("de305d54-75b4-431b-adb2-eb6b9e546013");
assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId());
assertXMLEqual(xml, element.toXML().toString());
OriginIdElement parsed = OriginIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml));
assertEquals(element.getId(), parsed.getId());
}
@Test
public void createOriginIdTest() {
OriginIdElement element = new OriginIdElement();
assertNotNull(element);
assertEquals(StableUniqueStanzaIdManager.NAMESPACE, element.getNamespace());
assertEquals(36, element.getId().length());
}
@Test
public void fromMessageTest() {
Message message = new Message();
assertFalse(OriginIdElement.hasOriginId(message));
assertFalse(StanzaIdElement.hasStanzaId(message));
OriginIdElement.addOriginId(message);
assertTrue(OriginIdElement.hasOriginId(message));
StanzaIdElement stanzaId = new StanzaIdElement("alice@wonderland.lit");
message.addExtension(stanzaId);
assertTrue(StanzaIdElement.hasStanzaId(message));
assertEquals(stanzaId, StanzaIdElement.getStanzaId(message));
}
}