diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 687fabc0c..c00592041 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -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). | diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java new file mode 100644 index 000000000..966861634 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java @@ -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 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); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java new file mode 100644 index 000000000..e17966cac --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java @@ -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(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java new file mode 100644 index 000000000..7c87f4592 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java @@ -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; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java new file mode 100644 index 000000000..6c1e24bba --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java @@ -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(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java new file mode 100644 index 000000000..7fed273a0 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java @@ -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; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java new file mode 100644 index 000000000..0f2279331 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java @@ -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; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java new file mode 100644 index 000000000..19ef6edd7 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java @@ -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 { + + 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)); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java new file mode 100644 index 000000000..2a3fcad28 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java @@ -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 { + + 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); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java new file mode 100644 index 000000000..1ed2658f9 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java @@ -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; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index b5c89b3fb..1ef9366a8 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -211,6 +211,18 @@ org.jivesoftware.smackx.push_notifications.provider.RemoteDisablingProvider + + + stanza-id + urn:xmpp:sid:0 + org.jivesoftware.smackx.sid.provider.StanzaIdProvider + + + origin-id + urn:xmpp:sid:0 + org.jivesoftware.smackx.sid.provider.OriginIdProvider + + markable diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml index ab592717a..8a73c804f 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml @@ -6,5 +6,6 @@ org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager org.jivesoftware.smackx.eme.ExplicitMessageEncryptionManager + org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java new file mode 100644 index 000000000..cf8ca2f33 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java @@ -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 = ""; + 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 = ""; + 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)); + } +}