1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-12-22 18:48:00 +01:00

Smack 4.4.0

-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAl/Msl5fFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFIq8gf9GZp0Cl9Gr+seA/C8nczE/cNRKfHnDR224klbjlGrnojb+bL2QgeKG6wN
 u/eL3+UMus8uw3+R8NKARawqt/r6mbk4bgdGEbzoByLO6iiPYTr7TuZmTOQyZa5L
 tMNkFIvfvMzDeDDqI4z4uTgT3s7YBg0d4BT7cL8T6RaZGHF57OxqhWnKxMuaiTXH
 HJzVTPrtAcYg17QZl5+mrhiWyynE5+4QGeluYHlh7vvsniwNOqrwO1vWHg2BX003
 DjaHK+6oLjKxVljCklXxE7Wsx0VcMrBfv2qZwW3Q4ZzZVcrAn8TC4HrUtR8DL176
 DpGlEhmmdr3xKKX0JQ9RmIod+xSabw==
 =4wGX
 -----END PGP SIGNATURE-----

Merge tag '4.4.0'

Smack 4.4.0
This commit is contained in:
Florian Schmaus 2020-12-06 12:16:44 +01:00
commit d8642847ea
18 changed files with 403 additions and 81 deletions

View file

@ -141,6 +141,112 @@ hr {
<div id="pageBody"> <div id="pageBody">
<h2>4.4.0 -- <span style="font-weight: normal;">2020-12-06</span></h2>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-561'>SMACK-561</a>] - Smack should not reply with multiple stream types after stream initiation is offered
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-624'>SMACK-624</a>] - AdHocCommandManager&#39;s session sweeping thread does never stop
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-729'>SMACK-729</a>] - Not all providers from smack-legacy.jar are loaded
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-770'>SMACK-770</a>] - There is no Bits of Binary Extension Element provider registered
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-848'>SMACK-848</a>] - Make MultiUserChat.leave() wait for response
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-874'>SMACK-874</a>] - PacketParserUtilsTest#invalidXMLInMessageBody() fails on non-english machines
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-881'>SMACK-881</a>] - Deadlock between reader and writer if Stream Mangement unacked stanza queue is full
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-888'>SMACK-888</a>] - MUC roomDestroyed() callback is not invoked
</li>
</ul>
<h2> New Feature
</h2>
<ul>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-257'>SMACK-257</a>] - Add support for XEP-0118: User Tune
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-636'>SMACK-636</a>] - Add support for XEP-0319: Last User Interaction in Presence
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-743'>SMACK-743</a>] - Add support for XEP-0384: OMEMO Encryption
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-801'>SMACK-801</a>] - Update Smack to Java 8
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-824'>SMACK-824</a>] - Add support for XEP-0221: Data Forms Media Element
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-862'>SMACK-862</a>] - Add support for XEP-0418: DNS Queries over XMPP (DoX)
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-871'>SMACK-871</a>] - Add support for XEP-0350: Data Forms Geolocation Element
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-872'>SMACK-872</a>] - Add support for XEP-0315: Data Forms XML Element
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-878'>SMACK-878</a>] - Add support for XEP-0328: JID Prep
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-884'>SMACK-884</a>] - Add support for XEP-0422: Message Fastening
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-885'>SMACK-885</a>] - Add support for XEP-0420 Stanza Content Encryption
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-889'>SMACK-889</a>] - Add support for XEP-0428: Fallback Indication
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-591'>SMACK-591</a>] - Replace XPP3 by SmackXmlPullParser (wrapping Stax&#39;s XmlStreamReader and XPP3 on Android)
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-650'>SMACK-650</a>] - Enable Java8&#39;s javadoc doclint
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-651'>SMACK-651</a>] - Perform sound cross-compilation: Use newer javac&#39;s --release feature
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-718'>SMACK-718</a>] - Prevent extremely long reply timeouts from being set
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-821'>SMACK-821</a>] - Make Forwarded a generic type
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-822'>SMACK-822</a>] - Add API for XEP-0313 § 6.2 Advanced configuration via Ad-Hoc commands
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-825'>SMACK-825</a>] - Discourage Stanza.getExtension(String, String) in favor of Stanza.getExtension(Class&lt;E extends ExtensionElement&gt;)
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-826'>SMACK-826</a>] - Add support for XEP-0373:&quot; OpenPGP for XMPP&quot; and XEP-0374: &quot;OpenPGP for XMPP Instant Messaging&quot;
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-828'>SMACK-828</a>] - Add support for XEP-0107: User Mood
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-836'>SMACK-836</a>] - Save a ServiceDiscoveryManager instance in a private field of MultiUserChatManger
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-839'>SMACK-839</a>] - Provider.parse() should not throw a generic Exception, but instead IOException and XmlPullParserException
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-852'>SMACK-852</a>] - Message thread and subject should be designed and implemented as ExtensionElements
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-854'>SMACK-854</a>] - Rename smack-java7 to smack-java8
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-866'>SMACK-866</a>] - Remove all tabs from the source code and add checkstyle rule that enforces no-tabs
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-867'>SMACK-867</a>] - Extend HttpFileUploadManager by methods with InputStream parameter
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-882'>SMACK-882</a>] - Add support for MUC status code 333
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-883'>SMACK-883</a>] - Add generic MUC callback for &quot;participant left&quot; caused by unavailable presences
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-890'>SMACK-890</a>] - Update Message Archive Management (XEP-0313) support to urn:xmpp:mam:2
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-892'>SMACK-892</a>] - Smack performs unnecessary escaping in XML text
</li>
</ul>
<h2> Task
</h2>
<ul>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-750'>SMACK-750</a>] - Raise Smack&#39;s minimum required Android SDK level to 19 (Android 4.4, Kit Kat, 2013-10)
</li>
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-840'>SMACK-840</a>] - Remove smack-compression-jzlib, as it is obsolete (Smack uses Java 7 de- and inflate API now)
</li>
</ul>
<h2>4.3.4 -- <span style="font-weight: normal;">2019-05-27</span></h2> <h2>4.3.4 -- <span style="font-weight: normal;">2019-05-27</span></h2>
<h2> Bug <h2> Bug

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019 Florian Schmaus * Copyright 2019-2020 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +16,9 @@
*/ */
package org.jivesoftware.smack.datatypes; package org.jivesoftware.smack.datatypes;
public abstract class Scalar extends java.lang.Number { import org.jivesoftware.smack.util.DefaultCharSequence;
public abstract class Scalar extends java.lang.Number implements DefaultCharSequence {
/** /**
* *
@ -83,4 +85,11 @@ public abstract class Scalar extends java.lang.Number {
public final String toString() { public final String toString() {
return number.toString(); return number.toString();
} }
public abstract Scalar getMinValue();
public abstract Scalar getMaxValue();
public abstract Scalar incrementedByOne();
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019 Florian Schmaus * Copyright 2019-2020 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,12 +21,18 @@ import org.jivesoftware.smack.util.NumberUtil;
/** /**
* A number representing an unsigned 16-bit integer. Can be used for values with the XML schema type "xs:unsingedShort". * A number representing an unsigned 16-bit integer. Can be used for values with the XML schema type "xs:unsingedShort".
*/ */
public final class UInt16 extends Scalar { public final class UInt16 extends Scalar implements Comparable<UInt16> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final int number; private final int number;
public static final int MIN_VALUE_INT = 0;
public static final int MAX_VALUE_INT = (1 << 16) - 1;
public static final UInt16 MIN_VALUE = UInt16.from(MIN_VALUE_INT);
public static final UInt16 MAX_VALUE = UInt16.from(MAX_VALUE_INT);
private UInt16(int number) { private UInt16(int number) {
super(NumberUtil.requireUShort16(number)); super(NumberUtil.requireUShort16(number));
this.number = number; this.number = number;
@ -54,4 +60,25 @@ public final class UInt16 extends Scalar {
return super.equals(other); return super.equals(other);
} }
@Override
public int compareTo(UInt16 o) {
return Integer.compare(number, o.number);
}
@Override
public UInt16 getMinValue() {
return MIN_VALUE;
}
@Override
public UInt16 getMaxValue() {
return MAX_VALUE;
}
@Override
public UInt16 incrementedByOne() {
int incrementedValue = number < MAX_VALUE_INT ? number + 1 : 0;
return UInt16.from(incrementedValue);
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019 Florian Schmaus * Copyright 2019-2020 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,6 +27,12 @@ public final class UInt32 extends Scalar {
private final long number; private final long number;
public static final long MIN_VALUE_LONG = 0;
public static final long MAX_VALUE_LONG = (1L << 32) - 1;
public static final UInt32 MIN_VALUE = UInt32.from(MAX_VALUE_LONG);
public static final UInt32 MAX_VALUE = UInt32.from(MAX_VALUE_LONG);
private UInt32(long number) { private UInt32(long number) {
super(NumberUtil.requireUInt32(number)); super(NumberUtil.requireUInt32(number));
this.number = number; this.number = number;
@ -55,4 +61,20 @@ public final class UInt32 extends Scalar {
return super.equals(other); return super.equals(other);
} }
@Override
public UInt32 getMinValue() {
return MIN_VALUE;
}
@Override
public UInt32 getMaxValue() {
return MAX_VALUE;
}
@Override
public UInt32 incrementedByOne() {
long incrementedValue = number < MAX_VALUE_LONG ? number + 1 : 0;
return UInt32.from(incrementedValue);
}
} }

View file

@ -55,4 +55,28 @@ public class SmackParsingException extends Exception {
super(uriSyntaxException); super(uriSyntaxException);
} }
} }
public static class RequiredValueMissingException extends SmackParsingException {
/**
*
*/
private static final long serialVersionUID = 1L;
public RequiredValueMissingException(String message) {
super(message);
}
}
public static class RequiredAttributeMissingException extends RequiredValueMissingException {
/**
*
*/
private static final long serialVersionUID = 1L;
public RequiredAttributeMissingException(String attributeName) {
super("The required attribute '" + attributeName + "' is missing (or has the empty String as value)");
}
}
} }

View file

@ -0,0 +1,36 @@
/**
*
* Copyright 2020 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.util;
public interface DefaultCharSequence extends CharSequence {
@Override
default int length() {
return toString().length();
}
@Override
default char charAt(int index) {
return toString().charAt(index);
}
@Override
default CharSequence subSequence(int start, int end) {
return toString().subSequence(start, end);
}
}

View file

@ -29,6 +29,7 @@ import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.datatypes.UInt32; import org.jivesoftware.smack.datatypes.UInt32;
import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException; import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.parsing.SmackParsingException.RequiredAttributeMissingException;
import org.jivesoftware.smack.parsing.SmackParsingException.SmackTextParseException; import org.jivesoftware.smack.parsing.SmackParsingException.SmackTextParseException;
import org.jivesoftware.smack.parsing.SmackParsingException.SmackUriSyntaxParsingException; import org.jivesoftware.smack.parsing.SmackParsingException.SmackUriSyntaxParsingException;
import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParser;
@ -231,6 +232,14 @@ public class ParserUtils {
return UInt16.from(integer); return UInt16.from(integer);
} }
public static UInt16 getRequiredUInt16Attribute(XmlPullParser parser, String name) throws RequiredAttributeMissingException {
UInt16 uint16 = getUInt16Attribute(parser, name);
if (uint16 == null) {
throw new SmackParsingException.RequiredAttributeMissingException(name);
}
return uint16;
}
public static int getIntegerFromNextText(XmlPullParser parser) throws XmlPullParserException, IOException { public static int getIntegerFromNextText(XmlPullParser parser) throws XmlPullParserException, IOException {
String intString = parser.nextText(); String intString = parser.nextText();
return Integer.valueOf(intString); return Integer.valueOf(intString);

View file

@ -361,6 +361,20 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return this; return this;
} }
/**
* Same as {@link #optAttribute(String, CharSequence)}, but with a different method name. This method can be used if
* the provided attribute value argument type causes ambiguity in method overloading. For example if the type is a
* subclass of Number and CharSequence.
*
* @param name the name of the attribute.
* @param value the value of the attribute.
* @return a reference to this object.
* @since 4.5
*/
public XmlStringBuilder optAttributeCs(String name, CharSequence value) {
return optAttribute(name, value);
}
/** /**
* Add the given attribute if {@code value => 0}. * Add the given attribute if {@code value => 0}.
* *

View file

@ -0,0 +1,30 @@
/**
*
* Copyright 2020 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.datatypes;
import static org.junit.Assert.assertEquals;
import org.junit.jupiter.api.Test;
public class UInt16Test {
@Test
public void testMaxValue() {
assertEquals(65535, UInt16.MAX_VALUE_INT);
}
}

View file

@ -0,0 +1,30 @@
/**
*
* Copyright 2020 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.datatypes;
import static org.junit.Assert.assertEquals;
import org.junit.jupiter.api.Test;
public class UInt32Test {
@Test
public void testMaxValue() {
assertEquals(4294967295L, UInt32.MAX_VALUE_LONG);
}
}

View file

@ -23,11 +23,14 @@ import java.net.SocketTimeoutException;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.StanzaTypeFilter; import org.jivesoftware.smack.filter.StanzaTypeFilter;
@ -61,6 +64,10 @@ import org.jxmpp.jid.Jid;
*/ */
public class InBandBytestreamSession implements BytestreamSession { public class InBandBytestreamSession implements BytestreamSession {
private static final Logger LOGGER = Logger.getLogger(InBandBytestreamSession.class.getName());
static final String UNEXPECTED_IBB_SEQUENCE = "Unexpected IBB sequence";
/* XMPP connection */ /* XMPP connection */
private final XMPPConnection connection; private final XMPPConnection connection;
@ -261,7 +268,7 @@ public class InBandBytestreamSession implements BytestreamSession {
private int bufferPointer = -1; private int bufferPointer = -1;
/* data packet sequence (range from 0 to 65535) */ /* data packet sequence (range from 0 to 65535) */
private long seq = -1; private UInt16 expectedSeq = UInt16.MIN_VALUE;
/* flag to indicate if input stream is closed */ /* flag to indicate if input stream is closed */
private boolean isClosed = false; private boolean isClosed = false;
@ -383,21 +390,16 @@ public class InBandBytestreamSession implements BytestreamSession {
return false; return false;
} }
// handle sequence overflow final UInt16 dataSeq = data.getSeq();
if (this.seq == 65535) {
this.seq = -1;
}
// check if data packets sequence is successor of last seen sequence // check if data packets sequence is successor of last seen sequence
long seq = data.getSeq(); if (!expectedSeq.equals(dataSeq)) {
if (seq - 1 != this.seq) {
// packets out of order; close stream/session // packets out of order; close stream/session
InBandBytestreamSession.this.close(); InBandBytestreamSession.this.close();
throw new IOException("Packets out of sequence"); String message = UNEXPECTED_IBB_SEQUENCE + " " + dataSeq + " received, expected "
} + expectedSeq;
else { throw new IOException(message);
this.seq = seq;
} }
expectedSeq = dataSeq.incrementedByOne();
// set buffer to decoded data // set buffer to decoded data
buffer = data.getDecodedData(); buffer = data.getDecodedData();
@ -465,22 +467,41 @@ public class InBandBytestreamSession implements BytestreamSession {
protected StanzaListener getDataPacketListener() { protected StanzaListener getDataPacketListener() {
return new StanzaListener() { return new StanzaListener() {
private long lastSequence = -1; private UInt16 expectedSequence = UInt16.MIN_VALUE;;
@Override @Override
public void processStanza(Stanza packet) throws NotConnectedException, InterruptedException { public void processStanza(Stanza packet) throws NotConnectedException, InterruptedException {
final Data dataIq = (Data) packet;
// get data packet extension // get data packet extension
DataPacketExtension data = ((Data) packet).getDataPacketExtension(); DataPacketExtension data = dataIq.getDataPacketExtension();
final UInt16 seq = data.getSeq();
/* /*
* check if sequence was not used already (see XEP-0047 Section 2.2) * check if sequence was not used already (see XEP-0047 Section 2.2)
*/ */
if (data.getSeq() <= this.lastSequence) { if (!expectedSequence.equals(seq)) {
IQ unexpectedRequest = IQ.createErrorResponse((IQ) packet, String descriptiveEnTest = UNEXPECTED_IBB_SEQUENCE + " " + seq + " received, expected "
StanzaError.Condition.unexpected_request); + expectedSequence;
StanzaError stanzaError = StanzaError.getBuilder()
.setCondition(StanzaError.Condition.unexpected_request)
.setDescriptiveEnText(descriptiveEnTest)
.build();
IQ unexpectedRequest = IQ.createErrorResponse(dataIq, stanzaError);
connection.sendStanza(unexpectedRequest); connection.sendStanza(unexpectedRequest);
return;
try {
// TODO: It would be great if close would take a "close error reason" argument. Also there
// is the question if this is really a reason to close the stream. We could have some more
// tolerance regarding out-of-sequence stanzas arriving: Even though XMPP has the in-order
// guarantee, I could imagine that there are cases where stanzas are, for example,
// duplicated because of stream resumption.
close();
} catch (IOException e) {
LOGGER.log(Level.FINER, "Could not close session, because of IOException. Close reason: "
+ descriptiveEnTest);
}
return;
} }
// check if encoded data is valid (see XEP-0047 Section 2.2) // check if encoded data is valid (see XEP-0047 Section 2.2)
@ -492,19 +513,14 @@ public class InBandBytestreamSession implements BytestreamSession {
return; return;
} }
expectedSequence = seq.incrementedByOne();
// data is valid; add to data queue // data is valid; add to data queue
dataQueue.offer(data); dataQueue.offer(data);
// confirm IQ // confirm IQ
IQ confirmData = IQ.createResultIQ((IQ) packet); IQ confirmData = IQ.createResultIQ((IQ) packet);
connection.sendStanza(confirmData); connection.sendStanza(confirmData);
// set last seen sequence
this.lastSequence = data.getSeq();
if (this.lastSequence == 65535) {
this.lastSequence = -1;
}
} }
}; };
@ -618,7 +634,7 @@ public class InBandBytestreamSession implements BytestreamSession {
protected int bufferPointer = 0; protected int bufferPointer = 0;
/* data packet sequence (range from 0 to 65535) */ /* data packet sequence (range from 0 to 65535) */
protected long seq = 0; protected UInt16 seq = UInt16.from(0);
/* flag to indicate if output stream is closed */ /* flag to indicate if output stream is closed */
protected boolean isClosed = false; protected boolean isClosed = false;
@ -756,7 +772,7 @@ public class InBandBytestreamSession implements BytestreamSession {
bufferPointer = 0; bufferPointer = 0;
// increment sequence, considering sequence overflow // increment sequence, considering sequence overflow
this.seq = this.seq + 1 == 65535 ? 0 : this.seq + 1; seq = seq.incrementedByOne();
} }

View file

@ -18,8 +18,10 @@ package org.jivesoftware.smackx.bytestreams.ibb.packet;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ.IQChildElementXmlStringBuilder; import org.jivesoftware.smack.packet.IQ.IQChildElementXmlStringBuilder;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.util.stringencoder.Base64;
@ -47,7 +49,7 @@ public class DataPacketExtension implements ExtensionElement {
private final String sessionID; private final String sessionID;
/* sequence of this packet in regard to the other data packets */ /* sequence of this packet in regard to the other data packets */
private final long seq; private final UInt16 seq;
/* the data contained in this packet */ /* the data contained in this packet */
private final String data; private final String data;
@ -60,19 +62,28 @@ public class DataPacketExtension implements ExtensionElement {
* @param sessionID unique session ID identifying this In-Band Bytestream * @param sessionID unique session ID identifying this In-Band Bytestream
* @param seq sequence of this stanza in regard to the other data packets * @param seq sequence of this stanza in regard to the other data packets
* @param data the base64 encoded data contained in this packet * @param data the base64 encoded data contained in this packet
* @throws IllegalArgumentException if seq is not within the range [0, 65535].
*/ */
public DataPacketExtension(String sessionID, long seq, String data) { public DataPacketExtension(String sessionID, int seq, String data) {
this(sessionID, UInt16.from(seq), data);
}
/**
* Creates a new In-Band Bytestream data packet.
*
* @param sessionID unique session ID identifying this In-Band Bytestream
* @param seq sequence of this stanza in regard to the other data packets
* @param data the base64 encoded data contained in this packet
*/
public DataPacketExtension(String sessionID, UInt16 seq, String data) {
if (sessionID == null || "".equals(sessionID)) { if (sessionID == null || "".equals(sessionID)) {
throw new IllegalArgumentException("Session ID must not be null or empty"); throw new IllegalArgumentException("Session ID must not be null or empty");
} }
if (seq < 0 || seq > 65535) {
throw new IllegalArgumentException("Sequence must not be between 0 and 65535");
}
if (data == null) { if (data == null) {
throw new IllegalArgumentException("Data must not be null"); throw new IllegalArgumentException("Data must not be null");
} }
this.sessionID = sessionID; this.sessionID = sessionID;
this.seq = seq; this.seq = Objects.requireNonNull(seq);
this.data = data; this.data = data;
} }
@ -90,7 +101,7 @@ public class DataPacketExtension implements ExtensionElement {
* *
* @return the sequence of this stanza in regard to the other data packets. * @return the sequence of this stanza in regard to the other data packets.
*/ */
public long getSeq() { public UInt16 getSeq() {
return seq; return seq;
} }
@ -148,7 +159,7 @@ public class DataPacketExtension implements ExtensionElement {
} }
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.attribute("seq", Long.toString(seq)); xml.attribute("seq", seq);
xml.attribute("sid", sessionID); xml.attribute("sid", sessionID);
xml.rightAngleBracket(); xml.rightAngleBracket();
xml.append(data); xml.append(data);

View file

@ -18,8 +18,11 @@ package org.jivesoftware.smackx.bytestreams.ibb.provider;
import java.io.IOException; import java.io.IOException;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException; import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.parsing.SmackParsingException.RequiredAttributeMissingException;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smack.xml.XmlPullParserException;
@ -51,9 +54,9 @@ public class DataPacketProvider {
@Override @Override
public DataPacketExtension parse(XmlPullParser parser, public DataPacketExtension parse(XmlPullParser parser,
int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException,
IOException { IOException, RequiredAttributeMissingException {
String sessionID = parser.getAttributeValue("", "sid"); String sessionID = parser.getAttributeValue("", "sid");
long seq = Long.parseLong(parser.getAttributeValue("", "seq")); UInt16 seq = ParserUtils.getRequiredUInt16Attribute(parser, "seq");
String data = parser.nextText(); String data = parser.nextText();
return new DataPacketExtension(sessionID, seq, data); return new DataPacketExtension(sessionID, seq, data);
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019 Florian Schmaus * Copyright 2019-2020 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -83,8 +83,8 @@ public class MediaElement implements FormFieldChildElement {
@Override @Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.optAttribute("height", height) xml.optAttributeCs("height", height)
.optAttribute("width", width) .optAttributeCs("width", width)
.rightAngleBracket(); .rightAngleBracket();
xml.append(uris); xml.append(uris);

View file

@ -401,8 +401,8 @@ public abstract class ValidateElement implements FormFieldChildElement {
@Override @Override
public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment); XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment);
buf.optAttribute("min", getMin()); buf.optAttributeCs("min", getMin());
buf.optAttribute("max", getMax()); buf.optAttributeCs("max", getMax());
buf.closeEmptyElement(); buf.closeEmptyElement();
return buf; return buf;
} }

View file

@ -17,8 +17,8 @@
package org.jivesoftware.smackx.bytestreams.ibb; package org.jivesoftware.smackx.bytestreams.ibb;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -102,7 +102,7 @@ public class InBandBytestreamSessionMessageTest extends SmackTestSuite {
public void verify(Message request, IQ response) { public void verify(Message request, IQ response) {
DataPacketExtension dpe = request.getExtension( DataPacketExtension dpe = request.getExtension(
DataPacketExtension.class); DataPacketExtension.class);
assertEquals(lastSeq++, dpe.getSeq()); assertEquals(lastSeq++, dpe.getSeq().longValue());
} }
}; };
@ -275,16 +275,13 @@ public class InBandBytestreamSessionMessageTest extends SmackTestSuite {
listener.processStanza(dataMessage); listener.processStanza(dataMessage);
// read until exception is thrown // read until exception is thrown
try { IOException ioException = assertThrows(IOException.class, () ->
inputStream.read(); inputStream.read()
fail("exception should be thrown"); );
} String ioExceptionMessage = ioException.getMessage();
catch (IOException e) { assertTrue(ioExceptionMessage.startsWith(InBandBytestreamSession.UNEXPECTED_IBB_SEQUENCE));
assertTrue(e.getMessage().contains("Packets out of sequence"));
}
protocol.verifyAll(); protocol.verifyAll();
} }
/** /**

View file

@ -17,6 +17,7 @@
package org.jivesoftware.smackx.bytestreams.ibb; package org.jivesoftware.smackx.bytestreams.ibb;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -29,6 +30,7 @@ import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.test.util.SmackTestSuite; import org.jivesoftware.smack.test.util.SmackTestSuite;
@ -96,11 +98,11 @@ public class InBandBytestreamSessionTest extends SmackTestSuite {
incrementingSequence = new Verification<Data, IQ>() { incrementingSequence = new Verification<Data, IQ>() {
long lastSeq = 0; int lastSeq = 0;
@Override @Override
public void verify(Data request, IQ response) { public void verify(Data request, IQ response) {
assertEquals(lastSeq++, request.getDataPacketExtension().getSeq()); assertEquals(lastSeq++, request.getDataPacketExtension().getSeq().intValue());
} }
}; };
@ -266,9 +268,9 @@ public class InBandBytestreamSessionTest extends SmackTestSuite {
@Override @Override
public void verify(Data request, IQ response) { public void verify(Data request, IQ response) {
byte[] decodedData = request.getDataPacketExtension().getDecodedData(); byte[] decodedData = request.getDataPacketExtension().getDecodedData();
int seq = (int) request.getDataPacketExtension().getSeq(); UInt16 seq = request.getDataPacketExtension().getSeq();
for (int i = 0; i < decodedData.length; i++) { for (int i = 0; i < decodedData.length; i++) {
assertEquals(controlData[(seq * blockSize) + i], decodedData[i]); assertEquals(controlData[(seq.intValue() * blockSize) + i], decodedData[i]);
} }
} }
@ -441,15 +443,6 @@ public class InBandBytestreamSessionTest extends SmackTestSuite {
*/ */
@Test @Test
public void shouldSendCloseRequestIfInvalidSequenceReceived() throws Exception { public void shouldSendCloseRequestIfInvalidSequenceReceived() throws Exception {
IQ resultIQ = IBBPacketUtils.createResultIQ(initiatorJID, targetJID);
// confirm data packet with invalid sequence
protocol.addResponse(resultIQ);
// confirm close request
protocol.addResponse(resultIQ, Verification.requestTypeSET,
Verification.correspondingSenderReceiver);
// get IBB sessions data packet listener // get IBB sessions data packet listener
InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream,
initiatorJID); initiatorJID);
@ -465,16 +458,11 @@ public class InBandBytestreamSessionTest extends SmackTestSuite {
listener.processStanza(data); listener.processStanza(data);
// read until exception is thrown // read until exception is thrown
try { IOException ioException = assertThrows(IOException.class, () ->
inputStream.read(); inputStream.read()
fail("exception should be thrown"); );
} String ioExceptionMessage = ioException.getMessage();
catch (IOException e) { assertTrue(ioExceptionMessage.equals("Stream is closed"));
assertTrue(e.getMessage().contains("Packets out of sequence"));
}
protocol.verifyAll();
} }
/** /**

View file

@ -75,7 +75,7 @@ public class DataPacketExtensionTest extends SmackTestSuite {
public void shouldSetAllFieldsCorrectly() { public void shouldSetAllFieldsCorrectly() {
DataPacketExtension data = new DataPacketExtension("sessionID", 0, "data"); DataPacketExtension data = new DataPacketExtension("sessionID", 0, "data");
assertEquals("sessionID", data.getSessionID()); assertEquals("sessionID", data.getSessionID());
assertEquals(0, data.getSeq()); assertEquals(0, data.getSeq().intValue());
assertEquals("data", data.getData()); assertEquals("data", data.getData());
} }