From 5bd01b7385591c406444ff65e55d7ddc2e688569 Mon Sep 17 00:00:00 2001 From: vanitasvitae Date: Fri, 30 Jun 2017 15:03:13 +0200 Subject: [PATCH 1/3] Add Jingle File Transfer elements and JingleUtil class --- .../JingleFileTransferManager.java | 43 ++ .../jingle_filetransfer/element/Checksum.java | 63 +++ .../element/JingleFileTransfer.java | 38 ++ .../element/JingleFileTransferChild.java | 167 ++++++++ .../jingle_filetransfer/element/Range.java | 135 +++++++ .../element/package-info.java | 22 + .../jingle_filetransfer/package-info.java | 21 + .../provider/ChecksumProvider.java | 90 +++++ .../provider/JingleFileTransferProvider.java | 120 ++++++ .../provider/package-info.java | 22 + .../smackx/jingle/JingleUtil.java | 381 ++++++++++++++++++ .../org/jivesoftware/smackx/jingle/Role.java | 23 ++ .../smackx/jingle/element/JingleReason.java | 2 +- 13 files changed, 1126 insertions(+), 1 deletion(-) create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/package-info.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/Role.java diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java new file mode 100644 index 000000000..7b96cdbd4 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java @@ -0,0 +1,43 @@ +/** + * + * Copyright 2017 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.jingle_filetransfer; + +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.XMPPConnection; + +/** + * Manager for JingleFileTransfer (XEP-0234). + */ +public final class JingleFileTransferManager extends Manager { + + private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + + private JingleFileTransferManager(XMPPConnection connection) { + super(connection); + } + + public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) { + JingleFileTransferManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new JingleFileTransferManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java new file mode 100644 index 000000000..a841c8ae7 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java @@ -0,0 +1,63 @@ +/** + * + * Copyright 2017 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.jingle_filetransfer.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.jingle.element.JingleContent; + +/** + * Checksum element. + */ +public class Checksum implements ExtensionElement { + public static final String ELEMENT = "checksum"; + public static final String ATTR_CREATOR = "creator"; + public static final String ATTR_NAME = "name"; + + private final JingleContent.Creator creator; + private final String name; + private final JingleFileTransferChild file; + + public Checksum(JingleContent.Creator creator, String name, JingleFileTransferChild file) { + this.creator = creator; + this.name = name; + this.file = Objects.requireNonNull(file, "file MUST NOT be null."); + Objects.requireNonNull(file.getHash(), "file MUST contain at least one hash element."); + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + sb.optAttribute(ATTR_CREATOR, creator); + sb.optAttribute(ATTR_NAME, name); + sb.rightAngleBracket(); + sb.element(file); + sb.closeElement(this); + return sb; + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE_V5; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java new file mode 100644 index 000000000..40dc1aaa2 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java @@ -0,0 +1,38 @@ +/** + * + * Copyright 2017 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.jingle_filetransfer.element; + +import java.util.List; + +import org.jivesoftware.smackx.jingle.element.JingleContentDescription; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; + +/** + * File element. + */ +public class JingleFileTransfer extends JingleContentDescription { + public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5"; + + public JingleFileTransfer(List payloads) { + super(payloads); + } + + @Override + public String getNamespace() { + return NAMESPACE_V5; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java new file mode 100644 index 000000000..9634d0a1b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java @@ -0,0 +1,167 @@ +/** + * + * Copyright 2017 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.jingle_filetransfer.element; + +import java.io.File; +import java.util.Date; + +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; + +/** + * Content of type File. + */ +public class JingleFileTransferChild extends JingleContentDescriptionChildElement { + public static final String ELEMENT = "file"; + public static final String ELEM_DATE = "date"; + public static final String ELEM_DESC = "desc"; + public static final String ELEM_MEDIA_TYPE = "media-type"; + public static final String ELEM_NAME = "name"; + public static final String ELEM_SIZE = "size"; + + private final Date date; + private final String desc; + private final HashElement hash; + private final String mediaType; + private final String name; + private final int size; + private final Range range; + + public JingleFileTransferChild(Date date, String desc, HashElement hash, String mediaType, String name, int size, Range range) { + this.date = date; + this.desc = desc; + this.hash = hash; + this.mediaType = mediaType; + this.name = name; + this.size = size; + this.range = range; + } + + public Date getDate() { + return date; + } + + public String getDescription() { + return desc; + } + + public HashElement getHash() { + return hash; + } + + public String getMediaType() { + return mediaType; + } + + public String getName() { + return name; + } + + public int getSize() { + return size; + } + + public Range getRange() { + return range; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + sb.rightAngleBracket(); + + sb.optElement(ELEM_DATE, date); + sb.optElement(ELEM_DESC, desc); + sb.optElement(ELEM_MEDIA_TYPE, mediaType); + sb.optElement(ELEM_NAME, name); + sb.optElement(range); + if (size > 0) { + sb.element(ELEM_SIZE, Integer.toString(size)); + } + sb.optElement(hash); + sb.closeElement(this); + return sb; + } + + public static Builder getBuilder() { + return new Builder(); + } + + public static final class Builder { + private Date date; + private String desc; + private HashElement hash; + private String mediaType; + private String name; + private int size; + private Range range; + + private Builder() { + } + + public Builder setDate(Date date) { + this.date = date; + return this; + } + + public Builder setDescription(String desc) { + this.desc = desc; + return this; + } + + public Builder setHash(HashElement hash) { + this.hash = hash; + return this; + } + + public Builder setMediaType(String mediaType) { + this.mediaType = mediaType; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setSize(int size) { + this.size = size; + return this; + } + + public Builder setRange(Range range) { + this.range = range; + return this; + } + + public JingleFileTransferChild build() { + return new JingleFileTransferChild(date, desc, hash, mediaType, name, size, range); + } + + public Builder setFile(File file) { + return setDate(new Date(file.lastModified())) + .setName(file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf("/") + 1)) + .setSize((int) file.length()); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java new file mode 100644 index 000000000..9d4f867ad --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java @@ -0,0 +1,135 @@ +/** + * + * Copyright © 2017 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.jingle_filetransfer.element; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.hashes.element.HashElement; + +/** + * RangeElement which specifies, which range of a file shall be transferred. + */ +public class Range implements NamedElement { + + public static final String ELEMENT = "range"; + public static final String ATTR_OFFSET = "offset"; + public static final String ATTR_LENGTH = "length"; + + private final int offset, length; + private final HashElement hash; + + /** + * Create a Range element with default values. + */ + public Range() { + this(0, -1, null); + } + + /** + * Create a Range element with specified length. + * @param length length of the transmitted data in bytes. + */ + public Range(int length) { + this(0, length, null); + } + + /** + * Create a Range element with specified offset and length. + * @param offset offset in bytes from the beginning of the transmitted data. + * @param length number of bytes that shall be transferred. + */ + public Range(int offset, int length) { + this(offset, length, null); + } + + /** + * Create a Range element with specified offset, length and hash. + * @param offset offset in bytes from the beginning of the transmitted data. + * @param length number of bytes that shall be transferred. + * @param hash hash of the bytes in the specified range. + */ + public Range(int offset, int length, HashElement hash) { + this.offset = offset; + this.length = length; + this.hash = hash; + } + + /** + * Return the index of the offset. + * This marks the begin of the specified range. + * @return offset + */ + public int getOffset() { + return offset; + } + + /** + * Return the length of the range. + * @return length + */ + public int getLength() { + return length; + } + + /** + * Return the hash element that contains a checksum of the bytes specified in the range. + * @return hash element + */ + public HashElement getHash() { + return hash; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + + if (offset > 0) { + sb.attribute(ATTR_OFFSET, offset); + } + if (length > 0) { + sb.attribute(ATTR_LENGTH, length); + } + + if (hash != null) { + sb.rightAngleBracket(); + sb.element(hash); + sb.closeElement(this); + } else { + sb.closeEmptyElement(); + } + return sb; + } + + @Override + public boolean equals(Object other) { + if (other == null || !(other instanceof Range)) { + return false; + } + + return this.hashCode() == other.hashCode(); + } + + @Override + public int hashCode() { + return toXML().toString().hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/package-info.java new file mode 100644 index 000000000..da2e22582 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/package-info.java @@ -0,0 +1,22 @@ +/** + * + * Copyright 2017 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-0234: Jingle File Transfer. + * Elements. + */ +package org.jivesoftware.smackx.jingle_filetransfer.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java new file mode 100644 index 000000000..86fb45ecc --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2017 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-0234: Jingle File Transfer. + */ +package org.jivesoftware.smackx.jingle_filetransfer; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java new file mode 100644 index 000000000..980b50a74 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java @@ -0,0 +1,90 @@ +/** + * + * Copyright 2017 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.jingle_filetransfer.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.hashes.provider.HashElementProvider; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle_filetransfer.element.Checksum; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChild; +import org.jivesoftware.smackx.jingle_filetransfer.element.Range; + +import org.xmlpull.v1.XmlPullParser; + +/** + * Provider for the Checksum element. + */ +public class ChecksumProvider extends ExtensionElementProvider { + @Override + public Checksum parse(XmlPullParser parser, int initialDepth) throws Exception { + JingleContent.Creator creator = null; + String creatorString = parser.getAttributeValue(null, Checksum.ATTR_CREATOR); + if (creatorString != null) { + creator = JingleContent.Creator.valueOf(creatorString); + } + String name = parser.getAttributeValue(null, Checksum.ATTR_NAME); + + + JingleFileTransferChild.Builder cb = JingleFileTransferChild.getBuilder(); + HashElement hashElement = null; + Range range = null; + + boolean go = true; + while (go) { + int tag = parser.nextTag(); + String n = parser.getText(); + + if (tag == START_TAG) { + switch (n) { + case HashElement.ELEMENT: + hashElement = new HashElementProvider().parse(parser); + break; + + case Range.ELEMENT: + String offset = parser.getAttributeValue(null, Range.ATTR_OFFSET); + String length = parser.getAttributeValue(null, Range.ATTR_LENGTH); + int o = offset == null ? 0 : Integer.parseInt(offset); + int l = length == null ? -1 : Integer.parseInt(length); + range = new Range(o, l); + } + } else if (tag == END_TAG) { + switch (n) { + case Range.ELEMENT: + if (hashElement != null && range != null) { + range = new Range(range.getOffset(), range.getLength(), hashElement); + hashElement = null; + } + break; + + case JingleFileTransferChild.ELEMENT: + if (hashElement != null) { + cb.setHash(hashElement); + } + if (range != null) { + cb.setRange(range); + } + go = false; + } + } + } + return new Checksum(creator, name, cb.build()); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java new file mode 100644 index 000000000..8570188a6 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java @@ -0,0 +1,120 @@ +/** + * + * Copyright 2017 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.jingle_filetransfer.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import java.util.ArrayList; + +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.hashes.provider.HashElementProvider; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; +import org.jivesoftware.smackx.jingle.provider.JingleContentDescriptionProvider; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChild; +import org.jivesoftware.smackx.jingle_filetransfer.element.Range; + +import org.jxmpp.util.XmppDateTime; +import org.xmlpull.v1.XmlPullParser; + +/** + * Provider for JingleContentDescriptionFileTransfer elements. + */ +public class JingleFileTransferProvider + extends JingleContentDescriptionProvider { + + @Override + public JingleFileTransfer parse(XmlPullParser parser, int initialDepth) throws Exception { + ArrayList payloads = new ArrayList<>(); + boolean inRange = false; + JingleFileTransferChild.Builder builder = JingleFileTransferChild.getBuilder(); + HashElement inRangeHash = null; + + int offset = 0; + int length = -1; + + while (true) { + + int tag = parser.nextTag(); + String elem = parser.getName(); + + if (tag == START_TAG) { + switch (elem) { + case JingleFileTransferChild.ELEM_DATE: + builder.setDate(XmppDateTime.parseXEP0082Date(parser.nextText())); + break; + + case JingleFileTransferChild.ELEM_DESC: + builder.setDescription(parser.nextText()); + break; + + case JingleFileTransferChild.ELEM_MEDIA_TYPE: + builder.setMediaType(parser.nextText()); + break; + + case JingleFileTransferChild.ELEM_NAME: + builder.setName(parser.nextText()); + break; + + case JingleFileTransferChild.ELEM_SIZE: + builder.setSize(Integer.parseInt(parser.nextText())); + break; + + case Range.ELEMENT: + inRange = true; + String offsetString = parser.getAttributeValue(null, Range.ATTR_OFFSET); + String lengthString = parser.getAttributeValue(null, Range.ATTR_LENGTH); + offset = (offsetString != null ? Integer.parseInt(offsetString) : 0); + length = (lengthString != null ? Integer.parseInt(lengthString) : -1); + + if (parser.isEmptyElementTag()) { + inRange = false; + builder.setRange(new Range(offset, length)); + } + break; + + case HashElement.ELEMENT: + if (inRange) { + inRangeHash = new HashElementProvider().parse(parser); + } else { + builder.setHash(new HashElementProvider().parse(parser)); + } + break; + } + + } else if (tag == END_TAG) { + switch (elem) { + + case Range.ELEMENT: + inRange = false; + builder.setRange(new Range(offset, length, inRangeHash)); + inRangeHash = null; + break; + + case JingleFileTransferChild.ELEMENT: + payloads.add(builder.build()); + builder = JingleFileTransferChild.getBuilder(); + break; + + case JingleFileTransfer.ELEMENT: + return new JingleFileTransfer(payloads); + } + } + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/package-info.java new file mode 100644 index 000000000..85163fdeb --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/package-info.java @@ -0,0 +1,22 @@ +/** + * + * Copyright 2017 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-0234: Jingle File Transfer. + * Providers. + */ +package org.jivesoftware.smackx.jingle_filetransfer.provider; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java new file mode 100644 index 000000000..26d7c7ed3 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java @@ -0,0 +1,381 @@ +/** + * + * Copyright 2017 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.jingle; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleAction; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentDescription; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.element.JingleError; +import org.jivesoftware.smackx.jingle.element.JingleReason; + +import org.jxmpp.jid.FullJid; + +/** + * Util to quickly create and send jingle stanzas. + */ +public class JingleUtil { + + private final XMPPConnection connection; + + public JingleUtil(XMPPConnection connection) { + this.connection = connection; + } + + public Jingle createSessionInitiate(FullJid recipient, + String sessionId, + JingleContent.Creator contentCreator, + String contentName, + JingleContent.Senders contentSenders, + JingleContentDescription description, + JingleContentTransport transport) { + + Jingle.Builder jb = Jingle.getBuilder(); + jb.setAction(JingleAction.session_initiate) + .setSessionId(sessionId) + .setInitiator(connection.getUser()); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setCreator(contentCreator) + .setName(contentName) + .setSenders(contentSenders) + .setDescription(description) + .setTransport(transport); + + Jingle jingle = jb.addJingleContent(cb.build()).build(); + jingle.setFrom(connection.getUser()); + jingle.setTo(recipient); + + return jingle; + } + + public IQ sendSessionInitiate(FullJid recipient, + String sessionId, + JingleContent.Creator contentCreator, + String contentName, + JingleContent.Senders contentSenders, + JingleContentDescription description, + JingleContentTransport transport) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + + Jingle jingle = createSessionInitiate(recipient, sessionId, contentCreator, contentName, contentSenders, + description, transport); + + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionAccept(FullJid recipient, + String sessionId, + JingleContent.Creator contentCreator, + String contentName, + JingleContent.Senders contentSenders, + JingleContentDescription description, + JingleContentTransport transport) { + + Jingle.Builder jb = Jingle.getBuilder(); + jb.setResponder(connection.getUser()) + .setAction(JingleAction.session_accept) + .setSessionId(sessionId); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setCreator(contentCreator) + .setName(contentName) + .setSenders(contentSenders) + .setDescription(description) + .setTransport(transport); + + Jingle jingle = jb.addJingleContent(cb.build()).build(); + jingle.setTo(recipient); + jingle.setFrom(connection.getUser()); + + return jingle; + } + + public IQ sendSessionAccept(FullJid recipient, + String sessionId, + JingleContent.Creator contentCreator, + String contentName, + JingleContent.Senders contentSenders, + JingleContentDescription description, + JingleContentTransport transport) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + + Jingle jingle = createSessionAccept(recipient, sessionId, contentCreator, contentName, contentSenders, + description, transport); + + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminate(FullJid recipient, String sessionId, JingleReason reason) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setAction(JingleAction.session_terminate) + .setSessionId(sessionId) + .setReason(reason); + + Jingle jingle = jb.build(); + jingle.setFrom(connection.getUser()); + jingle.setTo(recipient); + + return jingle; + } + + public Jingle createSessionTerminate(FullJid recipient, String sessionId, JingleReason.Reason reason) { + return createSessionTerminate(recipient, sessionId, new JingleReason(reason)); + } + + private IQ sendSessionTerminate(FullJid recipient, String sessionId, JingleReason.Reason reason) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + + return sendSessionTerminate(recipient, sessionId, new JingleReason(reason)); + } + + private IQ sendSessionTerminate(FullJid recipient, String sessionId, JingleReason reason) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + + Jingle jingle = createSessionTerminate(recipient, sessionId, reason); + + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateDecline(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.decline); + } + + public IQ sendSessionTerminateDecline(FullJid recipient, String sessionId) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + + Jingle jingle = createSessionTerminateDecline(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateSuccess(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.success); + } + + public IQ sendSessionTerminateSuccess(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + + Jingle jingle = createSessionTerminateSuccess(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateBusy(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.busy); + } + + public IQ sendSessionTerminateBusy(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + + Jingle jingle = createSessionTerminateBusy(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateAlternativeSession(FullJid recipient, String sessionId, String altSessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.AlternativeSession(altSessionId)); + } + + public IQ sendSessionTerminateAlternativeSession(FullJid recipient, String sessionId, String altSessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + + Jingle jingle = createSessionTerminateAlternativeSession(recipient, sessionId, altSessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateCancel(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.cancel); + } + + public IQ sendSessionTerminateCancel(FullJid recipient, + String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + + Jingle jingle = createSessionTerminateCancel(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateContentCancel(FullJid recipient, String sessionId, + JingleContent.Creator contentCreator, String contentName) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setAction(JingleAction.session_terminate) + .setSessionId(sessionId); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setCreator(contentCreator).setName(contentName); + + Jingle jingle = jb.addJingleContent(cb.build()).build(); + jingle.setFrom(connection.getUser()); + jingle.setTo(recipient); + + return jingle; + } + + public IQ sendSessionTerminateContentCancel(FullJid recipient, String sessionId, + JingleContent.Creator contentCreator, String contentName) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + Jingle jingle = createSessionTerminateContentCancel(recipient, sessionId, contentCreator, contentName); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateUnsupportedTransports(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.unsupported_transports); + } + + public IQ sendSessionTerminateUnsupportedTransports(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + Jingle jingle = createSessionTerminateUnsupportedTransports(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateFailedTransport(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.failed_transport); + } + + public IQ sendSessionTerminateFailedTransport(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + Jingle jingle = createSessionTerminateFailedTransport(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateUnsupportedApplications(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.unsupported_applications); + } + + public IQ sendSessionTerminateUnsupportedApplications(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + Jingle jingle = createSessionTerminateUnsupportedApplications(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateFailedApplication(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.failed_application); + } + + public IQ sendSessionTerminateFailedApplication(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + Jingle jingle = createSessionTerminateFailedApplication(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionTerminateIncompatibleParameters(FullJid recipient, String sessionId) { + return createSessionTerminate(recipient, sessionId, JingleReason.Reason.incompatible_parameters); + } + + public IQ sendSessionTerminateIncompatibleParameters(FullJid recipient, String sessionId) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + Jingle jingle = createSessionTerminateIncompatibleParameters(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createSessionPing(FullJid recipient, String sessionId) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setSessionId(sessionId) + .setAction(JingleAction.session_info); + + Jingle jingle = jb.build(); + jingle.setFrom(connection.getUser()); + jingle.setTo(recipient); + + return jingle; + } + + public IQ sendSessionPing(FullJid recipient, String sessionId) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + Jingle jingle = createSessionPing(recipient, sessionId); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public IQ createErrorUnknownSession(Jingle request) { + XMPPError.Builder error = XMPPError.getBuilder(); + error.setCondition(XMPPError.Condition.item_not_found) + .addExtension(JingleError.UNKNOWN_SESSION); + return IQ.createErrorResponse(request, error); + } + + public void sendErrorUnknownSession(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorUnknownSession(request)); + } + + public IQ createErrorUnknownInitiator(Jingle request) { + return IQ.createErrorResponse(request, XMPPError.Condition.service_unavailable); + } + + public void sendErrorUnknownInitiator(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorUnknownInitiator(request)); + } + + public IQ createErrorUnsupportedInfo(Jingle request) { + XMPPError.Builder error = XMPPError.getBuilder(); + error.setCondition(XMPPError.Condition.feature_not_implemented) + .addExtension(JingleError.UNSUPPORTED_INFO); + + return IQ.createErrorResponse(request, error); + } + + public void sendErrorUnsupportedInfo(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorUnsupportedInfo(request)); + } + + + public IQ createErrorTieBreak(Jingle request) { + XMPPError.Builder error = XMPPError.getBuilder(); + error.setCondition(XMPPError.Condition.conflict) + .addExtension(JingleError.TIE_BREAK); + return IQ.createErrorResponse(request, error); + } + + public void sendErrorTieBreak(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorTieBreak(request)); + } + + public IQ createErrorOutOfOrder(Jingle request) { + XMPPError.Builder error = XMPPError.getBuilder(); + error.setCondition(XMPPError.Condition.unexpected_request) + .addExtension(JingleError.OUT_OF_ORDER); + return IQ.createErrorResponse(request, error); + } + + public void sendErrorOutOfOrder(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorOutOfOrder(request)); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/Role.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/Role.java new file mode 100644 index 000000000..0473b0894 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/Role.java @@ -0,0 +1,23 @@ +/** + * + * Copyright 2017 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.jingle; + +public enum Role { + initiator, + responder, + ; +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java index 2855ccb8f..a975968b2 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java @@ -104,7 +104,7 @@ public class JingleReason implements NamedElement { protected final Reason reason; - protected JingleReason(Reason reason) { + public JingleReason(Reason reason) { this.reason = reason; } From 7e76bc1ae55771cac5a4bedb0f4ba591b43c19ef Mon Sep 17 00:00:00 2001 From: vanitasvitae Date: Mon, 3 Jul 2017 10:35:46 +0200 Subject: [PATCH 2/3] More changes to the Jingle package: - Change visibility of some Socks5Bytestreams code. - Add central ThreadPool - Move FullJidAndSessionId in own class - More complete JingleSession class - More complete JingleUtil class - Improved tests --- .../socks5/Socks5BytestreamManager.java | 4 +- .../socks5/Socks5BytestreamSession.java | 2 +- .../bytestreams/socks5/Socks5Client.java | 2 +- .../socks5/Socks5ClientForInitiator.java | 2 +- .../bytestreams/socks5/Socks5Proxy.java | 2 +- .../bytestreams/socks5/Socks5Utils.java | 2 +- .../smackx/jingle/FullJidAndSessionId.java | 57 +++ .../smackx/jingle/JingleManager.java | 112 +++--- .../smackx/jingle/JingleSession.java | 214 ++++++++++- .../smackx/jingle/JingleSessionHandler.java | 2 +- .../jingle/JingleTransportMethodManager.java | 132 +++++++ .../smackx/jingle/JingleUtil.java | 135 ++++++- .../smackx/jingle/element/Jingle.java | 4 + .../smackx/jingle/element/JingleReason.java | 4 + .../JingleTransportInitiationCallback.java | 30 ++ .../JingleTransportInitiationException.java | 33 ++ .../transports/JingleTransportManager.java | 74 ++++ .../transports/JingleTransportSession.java | 62 +++ .../jingle_ibb/JingleIBBTransportManager.java | 64 ++++ .../jingle_ibb/JingleIBBTransportSession.java | 115 ++++++ .../jingle_s5b/JingleS5BTransportManager.java | 234 ++++++++++++ .../jingle_s5b/JingleS5BTransportSession.java | 361 ++++++++++++++++++ .../elements/JingleS5BTransport.java | 13 +- .../elements/JingleS5BTransportCandidate.java | 10 +- .../jingle/transports/package-info.java | 21 + .../bytestreams/socks5/Socks5ProxyTest.java | 13 +- .../JingleContentProviderManagerTest.java | 50 +++ .../smackx/jingle/JingleContentTest.java | 3 +- .../smackx/jingle/JingleErrorTest.java | 40 +- .../smackx/jingle/JingleSessionTest.java | 52 --- .../smackx/jingle/JingleUtilTest.java | 102 +++++ .../jingle_ibb/JingleIBBTransportTest.java | 3 + .../jingle_s5b/JingleS5BTransportTest.java | 65 +++- 33 files changed, 1859 insertions(+), 160 deletions(-) create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/FullJidAndSessionId.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportMethodManager.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationCallback.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationException.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportSession.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportManager.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportSession.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportSession.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/package-info.java create mode 100644 smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentProviderManagerTest.java delete mode 100644 smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java create mode 100644 smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleUtilTest.java diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java index 38c0a6261..247e4de7e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java @@ -538,7 +538,7 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream * @throws NotConnectedException * @throws InterruptedException */ - private List determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public List determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { XMPPConnection connection = connection(); ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); @@ -634,7 +634,7 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy * is not running */ - private List getLocalStreamHost() { + public List getLocalStreamHost() { XMPPConnection connection = connection(); // get local proxy singleton Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java index 06cf408ad..c44b85f2a 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java @@ -37,7 +37,7 @@ public class Socks5BytestreamSession implements BytestreamSession { /* flag to indicate if this session is a direct or mediated connection */ private final boolean isDirect; - protected Socks5BytestreamSession(Socket socket, boolean isDirect) { + public Socks5BytestreamSession(Socket socket, boolean isDirect) { this.socket = socket; this.isDirect = isDirect; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java index 1c7b43670..15df6af4b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java @@ -46,7 +46,7 @@ import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; * * @author Henning Staib */ -class Socks5Client { +public class Socks5Client { private static final Logger LOGGER = Logger.getLogger(Socks5Client.class.getName()); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java index 53d744776..993aaa829 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java @@ -42,7 +42,7 @@ import org.jxmpp.jid.Jid; * * @author Henning Staib */ -class Socks5ClientForInitiator extends Socks5Client { +public class Socks5ClientForInitiator extends Socks5Client { /* the XMPP connection used to communicate with the SOCKS5 proxy */ private WeakReference connection; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java index 6f1b4bc3b..f7e89270a 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java @@ -348,7 +348,7 @@ public final class Socks5Proxy { * * @param digest to be added to the list of allowed transfers */ - protected void addTransfer(String digest) { + public void addTransfer(String digest) { this.allowedConnections.add(digest); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java index 0053cf362..bca0c5727 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java @@ -29,7 +29,7 @@ import org.jxmpp.jid.Jid; * * @author Henning Staib */ -class Socks5Utils { +public class Socks5Utils { /** * Returns a SHA-1 digest of the given parameters as specified in INSTANCES = new WeakHashMap<>(); + private static final ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + public static ExecutorService getThreadPool() { + return threadPool; + } + public static synchronized JingleManager getInstanceFor(XMPPConnection connection) { JingleManager jingleManager = INSTANCES.get(connection); if (jingleManager == null) { @@ -54,45 +65,53 @@ public final class JingleManager extends Manager { private final Map jingleSessionHandlers = new ConcurrentHashMap<>(); + private final JingleUtil jutil; + private JingleManager(XMPPConnection connection) { super(connection); + jutil = new JingleUtil(connection); + connection.registerIQRequestHandler( - new AbstractIqRequestHandler(Jingle.ELEMENT, Jingle.NAMESPACE, Type.set, Mode.async) { - @Override - public IQ handleIQRequest(IQ iqRequest) { - final Jingle jingle = (Jingle) iqRequest; + new AbstractIqRequestHandler(Jingle.ELEMENT, Jingle.NAMESPACE, Type.set, Mode.async) { + @Override + public IQ handleIQRequest(IQ iqRequest) { + final Jingle jingle = (Jingle) iqRequest; - if (jingle.getContents().isEmpty()) { - Jid from = jingle.getFrom(); - assert (from != null); - FullJid fullFrom = from.asFullJidOrThrow(); - String sid = jingle.getSid(); - FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(fullFrom, sid); - JingleSessionHandler jingleSessionHandler = jingleSessionHandlers.get(fullJidAndSessionId); - if (jingleSessionHandler == null) { - // TODO handle non existing jingle session handler. - return null; - } - return jingleSessionHandler.handleJingleSessionRequest(jingle, sid); - } + FullJid fullFrom = jingle.getFrom().asFullJidOrThrow(); + String sid = jingle.getSid(); + FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(fullFrom, sid); - if (jingle.getContents().size() > 1) { - LOGGER.severe("Jingle IQs with more then one content element are currently not supported by Smack"); - return null; - } + JingleSessionHandler sessionHandler = jingleSessionHandlers.get(fullJidAndSessionId); + if (sessionHandler != null) { + //Handle existing session + return sessionHandler.handleJingleSessionRequest(jingle); + } - JingleContent content = jingle.getContents().get(0); - JingleContentDescription description = content.getDescription(); - JingleHandler jingleDescriptionHandler = descriptionHandlers.get( - description.getNamespace()); - if (jingleDescriptionHandler == null) { - // TODO handle non existing content description handler. - return null; - } - return jingleDescriptionHandler.handleJingleRequest(jingle); + if (jingle.getAction() == JingleAction.session_initiate) { + + JingleContent content = jingle.getContents().get(0); + JingleContentDescription description = content.getDescription(); + JingleHandler jingleDescriptionHandler = descriptionHandlers.get( + description.getNamespace()); + + if (jingleDescriptionHandler == null) { + //Unsupported Application + LOGGER.log(Level.WARNING, "Unsupported Jingle application."); + return jutil.createSessionTerminateUnsupportedApplications(fullFrom, sid); } - }); + return jingleDescriptionHandler.handleJingleRequest(jingle); + } + + //Unknown session + LOGGER.log(Level.WARNING, "Unknown session."); + return jutil.createErrorUnknownSession(jingle); + } + }); + //Register transports. + JingleTransportMethodManager transportMethodManager = JingleTransportMethodManager.getInstanceFor(connection); + transportMethodManager.registerTransportManager(JingleIBBTransportManager.getInstanceFor(connection)); + transportMethodManager.registerTransportManager(JingleS5BTransportManager.getInstanceFor(connection)); } public JingleHandler registerDescriptionHandler(String namespace, JingleHandler handler) { @@ -109,30 +128,7 @@ public final class JingleManager extends Manager { return jingleSessionHandlers.remove(fullJidAndSessionId); } - private static final class FullJidAndSessionId { - final FullJid fullJid; - final String sessionId; - - private FullJidAndSessionId(FullJid fullJid, String sessionId) { - this.fullJid = fullJid; - this.sessionId = sessionId; - } - - @Override - public int hashCode() { - int hashCode = 31 * fullJid.hashCode(); - hashCode = 31 * hashCode + sessionId.hashCode(); - return hashCode; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof FullJidAndSessionId)) { - return false; - } - FullJidAndSessionId otherFullJidAndSessionId = (FullJidAndSessionId) other; - return fullJid.equals(otherFullJidAndSessionId.fullJid) - && sessionId.equals(otherFullJidAndSessionId.sessionId); - } + public static String randomId() { + return StringUtils.randomString(24); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java index e86223371..9bd71d81e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java @@ -16,28 +16,108 @@ */ package org.jivesoftware.smackx.jingle; -import org.jxmpp.jid.Jid; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Future; +import java.util.logging.Logger; -// TODO: Is this class still required? If not, then remove it. -public class JingleSession { +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.transports.JingleTransportSession; - private final Jid initiator; +import org.jxmpp.jid.FullJid; - private final Jid responder; +public abstract class JingleSession implements JingleSessionHandler { + private static final Logger LOGGER = Logger.getLogger(JingleSession.class.getName()); + protected HashSet failedTransportMethods = new HashSet<>(); - private final String sid; + protected final FullJid local; - public JingleSession(Jid initiator, Jid responder, String sid) { - this.initiator = initiator; - this.responder = responder; + protected final FullJid remote; + + protected final Role role; + + protected final String sid; + + protected final List contents = new ArrayList<>(); + + protected ArrayList> queued = new ArrayList<>(); + protected JingleTransportSession transportSession; + + public JingleSession(FullJid initiator, FullJid responder, Role role, String sid) { + this(initiator, responder, role, sid, null); + } + + public JingleSession(FullJid initiator, FullJid responder, Role role, String sid, List contents) { + if (role == Role.initiator) { + this.local = initiator; + this.remote = responder; + } else { + this.local = responder; + this.remote = initiator; + } this.sid = sid; + this.role = role; + + if (contents != null) { + this.contents.addAll(contents); + } + } + + public FullJid getInitiator() { + return isInitiator() ? local : remote; + } + + public boolean isInitiator() { + return role == Role.initiator; + } + + public FullJid getResponder() { + return isResponder() ? local : remote; + } + + public boolean isResponder() { + return role == Role.responder; + } + + public FullJid getRemote() { + return remote; + } + + public FullJid getLocal() { + return local; + } + + public String getSessionId() { + return sid; + } + + public FullJidAndSessionId getFullJidAndSessionId() { + return new FullJidAndSessionId(remote, sid); + } + + public List getContents() { + return contents; + } + + public JingleTransportSession getTransportSession() { + return transportSession; + } + + protected void setTransportSession(JingleTransportSession transportSession) { + this.transportSession = transportSession; } @Override public int hashCode() { - int hashCode = 31 + initiator.hashCode(); - hashCode = 31 * hashCode + responder.hashCode(); - hashCode = 31 * hashCode + sid.hashCode(); + int hashCode = 31 + getInitiator().hashCode(); + hashCode = 31 * hashCode + getResponder().hashCode(); + hashCode = 31 * hashCode + getSessionId().hashCode(); return hashCode; } @@ -48,7 +128,113 @@ public class JingleSession { } JingleSession otherJingleSession = (JingleSession) other; - return initiator.equals(otherJingleSession.initiator) && responder.equals(otherJingleSession.responder) - && sid.equals(otherJingleSession.sid); + return getInitiator().equals(otherJingleSession.getInitiator()) + && getResponder().equals(otherJingleSession.getResponder()) + && sid.equals(otherJingleSession.sid); } + + @Override + public IQ handleJingleSessionRequest(Jingle jingle) { + try { + switch (jingle.getAction()) { + case content_accept: + return handleContentAccept(jingle); + case content_add: + return handleContentAdd(jingle); + case content_modify: + return handleContentModify(jingle); + case content_reject: + return handleContentReject(jingle); + case content_remove: + return handleContentRemove(jingle); + case description_info: + return handleDescriptionInfo(jingle); + case session_info: + return handleSessionInfo(jingle); + case security_info: + return handleSecurityInfo(jingle); + case session_accept: + return handleSessionAccept(jingle); + case transport_accept: + return handleTransportAccept(jingle); + case transport_info: + return transportSession.handleTransportInfo(jingle); + case session_initiate: + return handleSessionInitiate(jingle); + case transport_reject: + return handleTransportReject(jingle); + case session_terminate: + return handleSessionTerminate(jingle); + case transport_replace: + return handleTransportReplace(jingle); + default: + return IQ.createResultIQ(jingle); + } + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + return null; //TODO: + } + } + + protected IQ handleSessionInitiate(Jingle sessionInitiate) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { + return IQ.createResultIQ(sessionInitiate); + } + + protected IQ handleSessionTerminate(Jingle sessionTerminate) { + return IQ.createResultIQ(sessionTerminate); + } + + protected IQ handleSessionInfo(Jingle sessionInfo) { + return IQ.createResultIQ(sessionInfo); + } + + protected IQ handleSessionAccept(Jingle sessionAccept) throws SmackException.NotConnectedException, InterruptedException { + return IQ.createResultIQ(sessionAccept); + } + + protected IQ handleContentAdd(Jingle contentAdd) { + return IQ.createResultIQ(contentAdd); + } + + protected IQ handleContentAccept(Jingle contentAccept) { + return IQ.createResultIQ(contentAccept); + } + + protected IQ handleContentModify(Jingle contentModify) { + return IQ.createResultIQ(contentModify); + } + + protected IQ handleContentReject(Jingle contentReject) { + return IQ.createResultIQ(contentReject); + } + + protected IQ handleContentRemove(Jingle contentRemove) { + return IQ.createResultIQ(contentRemove); + } + + protected IQ handleDescriptionInfo(Jingle descriptionInfo) { + return IQ.createResultIQ(descriptionInfo); + } + + protected IQ handleSecurityInfo(Jingle securityInfo) { + return IQ.createResultIQ(securityInfo); + } + + protected IQ handleTransportAccept(Jingle transportAccept) throws SmackException.NotConnectedException, InterruptedException { + return IQ.createResultIQ(transportAccept); + } + + protected IQ handleTransportReplace(Jingle transportReplace) + throws InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + return IQ.createResultIQ(transportReplace); + } + + protected IQ handleTransportReject(Jingle transportReject) { + return IQ.createResultIQ(transportReject); + } + + public abstract XMPPConnection getConnection(); + + public abstract void onTransportMethodFailed(String namespace); + } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSessionHandler.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSessionHandler.java index b830bdbe6..a57ede3ff 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSessionHandler.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSessionHandler.java @@ -21,6 +21,6 @@ import org.jivesoftware.smackx.jingle.element.Jingle; public interface JingleSessionHandler { - IQ handleJingleSessionRequest(Jingle jingle, String sessionId); + IQ handleJingleSessionRequest(Jingle jingle); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportMethodManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportMethodManager.java new file mode 100644 index 000000000..19206384b --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleTransportMethodManager.java @@ -0,0 +1,132 @@ +/** + * + * Copyright 2017 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.jingle; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.transports.JingleTransportManager; +import org.jivesoftware.smackx.jingle.transports.jingle_ibb.element.JingleIBBTransport; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; + +/** + * Manager where TransportMethods are registered. + */ +public final class JingleTransportMethodManager extends Manager { + + private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + + private final HashMap> transportManagers = new HashMap<>(); + + private static final String[] transportPreference = new String[] { + JingleS5BTransport.NAMESPACE_V1, + JingleIBBTransport.NAMESPACE_V1 + }; + + private JingleTransportMethodManager(XMPPConnection connection) { + super(connection); + } + + public static JingleTransportMethodManager getInstanceFor(XMPPConnection connection) { + JingleTransportMethodManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new JingleTransportMethodManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + public void registerTransportManager(JingleTransportManager manager) { + transportManagers.put(manager.getNamespace(), manager); + } + + public static JingleTransportManager getTransportManager(XMPPConnection connection, String namespace) { + return getInstanceFor(connection).getTransportManager(namespace); + } + + public JingleTransportManager getTransportManager(String namespace) { + return transportManagers.get(namespace); + } + + public static JingleTransportManager getTransportManager(XMPPConnection connection, Jingle request) { + return getInstanceFor(connection).getTransportManager(request); + } + public JingleTransportManager getTransportManager(Jingle request) { + + JingleContent content = request.getContents().get(0); + if (content == null) { + return null; + } + + JingleContentTransport transport = content.getJingleTransport(); + if (transport == null) { + return null; + } + + return getTransportManager(transport.getNamespace()); + } + + public JingleTransportManager getBestAvailableTransportManager(XMPPConnection connection) { + return getInstanceFor(connection).getBestAvailableTransportManager(); + } + + public JingleTransportManager getBestAvailableTransportManager() { + JingleTransportManager tm; + for (String ns : transportPreference) { + tm = getTransportManager(ns); + if (tm != null) { + return tm; + } + } + + Iterator it = transportManagers.keySet().iterator(); + if (it.hasNext()) { + return getTransportManager(it.next()); + } + + return null; + } + + public JingleTransportManager getBestAvailableTransportManager(Set except) { + JingleTransportManager tm; + for (String ns : transportPreference) { + tm = getTransportManager(ns); + if (tm != null) { + if (except.contains(tm.getNamespace())) { + continue; + } + return tm; + } + } + + for (String ns : transportManagers.keySet()) { + if (except.contains(ns)) { + continue; + } + return getTransportManager(ns); + } + + return null; + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java index 26d7c7ed3..8f47fea94 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java @@ -69,6 +69,29 @@ public class JingleUtil { return jingle; } + public Jingle createSessionInitiateFileOffer(FullJid recipient, + String sessionId, + JingleContent.Creator contentCreator, + String contentName, + JingleContentDescription description, + JingleContentTransport transport) { + return createSessionInitiate(recipient, sessionId, contentCreator, contentName, + JingleContent.Senders.initiator, description, transport); + } + + public IQ sendSessionInitiateFileOffer(FullJid recipient, + String sessionId, + JingleContent.Creator contentCreator, + String contentName, + JingleContentDescription description, + JingleContentTransport transport) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + + Jingle jingle = createSessionInitiateFileOffer(recipient, sessionId, contentCreator, contentName, description, transport); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + public IQ sendSessionInitiate(FullJid recipient, String sessionId, JingleContent.Creator contentCreator, @@ -82,7 +105,7 @@ public class JingleUtil { Jingle jingle = createSessionInitiate(recipient, sessionId, contentCreator, contentName, contentSenders, description, transport); - return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + return connection.createStanzaCollectorAndSend(jingle).nextResult(); } public Jingle createSessionAccept(FullJid recipient, @@ -125,7 +148,7 @@ public class JingleUtil { Jingle jingle = createSessionAccept(recipient, sessionId, contentCreator, contentName, contentSenders, description, transport); - return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + return connection.createStanzaCollectorAndSend(jingle).nextResult(); } public Jingle createSessionTerminate(FullJid recipient, String sessionId, JingleReason reason) { @@ -301,6 +324,10 @@ public class JingleUtil { return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); } + public IQ sendContentRejectFileNotAvailable(FullJid recipient, String sessionId, JingleContentDescription description) { + return null; //TODO Later + } + public Jingle createSessionPing(FullJid recipient, String sessionId) { Jingle.Builder jb = Jingle.getBuilder(); jb.setSessionId(sessionId) @@ -320,6 +347,99 @@ public class JingleUtil { return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); } + public IQ createAck(Jingle jingle) { + return IQ.createResultIQ(jingle); + } + + public void sendAck(Jingle jingle) throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createAck(jingle)); + } + + public Jingle createTransportReplace(FullJid recipient, FullJid initiator, String sessionId, + JingleContent.Creator contentCreator, String contentName, + JingleContentTransport transport) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setInitiator(initiator) + .setSessionId(sessionId) + .setAction(JingleAction.transport_replace); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setName(contentName).setCreator(contentCreator).setTransport(transport); + Jingle jingle = jb.addJingleContent(cb.build()).build(); + + jingle.setTo(recipient); + jingle.setFrom(connection.getUser()); + + return jingle; + } + + public IQ sendTransportReplace(FullJid recipient, FullJid initiator, String sessionId, + JingleContent.Creator contentCreator, String contentName, + JingleContentTransport transport) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + Jingle jingle = createTransportReplace(recipient, initiator, sessionId, contentCreator, contentName, transport); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createTransportAccept(FullJid recipient, FullJid initiator, String sessionId, + JingleContent.Creator contentCreator, String contentName, + JingleContentTransport transport) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setAction(JingleAction.transport_accept) + .setInitiator(initiator) + .setSessionId(sessionId); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setCreator(contentCreator).setName(contentName).setTransport(transport); + + Jingle jingle = jb.addJingleContent(cb.build()).build(); + jingle.setTo(recipient); + jingle.setFrom(connection.getUser()); + + return jingle; + } + + public IQ sendTransportAccept(FullJid recipient, FullJid initiator, String sessionId, + JingleContent.Creator contentCreator, String contentName, + JingleContentTransport transport) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + Jingle jingle = createTransportAccept(recipient, initiator, sessionId, contentCreator, contentName, transport); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + public Jingle createTransportReject(FullJid recipient, FullJid initiator, String sessionId, + JingleContent.Creator contentCreator, String contentName, + JingleContentTransport transport) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setAction(JingleAction.transport_reject) + .setInitiator(initiator) + .setSessionId(sessionId); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setCreator(contentCreator).setName(contentName).setTransport(transport); + + Jingle jingle = jb.addJingleContent(cb.build()).build(); + jingle.setTo(recipient); + jingle.setFrom(connection.getUser()); + + return jingle; + } + + public IQ sendTransportReject(FullJid recipient, FullJid initiator, String sessionId, + JingleContent.Creator contentCreator, String contentName, + JingleContentTransport transport) + throws SmackException.NotConnectedException, InterruptedException, + XMPPException.XMPPErrorException, SmackException.NoResponseException { + Jingle jingle = createTransportReject(recipient, initiator, sessionId, contentCreator, contentName, transport); + return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); + } + + /* + * #################################################################################################### + */ + public IQ createErrorUnknownSession(Jingle request) { XMPPError.Builder error = XMPPError.getBuilder(); error.setCondition(XMPPError.Condition.item_not_found) @@ -345,7 +465,6 @@ public class JingleUtil { XMPPError.Builder error = XMPPError.getBuilder(); error.setCondition(XMPPError.Condition.feature_not_implemented) .addExtension(JingleError.UNSUPPORTED_INFO); - return IQ.createErrorResponse(request, error); } @@ -354,7 +473,6 @@ public class JingleUtil { connection.sendStanza(createErrorUnsupportedInfo(request)); } - public IQ createErrorTieBreak(Jingle request) { XMPPError.Builder error = XMPPError.getBuilder(); error.setCondition(XMPPError.Condition.conflict) @@ -378,4 +496,13 @@ public class JingleUtil { throws SmackException.NotConnectedException, InterruptedException { connection.sendStanza(createErrorOutOfOrder(request)); } + + public IQ createErrorMalformedRequest(Jingle request) { + return IQ.createErrorResponse(request, XMPPError.Condition.bad_request); + } + + public void sendErrorMalformedRequest(Jingle request) + throws SmackException.NotConnectedException, InterruptedException { + connection.sendStanza(createErrorMalformedRequest(request)); + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java index 8e8b2f580..3106563d8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java @@ -122,6 +122,10 @@ public final class Jingle extends IQ { return action; } + public JingleReason getReason() { + return reason; + } + /** * Get a List of the contents. * diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java index a975968b2..b91eb758b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java @@ -124,6 +124,10 @@ public class JingleReason implements NamedElement { return xml; } + public Reason asEnum() { + return reason; + } + public static class AlternativeSession extends JingleReason { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationCallback.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationCallback.java new file mode 100644 index 000000000..79932c1e0 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationCallback.java @@ -0,0 +1,30 @@ +/** + * + * Copyright 2017 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.jingle.transports; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; + +/** + * Callback for bytestream session creation of TransportManagers. + */ +public interface JingleTransportInitiationCallback { + + void onSessionInitiated(BytestreamSession bytestreamSession); + + void onException(Exception e); + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationException.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationException.java new file mode 100644 index 000000000..8971d2821 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportInitiationException.java @@ -0,0 +1,33 @@ +/** + * + * Copyright 2017 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.jingle.transports; + +/** + * Created by vanitas on 25.06.17. + */ +public abstract class JingleTransportInitiationException extends Exception { + private static final long serialVersionUID = 1L; + + + public static class ProxyError extends JingleTransportInitiationException { + private static final long serialVersionUID = 1L; + } + + public static class CandidateError extends JingleTransportInitiationException { + private static final long serialVersionUID = 1L; + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java new file mode 100644 index 000000000..75d8797b0 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java @@ -0,0 +1,74 @@ +/** + * + * Copyright 2017 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.jingle.transports; + +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; + +/** + * Manager for a JingleTransport method. + * @param JingleContentTransport. + */ +public abstract class JingleTransportManager implements ConnectionListener { + + private final XMPPConnection connection; + + public JingleTransportManager(XMPPConnection connection) { + this.connection = connection; + connection.addConnectionListener(this); + } + + public XMPPConnection getConnection() { + return connection; + } + + public abstract String getNamespace(); + + public abstract JingleTransportSession transportSession(JingleSession jingleSession); + + + @Override + public void connected(XMPPConnection connection) { + } + + @Override + public void connectionClosed() { + + } + + @Override + public void connectionClosedOnError(Exception e) { + + } + + @Override + public void reconnectionSuccessful() { + + } + + @Override + public void reconnectingIn(int seconds) { + + } + + @Override + public void reconnectionFailed(Exception e) { + + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportSession.java new file mode 100644 index 000000000..fab36419d --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportSession.java @@ -0,0 +1,62 @@ +/** + * + * Copyright 2017 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.jingle.transports; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; + +/** + * Created by vanitas on 20.06.17. + */ +public abstract class JingleTransportSession { + protected final JingleSession jingleSession; + protected T ourProposal, theirProposal; + + public JingleTransportSession(JingleSession session) { + this.jingleSession = session; + } + + public abstract T createTransport(); + + public void processJingle(Jingle jingle) { + if (jingle.getContents().size() == 0) { + return; + } + + JingleContent content = jingle.getContents().get(0); + JingleContentTransport t = content.getJingleTransport(); + + if (t != null && t.getNamespace().equals(getNamespace())) { + setTheirProposal(t); + } + } + + public abstract void setTheirProposal(JingleContentTransport transport); + + public abstract void initiateOutgoingSession(JingleTransportInitiationCallback callback); + + public abstract void initiateIncomingSession(JingleTransportInitiationCallback callback); + + public abstract String getNamespace(); + + public abstract IQ handleTransportInfo(Jingle transportInfo); + + public abstract JingleTransportManager transportManager(); +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportManager.java new file mode 100644 index 000000000..1c52512ec --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportManager.java @@ -0,0 +1,64 @@ +/** + * + * Copyright 2017 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.jingle.transports.jingle_ibb; + +import java.util.WeakHashMap; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; +import org.jivesoftware.smackx.jingle.transports.JingleTransportManager; +import org.jivesoftware.smackx.jingle.transports.JingleTransportSession; +import org.jivesoftware.smackx.jingle.transports.jingle_ibb.element.JingleIBBTransport; +import org.jivesoftware.smackx.jingle.transports.jingle_ibb.provider.JingleIBBTransportProvider; + +/** + * Manager for Jingle InBandBytestream transports (XEP-0261). + */ +public final class JingleIBBTransportManager extends JingleTransportManager { + + private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + + private JingleIBBTransportManager(XMPPConnection connection) { + super(connection); + JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleIBBTransportProvider()); + } + + public static JingleIBBTransportManager getInstanceFor(XMPPConnection connection) { + JingleIBBTransportManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new JingleIBBTransportManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + @Override + public String getNamespace() { + return JingleIBBTransport.NAMESPACE_V1; + } + + @Override + public JingleTransportSession transportSession(JingleSession jingleSession) { + return new JingleIBBTransportSession(jingleSession); + } + + @Override + public void authenticated(XMPPConnection connection, boolean resumed) { + //Nothing to do. + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportSession.java new file mode 100644 index 000000000..61efc9e1a --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportSession.java @@ -0,0 +1,115 @@ +/** + * + * Copyright 2017 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.jingle.transports.jingle_ibb; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.BytestreamRequest; +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.transports.JingleTransportInitiationCallback; +import org.jivesoftware.smackx.jingle.transports.JingleTransportManager; +import org.jivesoftware.smackx.jingle.transports.JingleTransportSession; +import org.jivesoftware.smackx.jingle.transports.jingle_ibb.element.JingleIBBTransport; + +public class JingleIBBTransportSession extends JingleTransportSession { + private static final Logger LOGGER = Logger.getLogger(JingleIBBTransportSession.class.getName()); + + private final JingleIBBTransportManager transportManager; + + public JingleIBBTransportSession(JingleSession session) { + super(session); + transportManager = JingleIBBTransportManager.getInstanceFor(session.getConnection()); + } + + @Override + public JingleIBBTransport createTransport() { + + if (theirProposal == null) { + return new JingleIBBTransport(); + } else { + return new JingleIBBTransport(theirProposal.getBlockSize(), theirProposal.getSessionId()); + } + + } + + @Override + public void setTheirProposal(JingleContentTransport transport) { + theirProposal = (JingleIBBTransport) transport; + } + + @Override + public void initiateOutgoingSession(JingleTransportInitiationCallback callback) { + LOGGER.log(Level.INFO, "Initiate Jingle InBandBytestream session."); + + BytestreamSession session; + try { + session = InBandBytestreamManager.getByteStreamManager(jingleSession.getConnection()) + .establishSession(jingleSession.getRemote(), theirProposal.getSessionId()); + callback.onSessionInitiated(session); + } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { + callback.onException(e); + } + } + + @Override + public void initiateIncomingSession(final JingleTransportInitiationCallback callback) { + LOGGER.log(Level.INFO, "Await Jingle InBandBytestream session."); + + InBandBytestreamManager.getByteStreamManager(jingleSession.getConnection()).addIncomingBytestreamListener(new BytestreamListener() { + @Override + public void incomingBytestreamRequest(BytestreamRequest request) { + if (request.getFrom().asFullJidIfPossible().equals(jingleSession.getRemote()) + && request.getSessionID().equals(theirProposal.getSessionId())) { + BytestreamSession session; + + try { + session = request.accept(); + } catch (InterruptedException | SmackException | XMPPException.XMPPErrorException e) { + callback.onException(e); + return; + } + callback.onSessionInitiated(session); + } + } + }); + } + + @Override + public String getNamespace() { + return transportManager.getNamespace(); + } + + @Override + public IQ handleTransportInfo(Jingle transportInfo) { + return IQ.createResultIQ(transportInfo); + //TODO + } + + @Override + public JingleTransportManager transportManager() { + return JingleIBBTransportManager.getInstanceFor(jingleSession.getConnection()); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java new file mode 100644 index 000000000..2274aaac7 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java @@ -0,0 +1,234 @@ +/** + * + * Copyright 2017 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.jingle.transports.jingle_s5b; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleAction; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; +import org.jivesoftware.smackx.jingle.transports.JingleTransportManager; +import org.jivesoftware.smackx.jingle.transports.JingleTransportSession; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.provider.JingleS5BTransportProvider; + +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.Jid; + +/** + * Manager for Jingle SOCKS5 Bytestream transports (XEP-0261). + */ +public final class JingleS5BTransportManager extends JingleTransportManager { + + private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportManager.class.getName()); + + private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + + private List localStreamHosts = null; + private List availableStreamHosts = null; + + private static boolean useLocalCandidates = true; + private static boolean useExternalCandidates = true; + + private JingleS5BTransportManager(XMPPConnection connection) { + super(connection); + JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider()); + } + + public static JingleS5BTransportManager getInstanceFor(XMPPConnection connection) { + JingleS5BTransportManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new JingleS5BTransportManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + @Override + public String getNamespace() { + return JingleS5BTransport.NAMESPACE_V1; + } + + @Override + public JingleTransportSession transportSession(JingleSession jingleSession) { + return new JingleS5BTransportSession(jingleSession); + } + + private List queryAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { + Socks5BytestreamManager s5m = Socks5BytestreamManager.getBytestreamManager(getConnection()); + List proxies = s5m.determineProxies(); + return determineStreamHostInfo(proxies); + } + + private List queryLocalStreamHosts() { + return Socks5BytestreamManager.getBytestreamManager(getConnection()) + .getLocalStreamHost(); + } + + public List getAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { + if (availableStreamHosts == null) { + availableStreamHosts = queryAvailableStreamHosts(); + } + return availableStreamHosts; + } + + public List getLocalStreamHosts() { + if (localStreamHosts == null) { + localStreamHosts = queryLocalStreamHosts(); + } + return localStreamHosts; + } + + public List determineStreamHostInfo(List proxies) { + XMPPConnection connection = getConnection(); + List streamHosts = new ArrayList<>(); + + Iterator iterator = proxies.iterator(); + while (iterator.hasNext()) { + Jid proxy = iterator.next(); + Bytestream request = new Bytestream(); + request.setType(IQ.Type.get); + request.setTo(proxy); + try { + Bytestream response = connection.createStanzaCollectorAndSend(request).nextResultOrThrow(); + streamHosts.addAll(response.getStreamHosts()); + } + catch (Exception e) { + iterator.remove(); + } + } + + return streamHosts; + } + + + @Override + public void authenticated(XMPPConnection connection, boolean resumed) { + if (!resumed) try { + Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); + if (!socks5Proxy.isRunning()) { + socks5Proxy.start(); + } + localStreamHosts = queryLocalStreamHosts(); + availableStreamHosts = queryAvailableStreamHosts(); + } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { + LOGGER.log(Level.WARNING, "Could not query available StreamHosts: " + e, e); + } + } + + public Jingle createCandidateUsed(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Senders contentSenders, + JingleContent.Creator contentCreator, String contentName, String streamId, + String candidateId) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setName(contentName).setCreator(contentCreator).setSenders(contentSenders); + + JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); + tb.setCandidateUsed(candidateId).setStreamId(streamId); + + Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); + jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); + jingle.setTo(recipient); + + return jingle; + } + + public Jingle createCandidateError(FullJid remote, FullJid initiator, String sessionId, JingleContent.Senders senders, JingleContent.Creator creator, String name, String streamId) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setName(name).setCreator(creator).setSenders(senders); + + JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); + tb.setCandidateError().setStreamId(streamId); + + Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); + jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); + jingle.setTo(remote); + + return jingle; + } + + public Jingle createProxyError(FullJid remote, FullJid initiator, String sessionId, + JingleContent.Senders senders, JingleContent.Creator creator, + String name, String streamId) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setSessionId(sessionId).setAction(JingleAction.transport_info).setInitiator(initiator); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setSenders(senders).setCreator(creator).setName(name); + + JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); + tb.setStreamId(sessionId).setProxyError().setStreamId(streamId); + + Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); + jingle.setTo(remote); + jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); + return jingle; + } + + public Jingle createCandidateActivated(FullJid remote, FullJid initiator, String sessionId, + JingleContent.Senders senders, JingleContent.Creator creator, + String name, String streamId, String candidateId) { + Jingle.Builder jb = Jingle.getBuilder(); + jb.setInitiator(initiator).setSessionId(sessionId).setAction(JingleAction.transport_info); + + JingleContent.Builder cb = JingleContent.getBuilder(); + cb.setName(name).setCreator(creator).setSenders(senders); + + JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); + tb.setStreamId(streamId).setCandidateActivated(candidateId); + + Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); + jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); + jingle.setTo(remote); + return jingle; + } + + public static void setUseLocalCandidates(boolean localCandidates) { + JingleS5BTransportManager.useLocalCandidates = localCandidates; + } + + public static void setUseExternalCandidates(boolean externalCandidates) { + JingleS5BTransportManager.useExternalCandidates = externalCandidates; + } + + public static boolean isUseLocalCandidates() { + return useLocalCandidates; + } + + public static boolean isUseExternalCandidates() { + return useExternalCandidates; + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportSession.java new file mode 100644 index 000000000..ba03e1d58 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportSession.java @@ -0,0 +1,361 @@ +/** + * + * Copyright 2017 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.jingle.transports.jingle_s5b; + +import java.io.IOException; +import java.net.Socket; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Client; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5ClientForInitiator; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Utils; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.jingle.JingleManager; +import org.jivesoftware.smackx.jingle.JingleSession; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidate; +import org.jivesoftware.smackx.jingle.transports.JingleTransportInitiationCallback; +import org.jivesoftware.smackx.jingle.transports.JingleTransportSession; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransportCandidate; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransportInfo; + +/** + * Handler that handles Jingle Socks5Bytestream transports (XEP-0260). + */ +public class JingleS5BTransportSession extends JingleTransportSession { + private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportSession.class.getName()); + + private JingleTransportInitiationCallback callback; + + public JingleS5BTransportSession(JingleSession jingleSession) { + super(jingleSession); + } + + private UsedCandidate ourChoice, theirChoice; + + @Override + public JingleS5BTransport createTransport() { + if (ourProposal == null) { + ourProposal = createTransport(JingleManager.randomId(), Bytestream.Mode.tcp); + } + return ourProposal; + } + + @Override + public void setTheirProposal(JingleContentTransport transport) { + theirProposal = (JingleS5BTransport) transport; + } + + public JingleS5BTransport createTransport(String sid, Bytestream.Mode mode) { + JingleS5BTransport.Builder jb = JingleS5BTransport.getBuilder() + .setStreamId(sid).setMode(mode).setDestinationAddress( + Socks5Utils.createDigest(sid, jingleSession.getLocal(), jingleSession.getRemote())); + + //Local host + if (JingleS5BTransportManager.isUseLocalCandidates()) { + for (Bytestream.StreamHost host : transportManager().getLocalStreamHosts()) { + jb.addTransportCandidate(new JingleS5BTransportCandidate(host, 100, JingleS5BTransportCandidate.Type.direct)); + } + } + + List remoteHosts = Collections.emptyList(); + if (JingleS5BTransportManager.isUseExternalCandidates()) { + try { + remoteHosts = transportManager().getAvailableStreamHosts(); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + LOGGER.log(Level.WARNING, "Could not determine available StreamHosts.", e); + } + } + + for (Bytestream.StreamHost host : remoteHosts) { + jb.addTransportCandidate(new JingleS5BTransportCandidate(host, 0, JingleS5BTransportCandidate.Type.proxy)); + } + + return jb.build(); + } + + public void setTheirTransport(JingleContentTransport transport) { + theirProposal = (JingleS5BTransport) transport; + } + + @Override + public void initiateOutgoingSession(JingleTransportInitiationCallback callback) { + this.callback = callback; + initiateSession(); + } + + @Override + public void initiateIncomingSession(JingleTransportInitiationCallback callback) { + this.callback = callback; + initiateSession(); + } + + private void initiateSession() { + Socks5Proxy.getSocks5Proxy().addTransfer(createTransport().getDestinationAddress()); + JingleContent content = jingleSession.getContents().get(0); + UsedCandidate usedCandidate = chooseFromProposedCandidates(theirProposal); + if (usedCandidate == null) { + ourChoice = CANDIDATE_FAILURE; + Jingle candidateError = transportManager().createCandidateError( + jingleSession.getRemote(), jingleSession.getInitiator(), jingleSession.getSessionId(), + content.getSenders(), content.getCreator(), content.getName(), theirProposal.getStreamId()); + try { + jingleSession.getConnection().sendStanza(candidateError); + } catch (SmackException.NotConnectedException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Could not send candidate-error.", e); + } + } else { + ourChoice = usedCandidate; + Jingle jingle = transportManager().createCandidateUsed(jingleSession.getRemote(), jingleSession.getInitiator(), jingleSession.getSessionId(), + content.getSenders(), content.getCreator(), content.getName(), theirProposal.getStreamId(), ourChoice.candidate.getCandidateId()); + try { + jingleSession.getConnection().createStanzaCollectorAndSend(jingle) + .nextResultOrThrow(); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + LOGGER.log(Level.WARNING, "Could not send candidate-used.", e); + } + } + connectIfReady(); + } + + private UsedCandidate chooseFromProposedCandidates(JingleS5BTransport proposal) { + for (JingleContentTransportCandidate c : proposal.getCandidates()) { + JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) c; + + try { + return connectToTheirCandidate(candidate); + } catch (InterruptedException | TimeoutException | XMPPException | SmackException | IOException e) { + LOGGER.log(Level.WARNING, "Could not connect to " + candidate.getHost(), e); + } + } + LOGGER.log(Level.WARNING, "Failed to connect to any candidate."); + return null; + } + + private UsedCandidate connectToTheirCandidate(JingleS5BTransportCandidate candidate) + throws InterruptedException, TimeoutException, SmackException, XMPPException, IOException { + Bytestream.StreamHost streamHost = candidate.getStreamHost(); + String address = streamHost.getAddress(); + Socks5Client socks5Client = new Socks5Client(streamHost, theirProposal.getDestinationAddress()); + Socket socket = socks5Client.getSocket(10 * 1000); + LOGGER.log(Level.INFO, "Connected to their StreamHost " + address + " using dstAddr " + + theirProposal.getDestinationAddress()); + return new UsedCandidate(theirProposal, candidate, socket); + } + + private UsedCandidate connectToOurCandidate(JingleS5BTransportCandidate candidate) + throws InterruptedException, TimeoutException, SmackException, XMPPException, IOException { + Bytestream.StreamHost streamHost = candidate.getStreamHost(); + String address = streamHost.getAddress(); + Socks5ClientForInitiator socks5Client = new Socks5ClientForInitiator( + streamHost, ourProposal.getDestinationAddress(), jingleSession.getConnection(), + ourProposal.getStreamId(), jingleSession.getRemote()); + Socket socket = socks5Client.getSocket(10 * 1000); + LOGGER.log(Level.INFO, "Connected to our StreamHost " + address + " using dstAddr " + + ourProposal.getDestinationAddress()); + return new UsedCandidate(ourProposal, candidate, socket); + } + + @Override + public String getNamespace() { + return JingleS5BTransport.NAMESPACE_V1; + } + + @Override + public IQ handleTransportInfo(Jingle transportInfo) { + JingleS5BTransportInfo info = (JingleS5BTransportInfo) transportInfo.getContents().get(0).getJingleTransport().getInfo(); + + switch (info.getElementName()) { + case JingleS5BTransportInfo.CandidateUsed.ELEMENT: + return handleCandidateUsed(transportInfo); + + case JingleS5BTransportInfo.CandidateActivated.ELEMENT: + return handleCandidateActivate(transportInfo); + + case JingleS5BTransportInfo.CandidateError.ELEMENT: + return handleCandidateError(transportInfo); + + case JingleS5BTransportInfo.ProxyError.ELEMENT: + return handleProxyError(transportInfo); + } + //We should never go here, but lets be gracious... + return IQ.createResultIQ(transportInfo); + } + + public IQ handleCandidateUsed(Jingle jingle) { + JingleS5BTransportInfo info = (JingleS5BTransportInfo) jingle.getContents().get(0).getJingleTransport().getInfo(); + String candidateId = ((JingleS5BTransportInfo.CandidateUsed) info).getCandidateId(); + theirChoice = new UsedCandidate(ourProposal, ourProposal.getCandidate(candidateId), null); + + if (theirChoice.candidate == null) { + /* + TODO: Booooooh illegal candidateId!! Go home!!!!11elf + */ + } + + connectIfReady(); + + return IQ.createResultIQ(jingle); + } + + public IQ handleCandidateActivate(Jingle jingle) { + LOGGER.log(Level.INFO, "handleCandidateActivate"); + Socks5BytestreamSession bs = new Socks5BytestreamSession(ourChoice.socket, + ourChoice.candidate.getJid().asBareJid().equals(jingleSession.getRemote().asBareJid())); + callback.onSessionInitiated(bs); + return IQ.createResultIQ(jingle); + } + + public IQ handleCandidateError(Jingle jingle) { + theirChoice = CANDIDATE_FAILURE; + connectIfReady(); + return IQ.createResultIQ(jingle); + } + + public IQ handleProxyError(Jingle jingle) { + //TODO + return IQ.createResultIQ(jingle); + } + + /** + * Determine, which candidate (ours/theirs) is the nominated one. + * Connect to this candidate. If it is a proxy and it is ours, activate it and connect. + * If its a proxy and it is theirs, wait for activation. + * If it is not a proxy, just connect. + */ + private void connectIfReady() { + JingleContent content = jingleSession.getContents().get(0); + if (ourChoice == null || theirChoice == null) { + // Not yet ready. + LOGGER.log(Level.INFO, "Not ready."); + return; + } + + if (ourChoice == CANDIDATE_FAILURE && theirChoice == CANDIDATE_FAILURE) { + LOGGER.log(Level.INFO, "Failure."); + jingleSession.onTransportMethodFailed(getNamespace()); + return; + } + + LOGGER.log(Level.INFO, "Ready."); + + //Determine nominated candidate. + UsedCandidate nominated; + if (ourChoice != CANDIDATE_FAILURE && theirChoice != CANDIDATE_FAILURE) { + if (ourChoice.candidate.getPriority() > theirChoice.candidate.getPriority()) { + nominated = ourChoice; + } else if (ourChoice.candidate.getPriority() < theirChoice.candidate.getPriority()) { + nominated = theirChoice; + } else { + nominated = jingleSession.isInitiator() ? ourChoice : theirChoice; + } + } else if (ourChoice != CANDIDATE_FAILURE) { + nominated = ourChoice; + } else { + nominated = theirChoice; + } + + if (nominated == theirChoice) { + LOGGER.log(Level.INFO, "Their choice, so our proposed candidate is used."); + boolean isProxy = nominated.candidate.getType() == JingleS5BTransportCandidate.Type.proxy; + try { + nominated = connectToOurCandidate(nominated.candidate); + } catch (InterruptedException | IOException | XMPPException | SmackException | TimeoutException e) { + LOGGER.log(Level.INFO, "Could not connect to our candidate.", e); + //TODO: Proxy-Error + return; + } + + if (isProxy) { + LOGGER.log(Level.INFO, "Is external proxy. Activate it."); + Bytestream activate = new Bytestream(ourProposal.getStreamId()); + activate.setMode(null); + activate.setType(IQ.Type.set); + activate.setTo(nominated.candidate.getJid()); + activate.setToActivate(jingleSession.getRemote()); + activate.setFrom(jingleSession.getLocal()); + try { + jingleSession.getConnection().createStanzaCollectorAndSend(activate).nextResultOrThrow(); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + LOGGER.log(Level.WARNING, "Could not activate proxy.", e); + return; + } + + LOGGER.log(Level.INFO, "Send candidate-activate."); + Jingle candidateActivate = transportManager().createCandidateActivated( + jingleSession.getRemote(), jingleSession.getInitiator(), jingleSession.getSessionId(), + content.getSenders(), content.getCreator(), content.getName(), nominated.transport.getStreamId(), + nominated.candidate.getCandidateId()); + try { + jingleSession.getConnection().createStanzaCollectorAndSend(candidateActivate) + .nextResultOrThrow(); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + LOGGER.log(Level.WARNING, "Could not send candidate-activated", e); + return; + } + } + + LOGGER.log(Level.INFO, "Start transmission."); + Socks5BytestreamSession bs = new Socks5BytestreamSession(nominated.socket, !isProxy); + callback.onSessionInitiated(bs); + + } + //Our choice + else { + LOGGER.log(Level.INFO, "Our choice, so their candidate was used."); + boolean isProxy = nominated.candidate.getType() == JingleS5BTransportCandidate.Type.proxy; + if (!isProxy) { + LOGGER.log(Level.INFO, "Direct connection."); + Socks5BytestreamSession bs = new Socks5BytestreamSession(nominated.socket, true); + callback.onSessionInitiated(bs); + } else { + LOGGER.log(Level.INFO, "Our choice was their external proxy. wait for candidate-activate."); + } + } + } + + @Override + public JingleS5BTransportManager transportManager() { + return JingleS5BTransportManager.getInstanceFor(jingleSession.getConnection()); + } + + private static class UsedCandidate { + private final Socket socket; + private final JingleS5BTransport transport; + private final JingleS5BTransportCandidate candidate; + + public UsedCandidate(JingleS5BTransport transport, JingleS5BTransportCandidate candidate, Socket socket) { + this.socket = socket; + this.transport = transport; + this.candidate = candidate; + } + } + + private static final UsedCandidate CANDIDATE_FAILURE = new UsedCandidate(null, null, null); +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransport.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransport.java index 3d1a3759d..a70b5d9a6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransport.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransport.java @@ -93,7 +93,7 @@ public class JingleS5BTransport extends JingleContentTransport { private String streamId; private String dstAddr; private Bytestream.Mode mode; - private ArrayList candidates = new ArrayList<>(); + private final ArrayList candidates = new ArrayList<>(); private JingleContentTransportInfo info; public Builder setStreamId(String sid) { @@ -112,11 +112,22 @@ public class JingleS5BTransport extends JingleContentTransport { } public Builder addTransportCandidate(JingleS5BTransportCandidate candidate) { + if (info != null) { + throw new IllegalStateException("Builder has already an info set. " + + "The transport can only have either an info or transport candidates, not both."); + } this.candidates.add(candidate); return this; } public Builder setTransportInfo(JingleContentTransportInfo info) { + if (!candidates.isEmpty()) { + throw new IllegalStateException("Builder has already at least one candidate set. " + + "The transport can only have either an info or transport candidates, not both."); + } + if (this.info != null) { + throw new IllegalStateException("Builder has already an info set."); + } this.info = info; return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransportCandidate.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransportCandidate.java index 284fc72e9..b16e5f563 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransportCandidate.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/elements/JingleS5BTransportCandidate.java @@ -54,8 +54,12 @@ public final class JingleS5BTransportCandidate extends JingleContentTransportCan Objects.requireNonNull(candidateId); Objects.requireNonNull(host); Objects.requireNonNull(jid); + if (priority < 0) { - throw new IllegalArgumentException("Priority MUST be present and NOT less than 0."); + throw new IllegalArgumentException("Priority MUST NOT be less than 0."); + } + if (port < 0) { + throw new IllegalArgumentException("Port MUST NOT be less than 0."); } this.cid = candidateId; @@ -66,8 +70,8 @@ public final class JingleS5BTransportCandidate extends JingleContentTransportCan this.type = type; } - public JingleS5BTransportCandidate(Bytestream.StreamHost streamHost, int priority) { - this(StringUtils.randomString(24), streamHost.getAddress(), streamHost.getJID(), streamHost.getPort(), priority, Type.proxy); + public JingleS5BTransportCandidate(Bytestream.StreamHost streamHost, int priority, Type type) { + this(StringUtils.randomString(24), streamHost.getAddress(), streamHost.getJID(), streamHost.getPort(), priority, type); } public enum Type { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/package-info.java new file mode 100644 index 000000000..9cfe634a5 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2017 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-0261: Jingle In-Band Bytestreams. + */ +package org.jivesoftware.smackx.jingle.transports; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java index a5b0859b8..a4dd53bb6 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java @@ -102,10 +102,15 @@ public class Socks5ProxyTest { @Test public void shouldPreserveAddressOrderOnInsertions() { Socks5Proxy proxy = Socks5Proxy.getSocks5Proxy(); - List addresses = new ArrayList(proxy.getLocalAddresses()); - addresses.add("1"); - addresses.add("2"); - addresses.add("3"); + List addresses = new ArrayList<>(proxy.getLocalAddresses()); + + for (int i = 1 ; i <= 3; i++) { + String addr = Integer.toString(i); + if (!addresses.contains(addr)) { + addresses.add(addr); + } + } + for (String address : addresses) { proxy.addLocalAddress(address); } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentProviderManagerTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentProviderManagerTest.java new file mode 100644 index 000000000..8b9109426 --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentProviderManagerTest.java @@ -0,0 +1,50 @@ +/** + * + * Copyright 2017 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.jingle; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; +import org.jivesoftware.smackx.jingle.transports.jingle_ibb.element.JingleIBBTransport; +import org.jivesoftware.smackx.jingle.transports.jingle_ibb.provider.JingleIBBTransportProvider; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; +import org.jivesoftware.smackx.jingle.transports.jingle_s5b.provider.JingleS5BTransportProvider; + +import org.junit.Test; + +/** + * Tests for the JingleContentProviderManager. + */ +public class JingleContentProviderManagerTest extends SmackTestSuite { + + @Test + public void transportProviderTest() { + assertNull(JingleContentProviderManager.getJingleContentTransportProvider(JingleIBBTransport.NAMESPACE_V1)); + assertNull(JingleContentProviderManager.getJingleContentTransportProvider(JingleS5BTransport.NAMESPACE_V1)); + + JingleIBBTransportProvider ibbProvider = new JingleIBBTransportProvider(); + JingleContentProviderManager.addJingleContentTransportProvider(JingleIBBTransport.NAMESPACE_V1, ibbProvider); + assertEquals(ibbProvider, JingleContentProviderManager.getJingleContentTransportProvider(JingleIBBTransport.NAMESPACE_V1)); + + assertNull(JingleContentProviderManager.getJingleContentTransportProvider(JingleS5BTransport.NAMESPACE_V1)); + JingleS5BTransportProvider s5bProvider = new JingleS5BTransportProvider(); + JingleContentProviderManager.addJingleContentTransportProvider(JingleS5BTransport.NAMESPACE_V1, s5bProvider); + assertEquals(s5bProvider, JingleContentProviderManager.getJingleContentTransportProvider(JingleS5BTransport.NAMESPACE_V1)); + } +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java index 49bd0fde4..bd1a430a8 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java @@ -22,7 +22,6 @@ import static junit.framework.TestCase.assertNotSame; import static junit.framework.TestCase.assertNull; import org.jivesoftware.smack.test.util.SmackTestSuite; - import org.jivesoftware.smackx.jingle.element.JingleContent; import org.junit.Test; @@ -46,7 +45,7 @@ public class JingleContentTest extends SmackTestSuite { } @Test - public void parserTest() { + public void parserTest() throws Exception { JingleContent.Builder builder = JingleContent.getBuilder(); diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleErrorTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleErrorTest.java index d21ecf73d..6ca6d88a9 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleErrorTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleErrorTest.java @@ -19,8 +19,9 @@ package org.jivesoftware.smackx.jingle; import static junit.framework.TestCase.assertEquals; import org.jivesoftware.smack.test.util.SmackTestSuite; - +import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smackx.jingle.element.JingleError; +import org.jivesoftware.smackx.jingle.provider.JingleErrorProvider; import org.junit.Test; @@ -30,20 +31,37 @@ import org.junit.Test; public class JingleErrorTest extends SmackTestSuite { @Test - public void parserTest() { - assertEquals("", - JingleError.fromString("out-of-order").toXML().toString()); - assertEquals("", - JingleError.fromString("tie-break").toXML().toString()); - assertEquals("", - JingleError.fromString("unknown-session").toXML().toString()); - assertEquals("", - JingleError.fromString("unsupported-info").toXML().toString()); - assertEquals("unknown-session", JingleError.fromString("unknown-session").getMessage()); + public void tieBreakTest() throws Exception { + String xml = ""; + JingleError error = new JingleErrorProvider().parse(TestUtils.getParser(xml)); + assertEquals(xml, error.toXML().toString()); + } + + @Test + public void unknownSessionTest() throws Exception { + String xml = ""; + JingleError error = new JingleErrorProvider().parse(TestUtils.getParser(xml)); + assertEquals(xml, error.toXML().toString()); + } + + @Test + public void unsupportedInfoTest() throws Exception { + String xml = ""; + JingleError error = new JingleErrorProvider().parse(TestUtils.getParser(xml)); + assertEquals(xml, error.toXML().toString()); + } + + @Test + public void outOfOrderTest() throws Exception { + String xml = ""; + JingleError error = new JingleErrorProvider().parse(TestUtils.getParser(xml)); + assertEquals(xml, error.toXML().toString()); } @Test(expected = IllegalArgumentException.class) public void illegalArgumentTest() { JingleError.fromString("inexistent-error"); } + + } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java deleted file mode 100644 index 02acf9b5e..000000000 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleSessionTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * - * Copyright 2017 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.jingle; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNotSame; - -import org.jivesoftware.smack.test.util.SmackTestSuite; -import org.jivesoftware.smack.util.StringUtils; - -import org.junit.Test; -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.stringprep.XmppStringprepException; - -/** - * Test JingleSession class. - */ -public class JingleSessionTest extends SmackTestSuite { - - @Test - public void sessionTest() throws XmppStringprepException { - Jid romeo = JidCreate.from("romeo@montague.lit"); - Jid juliet = JidCreate.from("juliet@capulet.lit"); - String sid = StringUtils.randomString(24); - - JingleSession s1 = new JingleSession(romeo, juliet, sid); - JingleSession s2 = new JingleSession(juliet, romeo, sid); - JingleSession s3 = new JingleSession(romeo, juliet, StringUtils.randomString(23)); - JingleSession s4 = new JingleSession(juliet, romeo, sid); - - assertNotSame(s1, s2); - assertNotSame(s1, s3); - assertNotSame(s2, s3); - assertEquals(s2, s4); - assertEquals(s2.hashCode(), s4.hashCode()); - } -} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleUtilTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleUtilTest.java new file mode 100644 index 000000000..500ee2279 --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleUtilTest.java @@ -0,0 +1,102 @@ +/** + * + * Copyright 2017 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.jingle; + +import org.jivesoftware.smack.DummyConnection; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; + +import org.junit.Before; +import org.junit.Test; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; + +/** + * Test the JingleUtil class. + */ +public class JingleUtilTest extends SmackTestSuite { + + private XMPPConnection connection; + private JingleUtil jutil; + + @Before + public void setup() { + connection = new DummyConnection( + DummyConnection.getDummyConfigurationBuilder() + .setUsernameAndPassword("romeo@montague.lit", + "iluvJulibabe13").build()); + jutil = new JingleUtil(connection); + } + + @Test + public void sessionInitiateTest() throws XmppStringprepException { + FullJid romeo = connection.getUser().asFullJidOrThrow(); + FullJid juliet = JidCreate.fullFrom("juliet@capulet.example/yn0cl4bnw0yr3vym"); + + String sid = "851ba2"; + String contentName = "a-file-offer"; + Jingle jingle = jutil.createSessionInitiate(juliet, sid, + JingleContent.Creator.initiator, contentName, JingleContent.Senders.initiator, null, null); + + + + String expected = + "" + + "" + + "" + + "" + + "" + + "1969-07-21T02:56:15Z" + + "This is a test. If this were a real file..." + + "text/plain" + + "test.txt" + + "" + + "6144" + + "w0mcJylzCn+AfvuGdqkty2+KP48=" + + "" + + "" + + " " + + "" + + "" + + "" + + "" + + "" + + ""; + } +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportTest.java index 70e0411b3..e33b2b814 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_ibb/JingleIBBTransportTest.java @@ -69,5 +69,8 @@ public class JingleIBBTransportTest extends SmackTestSuite { assertEquals(transport3.getNamespace(), JingleIBBTransport.NAMESPACE_V1); assertEquals(transport3.getElementName(), "transport"); + + JingleIBBTransport transport4 = new JingleIBBTransport("session-id"); + assertEquals(JingleIBBTransport.DEFAULT_BLOCK_SIZE, transport4.getBlockSize()); } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportTest.java index 33e616efd..1df9a9410 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportTest.java @@ -16,11 +16,14 @@ */ package org.jivesoftware.smackx.jingle.transports.jingle_s5b; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static junit.framework.TestCase.assertTrue; import org.jivesoftware.smack.test.util.SmackTestSuite; + import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; @@ -29,7 +32,9 @@ import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTr import org.jivesoftware.smackx.jingle.transports.jingle_s5b.provider.JingleS5BTransportProvider; import org.junit.Test; +import org.jxmpp.jid.FullJid; import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; /** * Test Provider and serialization. @@ -76,8 +81,13 @@ public class JingleS5BTransportTest extends SmackTestSuite { assertEquals(Bytestream.Mode.tcp, transport.getMode()); assertEquals(3, transport.getCandidates().size()); + assertTrue(transport.hasCandidate("hft54dqy")); + assertFalse(transport.hasCandidate("invalidId")); JingleS5BTransportCandidate candidate1 = (JingleS5BTransportCandidate) transport.getCandidates().get(0); + assertEquals(candidate1, transport.getCandidate("hft54dqy")); + assertNotNull(candidate1.getStreamHost()); + assertEquals(JingleS5BTransportCandidate.Type.direct.getWeight(), candidate1.getType().getWeight()); assertEquals("hft54dqy", candidate1.getCandidateId()); assertEquals("192.168.4.1", candidate1.getHost()); assertEquals(JidCreate.from("romeo@montague.lit/orchard"), candidate1.getJid()); @@ -128,6 +138,7 @@ public class JingleS5BTransportTest extends SmackTestSuite { JingleS5BTransport proxyErrorTransport = new JingleS5BTransportProvider() .parse(TestUtils.getParser(proxyError)); assertNull(proxyErrorTransport.getDestinationAddress()); + assertNotNull(proxyErrorTransport.getInfo()); assertNotNull(candidateErrorTransport.getInfo()); assertEquals("vj3hs98y", proxyErrorTransport.getStreamId()); assertEquals(JingleS5BTransportInfo.ProxyError(), @@ -140,7 +151,7 @@ public class JingleS5BTransportTest extends SmackTestSuite { ""; JingleS5BTransport candidateUsedTransport = new JingleS5BTransportProvider() .parse(TestUtils.getParser(candidateUsed)); - assertNotNull(candidateErrorTransport.getInfo()); + assertNotNull(candidateUsedTransport.getInfo()); assertEquals(JingleS5BTransportInfo.CandidateUsed("hr65dqyd"), candidateUsedTransport.getInfo()); assertEquals("hr65dqyd", @@ -154,7 +165,9 @@ public class JingleS5BTransportTest extends SmackTestSuite { ""; JingleS5BTransport candidateActivatedTransport = new JingleS5BTransportProvider() .parse(TestUtils.getParser(candidateActivated)); + assertNotNull(candidateActivatedTransport.getInfo()); assertNotNull(candidateErrorTransport.getInfo()); + assertEquals(JingleS5BTransportInfo.CandidateActivated("hr65dqyd"), candidateActivatedTransport.getInfo()); assertEquals("hr65dqyd", @@ -162,4 +175,50 @@ public class JingleS5BTransportTest extends SmackTestSuite { candidateActivatedTransport.getInfo()).getCandidateId()); assertEquals(candidateActivated, candidateActivatedTransport.toXML().toString()); } + + @Test(expected = IllegalArgumentException.class) + public void candidateBuilderInvalidPortTest() { + JingleS5BTransportCandidate.getBuilder().setPort(-5); + } + + @Test(expected = IllegalArgumentException.class) + public void candidateBuilderInvalidPriorityTest() { + JingleS5BTransportCandidate.getBuilder().setPriority(-1000); + } + + @Test(expected = IllegalArgumentException.class) + public void transportCandidateIllegalPriorityTest() throws XmppStringprepException { + FullJid jid = JidCreate.fullFrom("test@test.test/test"); + JingleS5BTransportCandidate candidate = new JingleS5BTransportCandidate( + "cid", "host", jid, 5555, -30, JingleS5BTransportCandidate.Type.proxy); + } + + @Test(expected = IllegalArgumentException.class) + public void transportCandidateIllegalPortTest() throws XmppStringprepException { + FullJid jid = JidCreate.fullFrom("test@test.test/test"); + JingleS5BTransportCandidate candidate = new JingleS5BTransportCandidate( + "cid", "host", jid, -5555, 30, JingleS5BTransportCandidate.Type.proxy); + } + + @Test + public void candidateFromStreamHostTest() throws XmppStringprepException { + FullJid jid = JidCreate.fullFrom("test@test.test/test"); + String host = "host.address"; + int port = 1234; + Bytestream.StreamHost streamHost = new Bytestream.StreamHost(jid, host, port); + + JingleS5BTransportCandidate candidate = new JingleS5BTransportCandidate(streamHost, 2000, JingleS5BTransportCandidate.Type.direct); + + assertEquals(2000, candidate.getPriority()); + assertEquals(jid, candidate.getJid()); + assertEquals(host, candidate.getHost()); + assertEquals(port, candidate.getPort()); + + assertEquals(streamHost.toXML().toString(), candidate.getStreamHost().toXML().toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void typeFromIllegalStringTest() { + JingleS5BTransportCandidate.Type.fromString("illegal-type"); + } } From 99c1c93c2ab771dc5428beb3cfb545a2bc14b3a8 Mon Sep 17 00:00:00 2001 From: vanitasvitae Date: Mon, 3 Jul 2017 17:26:57 +0200 Subject: [PATCH 3/3] Fix shouldPreserveAddressOrderOnInsertions test The test failed because the ArrayList - in contrast to the underlying Set - did not check for duplicates on insert. Under certain circumstances this lead to an index out of bounds exception because the list in the test contained duplicated entries which were not present in the set of the Socks5Proxy. I fixed the issue by only inserting the address when it was not in the list before. --- .../bytestreams/socks5/Socks5ProxyTest.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java index a4dd53bb6..1fea4c24b 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ProxyTest.java @@ -30,6 +30,8 @@ import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import org.jivesoftware.smack.util.StringUtils; @@ -39,7 +41,7 @@ import org.junit.Test; /** * Test for Socks5Proxy class. - * + * * @author Henning Staib */ public class Socks5ProxyTest { @@ -73,7 +75,7 @@ public class Socks5ProxyTest { /** * The SOCKS5 proxy should use a free port above the one configured. - * + * * @throws Exception should not happen */ @Test @@ -102,13 +104,11 @@ public class Socks5ProxyTest { @Test public void shouldPreserveAddressOrderOnInsertions() { Socks5Proxy proxy = Socks5Proxy.getSocks5Proxy(); - List addresses = new ArrayList<>(proxy.getLocalAddresses()); + + LinkedHashSet addresses = new LinkedHashSet<>(proxy.getLocalAddresses()); for (int i = 1 ; i <= 3; i++) { - String addr = Integer.toString(i); - if (!addresses.contains(addr)) { - addresses.add(addr); - } + addresses.add(Integer.toString(i)); } for (String address : addresses) { @@ -116,8 +116,10 @@ public class Socks5ProxyTest { } List localAddresses = proxy.getLocalAddresses(); + + Iterator iterator = addresses.iterator(); for (int i = 0; i < addresses.size(); i++) { - assertEquals(addresses.get(i), localAddresses.get(i)); + assertEquals(iterator.next(), localAddresses.get(i)); } } @@ -165,7 +167,7 @@ public class Socks5ProxyTest { /** * If the SOCKS5 proxy accepts a connection that is not a SOCKS5 connection it should close the * corresponding socket. - * + * * @throws Exception should not happen */ @Test @@ -196,7 +198,7 @@ public class Socks5ProxyTest { /** * The SOCKS5 proxy should reply with an error message if no supported authentication methods * are given in the SOCKS5 request. - * + * * @throws Exception should not happen */ @Test @@ -227,7 +229,7 @@ public class Socks5ProxyTest { /** * The SOCKS5 proxy should respond with an error message if the client is not allowed to connect * with the proxy. - * + * * @throws Exception should not happen */ @Test @@ -249,7 +251,7 @@ public class Socks5ProxyTest { // send valid SOCKS5 message out.write(new byte[] { (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x01, - (byte) 0xAA, (byte) 0x00, (byte) 0x00 }); + (byte) 0xAA, (byte) 0x00, (byte) 0x00 }); // verify error message assertEquals((byte) 0x05, (byte) in.read()); @@ -269,7 +271,7 @@ public class Socks5ProxyTest { /** * A Client should successfully establish a connection to the SOCKS5 proxy. - * + * * @throws Exception should not happen */ @Test @@ -297,7 +299,7 @@ public class Socks5ProxyTest { // send valid SOCKS5 message out.write(new byte[] { (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x01, - (byte) 0xAA, (byte) 0x00, (byte) 0x00 }); + (byte) 0xAA, (byte) 0x00, (byte) 0x00 }); // verify response assertEquals((byte) 0x05, (byte) in.read());