mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-22 06:12:05 +01:00
[ibb] Use UInt16 for 'seq' and fix its handling
Fixes a off-by-one error when incrementing 'seq'. Thanks to Kim Alvefur <zash@zash.se> for spotting this.
This commit is contained in:
parent
02c9058c3d
commit
a4bb5bfda8
17 changed files with 297 additions and 81 deletions
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -362,6 +362,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}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue