Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import org.jivesoftware.smack.packet.ErrorIQ;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Limits;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
Expand Down Expand Up @@ -1902,6 +1903,9 @@ protected final void parseFeatures(XmlPullParser parser) throws XmlPullParserExc
case Compress.Feature.ELEMENT:
streamFeature = PacketParserUtils.parseCompressionFeature(parser);
break;
case Limits.ELEMENT:
streamFeature = PacketParserUtils.parseLimitsFeature(parser);
break;
default:
ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getStreamFeatureProvider(name, namespace);
if (provider != null) {
Expand Down
90 changes: 90 additions & 0 deletions smack-core/src/main/java/org/jivesoftware/smack/packet/Limits.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
*
* Copyright © 2014-2026 Florian Schmaus

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I have the copyright on this code, have I?

*
* 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.smack.packet;

import javax.xml.namespace.QName;

import org.jivesoftware.smack.util.XmlStringBuilder;

/**
* Limits feature.
* For any XMPP stream, there is an "initiating entity" (a client or server) and a "responding entity" that they are connecting to.
* The responding entity advertises its limits in the <code>&lt;stream:features/&gt;</code> element that it sends at the start of the stream.
* <a href="https://xmpp.org/extensions/xep-0478.html">XEP-0478: Stream Limits Advertisement</a>
*/
public class Limits implements ExtensionElement {
public static final String ELEMENT = "limits";
public static final String NAMESPACE = "urn:xmpp:stream-limits:0";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

/**
* The maximum size of any first-level stream elements (including stanzas),
* in bytes the announcing entity is willing to accept.
* Guidance on acceptable limits is provided in RFC 6120 section 13.12.
* If the responding entity is unable to determine its limits, this child can be absent.
* Element: <code>&lt;max-bytes/&gt;</code>. Type UnsignedInt.
*/
public final int maxBytes;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use UInt32 for xs:unsignedInt.

Suggested change
public final int maxBytes;
public final UInt32 maxBytes;


/**
* The number of seconds without any traffic from the initiating entity after which
* the server may consider the stream idle, and either perform liveness checks
* (using e.g. Stream Management (XEP-0198) or XMPP Ping (XEP-0199)) or terminate the stream.
* Guidance on handling idle connections is provided in RFC 6120 section 4.6.
* If the responding entity is unable to determine its limits, this child can be absent.
* Element: <code>&lt;idle-seconds/&gt;</code>. Type UnsignedInt.
*/
public final int idleSeconds;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public final int idleSeconds;
public final UInt32 idleSeconds;


public Limits(int maxBytes, int idleSeconds) {
this.maxBytes = maxBytes;
this.idleSeconds = idleSeconds;
}

@Override
public String getElementName() {
return ELEMENT;
}

@Override
public String getNamespace() {
return NAMESPACE;
}

public int getMaxBytes() {
return maxBytes;
}

public int getIdleSeconds() {
return idleSeconds;
}

@Override
public XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.rightAngleBracket();
if (maxBytes != 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use

xml.optElement()

now, since we denote the absence of the element with null.

xml.element("max-bytes", String.valueOf(maxBytes));
}
if (idleSeconds != 0) {
xml.element("idle-seconds", String.valueOf(idleSeconds));
}
xml.closeElement(this);
return xml;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.Limits;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Presence;
Expand Down Expand Up @@ -894,6 +895,57 @@ public static Session.Feature parseSessionFeature(XmlPullParser parser) throws X
return new Session.Feature(optional);
}


/**
* Parse the stream limits from XEP-0478: Stream Limits Advertisement.
*
* @param parser the XML parser, positioned at the start of the "limits" stanza.
* @return a collection of Stings with the mechanisms included in the mechanisms stanza.
* @throws IOException if an I/O error occurred.
* @throws XmlPullParserException if an error in the XML parser occurred.
*/
public static Limits parseLimitsFeature(XmlPullParser parser)
throws XmlPullParserException, IOException {
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
int maxBytes = 0;
int idleSeconds = 0;
outerloop: while (true) {
XmlPullParser.Event event = parser.next();
switch (event) {
case START_ELEMENT:
String name = parser.getName();
switch (name) {
case "max-bytes":
try {
maxBytes = Integer.parseUnsignedInt(parser.nextText());
} catch (NumberFormatException ignored) {
// ignore
}
break;
case "idle-seconds":
try {
idleSeconds = Integer.parseUnsignedInt(parser.nextText());
} catch (NumberFormatException ignored) {
// ignore
}
break;
}
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
default:
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
break;
}
}

return new Limits(maxBytes, idleSeconds);
}

public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, XmlEnvironment outerXmlEnvironment, JxmppContext jxmppContext)
throws XmlPullParserException, IOException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
*
* Copyright 2018-2026 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.smack;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static java.util.Arrays.asList;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.Limits;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;

import org.junit.Test;

public class AbstractXMPPConnectionTest extends SmackTestSuite {
private final DummyConnection connection = new DummyConnection();

@Test
public void parseFeatures() throws XmlPullParserException, IOException, SmackParsingException {
String featureStr = "<features xmlns='http://etherx.jabber.org/streams'>" +
"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
" <mechanism>OAUTHBEARER</mechanism>" +
" <mechanism>SCRAM-SHA-1</mechanism>" +
" <mechanism>PLAIN</mechanism>" +
" <mechanism>SCRAM-SHA-1-PLUS</mechanism>" +
"</mechanisms>" +
"<sasl-channel-binding xmlns='urn:xmpp:sasl-cb:0'>" +
" <channel-binding type='tls-exporter' />" +
"</sasl-channel-binding>" +
"<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" +
" <required />" +
"</bind>" +
"<session xmlns='urn:ietf:params:xml:ns:xmpp-session'>" +
" <optional />" +
"</session>" +
"<limits xmlns='urn:xmpp:stream-limits:0'>" +
" <max-bytes>262144</max-bytes>" +
" <idle-seconds>840</idle-seconds>" +
"</limits>" +
"<register xmlns='urn:xmpp:invite' />" +
"<register xmlns='urn:xmpp:ibr-token:0' />" +
"<register xmlns='http://jabber.org/features/iq-register' />" +
"<ver xmlns='urn:xmpp:features:rosterver' />" +
"<sub xmlns='urn:xmpp:features:pre-approval' />" +
"<csi xmlns='urn:xmpp:csi:0' />" +
"<c hash='sha-1' node='http://prosody.im'" +
" ver='uWfOTni5YjTtMRfYAjNKsj2GUXM=' xmlns='http://jabber.org/protocol/caps' />" +
"</features>";
InputStream input = new ByteArrayInputStream(featureStr.getBytes(StandardCharsets.UTF_8));
XmlPullParser parser = PacketParserUtils.getParserFor(input);
connection.parseFeatures(parser);

assertTrue(connection.hasFeature("mechanisms", "urn:ietf:params:xml:ns:xmpp-sasl"));
Mechanisms featMechanisms = connection.getFeature(Mechanisms.class);
assertEquals(asList("OAUTHBEARER", "SCRAM-SHA-1", "PLAIN", "SCRAM-SHA-1-PLUS"), featMechanisms.getMechanisms());

assertTrue(connection.hasFeature("bind", "urn:ietf:params:xml:ns:xmpp-bind"));
Bind.Feature featBind = connection.getFeature(Bind.Feature.class);
assertEquals(Bind.Feature.INSTANCE, featBind);

assertTrue(connection.hasFeature("session", "urn:ietf:params:xml:ns:xmpp-session"));
Session.Feature featSession = connection.getFeature(Session.Feature.class);
assertTrue(featSession.isOptional());

assertTrue(connection.hasFeature("limits", "urn:xmpp:stream-limits:0"));
Limits featLimits = connection.getFeature(Limits.class);
assertEquals(262144, featLimits.getMaxBytes());
assertEquals(840, featLimits.getIdleSeconds());
}
}