New Jingle API groundwork

A start for the new Jingle API. Since Jingle is a single IQ with many
plugable extensions, there are some particularities we need to deal
with, e.g. jingle users have to register with JingleManager.

This is untested code. There may be drangons.
This commit is contained in:
Florian Schmaus 2017-05-30 08:45:27 +02:00
parent 7a5f9e6a03
commit 6bb001d274
24 changed files with 1592 additions and 0 deletions

View File

@ -56,6 +56,7 @@ Smack Extensions and currently supported XEPs of smack-extensions
| Stream Initation | [XEP-0095](http://xmpp.org/extensions/xep-0095.html) | Initiating a data stream between any two XMPP entities. |
| [SI File Transfer](filetransfer.md) | [XEP-0096](http://xmpp.org/extensions/xep-0096.html) | Transfer files between two users over XMPP. |
| [Entity Capabilities](caps.md) | [XEP-0115](http://xmpp.org/extensions/xep-0115.html) | Broadcasting and dynamic discovery of entity capabilities. |
| [Jingle](jingle.html) | [XEP-0116](http://xmpp.org/extensions/xep-0166.html) | Initiate and manage sessions between two XMPP entities. |
| Data Forms Validation | [XEP-0122](http://xmpp.org/extensions/xep-0122.html) | Enables an application to specify additional validation guidelines . |
| Service Administration | [XEP-0133](http://xmpp.org/extensions/xep-0133.html) | Recommended best practices for service-level administration of servers and components using Ad-Hoc Commands. |
| Stream Compression | [XEP-0138](http://xmpp.org/extensions/xep-0138.html) | Support for optional compression of the XMPP stream.

View File

@ -0,0 +1,72 @@
Jingle
======
**XEP related:** [XEP-0116: Jingle](http://xmpp.org/extensions/xep-0166.html)
Jingle Element Structure
------------------------
```
jingle
│ action (REQUIRED, XEP-0166 § 7.2)
| content-accept
| content-add
| content-modify
| content-reject
| content-remove
| description-info
| security-info
| session-accept
| session-info
| session-initiate
| transport-accept
| transport-info
| transport-reject
| transport-replace
│ initator (RECOMMENDED for session initiate, NOT RECOMMENDED otherwise, full JID, XEP-0166 § 7.1)
│ responder (RECOMMENDED for session accept, NOT RECOMMENDED otherwise, full JID. XEP-0166 § 7.1)
│ sid (REQUIRED, SHOULD match XML Nmtoken production)
├── <reason/> (optional, XEP-0166 § 7.4)
│ │
│ └──(alternative─session│busy│..)
└── <content/> (one or more, XEP-0166 § 7.3)
│ creator (REQUIRED, must be one of)
| initiator
| responder
│ disposition (OPTIONAL)
│ name (REQUIRED)
│ senders (OPTIONAL, except when content-modify then REQUIRED)
| both (default)
| initiator
| none
| responder
├──description
│ │ media
│ │ xmlns
│ │
│ ├──payload─type
│ │
│ └──file (XEP─0234)
└──transport
│ xmlns
│ pwd (OPTIONAL, XEP-0176 Jingle ICE)
│ ufrag (OPTIONAL, XEP-0176 Jingle ICE)
│ mode (XEP-0234 Jingle File Transfer)
│ sid (XEP-0234 Jingle File Transfer)
└──candidate
component
foundation
generation
id
ip
network
port
priority
protocol
type
```

View File

@ -0,0 +1,26 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.element.Jingle;
public interface JingleHandler {
IQ handleRequest(Jingle jingle);
}

View File

@ -0,0 +1,136 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smackx.jingle.element.Jingle;
import org.jivesoftware.smackx.jingle.element.JingleContent;
import org.jivesoftware.smackx.jingle.element.JingleContentDescription;
import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.Jid;
public final class JingleManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(JingleManager.class.getName());
private static final Map<XMPPConnection, JingleManager> INSTANCES = new WeakHashMap<>();
public static synchronized JingleManager getInstanceFor(XMPPConnection connection) {
JingleManager jingleManager = INSTANCES.get(connection);
if (jingleManager == null) {
jingleManager = new JingleManager(connection);
INSTANCES.put(connection, jingleManager);
}
return jingleManager;
}
private final Map<String, JingleHandler> descriptionHandlers = new ConcurrentHashMap<>();
private final Map<FullJidAndSessionId, JingleSessionHandler> jingleSessionHandlers = new ConcurrentHashMap<>();
private JingleManager(XMPPConnection connection) {
super(connection);
connection.registerIQRequestHandler(
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.handleRequest(jingle, sid);
}
if (jingle.getContents().size() > 1) {
LOGGER.severe("Jingle IQs with more then one content element are currently not supported by Smack");
return null;
}
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.handleRequest(jingle);
}
});
}
public JingleHandler registerDescriptionHandler(String namespace, JingleHandler handler) {
return descriptionHandlers.put(namespace, handler);
}
public JingleSessionHandler registerJingleSessionHandler(FullJid otherJid, String sessionId, JingleSessionHandler sessionHandler) {
FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(otherJid, sessionId);
return jingleSessionHandlers.put(fullJidAndSessionId, sessionHandler);
}
public JingleSessionHandler unregisterJingleSessionhandler(FullJid otherJid, String sessionId, JingleSessionHandler sessionHandler) {
FullJidAndSessionId fullJidAndSessionId = new FullJidAndSessionId(otherJid, sessionId);
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);
}
}
}

View File

@ -0,0 +1,54 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jxmpp.jid.Jid;
// TODO: Is this class still required? If not, then remove it.
public class JingleSession {
private final Jid initiator;
private final Jid responder;
private final String sid;
public JingleSession(Jid initiator, Jid responder, String sid) {
this.initiator = initiator;
this.responder = responder;
this.sid = sid;
}
@Override
public int hashCode() {
int hashCode = 31 + initiator.hashCode();
hashCode = 31 * hashCode + responder.hashCode();
hashCode = 31 * hashCode + sid.hashCode();
return hashCode;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof JingleSession)) {
return false;
}
JingleSession otherJingleSession = (JingleSession) other;
return initiator.equals(otherJingleSession.initiator) && responder.equals(otherJingleSession.responder)
&& sid.equals(otherJingleSession.sid);
}
}

View File

@ -0,0 +1,26 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.element.Jingle;
public interface JingleSessionHandler {
IQ handleRequest(Jingle jingle, String sessionId);
}

View File

@ -0,0 +1,204 @@
/**
*
* Copyright 2003-2007 Jive Software, 2014-2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jxmpp.jid.FullJid;
/**
* The Jingle element.
*
* @author Florian Schmaus
*/
public final class Jingle extends IQ {
public static final String NAMESPACE = "urn:xmpp:jingle:1";
public static final String ACTION_ATTRIBUTE_NAME = "action";
public static final String INITIATOR_ATTRIBUTE_NAME = "initiator";
public static final String RESPONDER_ATTRIBUTE_NAME = "responder";
public static final String SESSION_ID_ATTRIBUTE_NAME = "sid";
public static final String ELEMENT = "jingle";
/**
* The session ID related to this session. The session ID is a unique identifier generated by the initiator. This
* should match the XML Nmtoken production so that XML character escaping is not needed for characters such as &.
*/
private final String sessionId;
/**
* The jingle action. This attribute is required.
*/
private final JingleAction action;
private final FullJid initiator;
private final FullJid responder;
private final JingleReason reason;
private final List<JingleContent> contents;
private Jingle(String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason,
List<JingleContent> contents) {
super(ELEMENT, NAMESPACE);
this.sessionId = StringUtils.requireNotNullOrEmpty(sessionId, "Jingle session ID must not be null");
this.action = Objects.requireNonNull(action, "Jingle action must not be null");
this.initiator = initiator;
this.responder = responder;
this.reason = reason;
if (contents != null) {
this.contents = Collections.unmodifiableList(contents);
}
else {
this.contents = Collections.emptyList();
}
}
/**
* Get the initiator. The initiator will be the full JID of the entity that has initiated the flow (which may be
* different to the "from" address in the IQ)
*
* @return the initiator
*/
public FullJid getInitiator() {
return initiator;
}
/**
* Get the responder. The responder is the full JID of the entity that has replied to the initiation (which may be
* different to the "to" addresss in the IQ).
*
* @return the responder
*/
public FullJid getResponder() {
return responder;
}
/**
* Returns the session ID related to the session. The session ID is a unique identifier generated by the initiator.
* This should match the XML Nmtoken production so that XML character escaping is not needed for characters such as
* &.
*
* @return Returns the session ID related to the session.
*/
public String getSid() {
return sessionId;
}
/**
* Get the action specified in the jingle IQ.
*
* @return the action.
*/
public JingleAction getAction() {
return action;
}
/**
* Get a List of the contents.
*
* @return the contents.
*/
public List<JingleContent> getContents() {
return contents;
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.optAttribute(INITIATOR_ATTRIBUTE_NAME, getInitiator());
xml.optAttribute(RESPONDER_ATTRIBUTE_NAME, getResponder());
xml.optAttribute(ACTION_ATTRIBUTE_NAME, getAction());
xml.optAttribute(SESSION_ID_ATTRIBUTE_NAME, getSid());
xml.rightAngleBracket();
xml.optElement(reason);
xml.append(contents);
return xml;
}
public static Builder getBuilder() {
return new Builder();
}
public static final class Builder {
private String sid;
private JingleAction action;
private FullJid initiator;
private FullJid responder;
private JingleReason reason;
private List<JingleContent> contents;
private Builder() {
}
public Builder setSessionId(String sessionId) {
this.sid = sessionId;
return this;
}
public Builder setAction(JingleAction action) {
this.action = action;
return this;
}
public Builder setInitiator(FullJid initator) {
this.initiator = initator;
return this;
}
public Builder setResponder(FullJid responder) {
this.responder = responder;
return this;
}
public Builder addJingleContent(JingleContent content) {
if (contents == null) {
contents = new ArrayList<>(1);
}
contents.add(content);
return this;
}
public Builder setReason(JingleReason.Reason reason) {
this.reason = new JingleReason(reason);
return this;
}
public Jingle build() {
return new Jingle(sid, action, initiator, responder, reason, contents);
}
}
}

View File

@ -0,0 +1,72 @@
/**
*
* Copyright © 2014-2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.HashMap;
import java.util.Map;
/**
* The "action" in the jingle packet, as an enum.
*
*
* @author Florian Schmaus
*/
public enum JingleAction {
content_accept,
content_add,
content_modify,
content_reject,
content_remove,
description_info,
session_accept,
session_info,
session_initiate,
sessio_terminate,
transport_accept,
transport_info,
transport_reject,
transport_replace,
;
private static final Map<String, JingleAction> map = new HashMap<String, JingleAction>(
JingleAction.values().length);
static {
for (JingleAction jingleAction : JingleAction.values()) {
map.put(jingleAction.toString(), jingleAction);
}
}
private final String asString;
private JingleAction() {
asString = this.name().replace('_', '-');
}
@Override
public String toString() {
return asString;
}
public static JingleAction fromString(String string) {
JingleAction jingleAction = map.get(string);
if (jingleAction == null) {
throw new IllegalArgumentException("Unknown jingle action: " + string);
}
return jingleAction;
}
}

View File

@ -0,0 +1,218 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* Jingle content element.
*/
public final class JingleContent implements NamedElement {
public static final String ELEMENT = "content";
public static final String CREATOR_ATTRIBUTE_NAME = "creator";
public enum Creator {
initiator,
responder,
}
/**
* Which party originally generated the content type. Defined values are 'initiator' and 'responder'. Default is
* 'initiator'.
*/
private final Creator creator;
public static final String DISPOSITION_ATTRIBUTE_NAME = "disposition";
private final String disposition;
public static final String NAME_ATTRIBUTE_NAME = "name";
private final String name;
public static final String SENDERS_ATTRIBUTE_NAME = "senders";
public enum Senders {
both,
initiator,
none,
responder,
}
/**
* Which parties in the session will be generation the content. Defined values are 'both', 'initiator', 'none' and
* 'responder. Default is 'both'.
*/
private final Senders senders;
private final JingleContentDescription description;
private final List<JingleContentTransport> transports;
/**
* Creates a content description..
*/
private JingleContent(Creator creator, String disposition, String name, Senders senders,
JingleContentDescription description, List<JingleContentTransport> transports) {
this.creator = Objects.requireNonNull(creator, "Jingle content creator must not be null");
this.disposition = disposition;
this.name = StringUtils.requireNotNullOrEmpty(name, "Jingle content name must not be null or empty");
this.senders = senders;
this.description = description;
if (transports != null) {
this.transports = Collections.unmodifiableList(transports);
}
else {
this.transports = Collections.emptyList();
}
}
public Creator getCreator() {
return creator;
}
public String getDisposition() {
return disposition;
}
public String getName() {
return name;
}
public Senders getSenders() {
return senders;
}
/**
* Gets the description for this Jingle content.
*
* @return The description.
*/
public JingleContentDescription getDescription() {
return description;
}
/**
* Returns an Iterator for the JingleTransports in the packet.
*
* @return an Iterator for the JingleTransports in the packet.
*/
public List<JingleContentTransport> getJingleTransports() {
return transports;
}
/**
* Returns a count of the JingleTransports in the Jingle packet.
*
* @return the number of the JingleTransports in the Jingle packet.
*/
public int getJingleTransportsCount() {
return transports.size();
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.attribute(CREATOR_ATTRIBUTE_NAME, creator);
xml.optAttribute(DISPOSITION_ATTRIBUTE_NAME, disposition);
xml.attribute(NAME_ATTRIBUTE_NAME, name);
xml.optAttribute(SENDERS_ATTRIBUTE_NAME, senders);
xml.rightAngleBracket();
xml.optAppend(description);
xml.append(transports);
xml.closeElement(this);
return xml;
}
public static Builder getBuilder() {
return new Builder();
}
public static final class Builder {
private Creator creator;
private String disposition;
private String name;
private Senders senders;
private JingleContentDescription description;
private List<JingleContentTransport> transports;
private Builder() {
}
public Builder setCreator(Creator creator) {
this.creator = creator;
return this;
}
public Builder setDisposition(String disposition) {
this.disposition = disposition;
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setSenders(Senders senders) {
this.senders = senders;
return this;
}
public Builder setDescription(JingleContentDescription description) {
if (this.description != null) {
throw new IllegalStateException("Jingle content description already set");
}
this.description = description;
return this;
}
public Builder addTransport(JingleContentTransport transport) {
if (transports == null) {
transports = new ArrayList<>(4);
}
transports.add(transport);
return this;
}
public JingleContent build() {
return new JingleContent(creator, disposition, name, senders, description, transports);
}
}
}

View File

@ -0,0 +1,68 @@
/**
*
* Copyright 2017 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* Jingle content description.
*
*/
public abstract class JingleContentDescription implements ExtensionElement {
public static final String ELEMENT = "description";
private final List<JingleContentDescriptionPayloadType> payloads;
protected JingleContentDescription(List<JingleContentDescriptionPayloadType> payloads) {
if (payloads != null) {
this.payloads = Collections.unmodifiableList(payloads);
}
else {
this.payloads = Collections.emptyList();
}
}
@Override
public String getElementName() {
return ELEMENT;
}
public List<JingleContentDescriptionPayloadType> getJinglePayloadTypes() {
return payloads;
}
protected void addExtraAttributes(XmlStringBuilder xml) {
}
@Override
public final XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
addExtraAttributes(xml);
xml.rightAngleBracket();
xml.append(payloads);
return xml;
}
}

View File

@ -0,0 +1,33 @@
/**
*
* Copyright © 2014-2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import org.jivesoftware.smack.packet.NamedElement;
/**
* An element found usually in 'description' elements.
*
*/
public abstract class JingleContentDescriptionPayloadType implements NamedElement {
public static final String ELEMENT = "payload-type";
@Override
public String getElementName() {
return ELEMENT;
}
}

View File

@ -0,0 +1,69 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* A jingle transport extension.
*
*/
public abstract class JingleContentTransport implements ExtensionElement {
public static final String ELEMENT = "transport";
protected final List<JingleContentTransportCandidate> candidates;
protected JingleContentTransport(List<JingleContentTransportCandidate> candidates) {
if (candidates != null) {
this.candidates = Collections.unmodifiableList(candidates);
}
else {
this.candidates = Collections.emptyList();
}
}
public List<JingleContentTransportCandidate> getCandidates() {
return candidates;
}
@Override
public String getElementName() {
return ELEMENT;
}
protected void addExtraAttributes(XmlStringBuilder xml) {
}
@Override
public final XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
addExtraAttributes(xml);
xml.rightAngleBracket();
xml.append(candidates);
xml.closeElement(this);
return xml;
}
}

View File

@ -0,0 +1,33 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import org.jivesoftware.smack.packet.NamedElement;
/**
* An element found usually in Jingle 'transport' elements.
*
*/
public abstract class JingleContentTransportCandidate implements NamedElement {
public static final String ELEMENT = "candidate";
@Override
public String getElementName() {
return ELEMENT;
}
}

View File

@ -0,0 +1,98 @@
/**
*
* Copyright 2003-2005 Jive Software, 2017 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.Locale;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
public final class JingleError implements ExtensionElement {
public static String NAMESPACE = "urn:xmpp:jingle:errors:1";
public static final JingleError OUT_OF_ORDER = new JingleError("out-of-order");
public static final JingleError UNKNOWN_SESSION = new JingleError("unknown-session");
public static final JingleError UNSUPPORTED_CONTENT = new JingleError("unsupported-content");
public static final JingleError UNSUPPORTED_TRANSPORTS = new JingleError("unsupported-transports");
private final String errorName;
/**
* Creates a new error with the specified code and errorName.
*
* @param message a message describing the error.
*/
private JingleError(final String errorName) {
this.errorName = errorName;
}
/**
* Returns the name of the Jingle error.
*
* @return the name of the error.
*/
public String getMessage() {
return errorName;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.closeEmptyElement();
return xml;
}
/**
* Returns a Action instance associated with the String value.
*/
public static JingleError fromString(String value) {
value = value.toLowerCase(Locale.US);
switch (value) {
case "out-of-order":
return OUT_OF_ORDER;
case "unknown-session":
return UNKNOWN_SESSION;
case "unsupported-content":
return UNSUPPORTED_CONTENT;
case "unsupported-transports":
return UNSUPPORTED_TRANSPORTS;
default:
throw new IllegalArgumentException();
}
}
@Override
public String toString() {
return getMessage();
}
@Override
public String getElementName() {
return errorName;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
}

View File

@ -0,0 +1,105 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.element;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* The Jingle 'reason' element.
*
* @see <a href="https://xmpp.org/extensions/xep-0166.html#def-reason">XEP-0166 § 7.4</a>
*
*/
public class JingleReason implements NamedElement {
public static final String ELEMENT = "reason";
public enum Reason {
alternative_session,
busy,
cancel,
connectivity_error,
decline,
expired,
failed_application,
failed_transport,
general_error,
gone,
incompatible_parameters,
media_error,
security_error,
success,
timeout,
unsupported_applications,
unsupported_transports,
;
private static final Map<String, Reason> LUT = new HashMap<>(Reason.values().length);
static {
for (Reason reason : Reason.values()) {
LUT.put(reason.toString(), reason);
}
}
private final String asString;
private Reason() {
asString = name().replace('_', '-');
}
@Override
public String toString() {
return asString;
}
public static Reason fromString(String string) {
Reason reason = LUT.get(string);
if (reason == null) {
throw new IllegalArgumentException("Unknown reason: " + string);
}
return reason;
}
}
private final Reason reason;
public JingleReason(Reason reason) {
this.reason = reason;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.rightAngleBracket();
xml.emptyElement(reason);
xml.closeElement(this);
return xml;
}
}

View File

@ -0,0 +1,21 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Stanzas and Extension Elements for <a href="https://xmpp.org/extensions/xep-0166.html">XEP-0166: Jingle</a>.
*/
package org.jivesoftware.smackx.jingle.element;

View File

@ -0,0 +1,21 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's API for <a href="https://xmpp.org/extensions/xep-0166.html">XEP-0166: Jingle</a>.
*/
package org.jivesoftware.smackx.jingle;

View File

@ -0,0 +1,29 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.jingle.element.JingleContentDescription;
import org.xmlpull.v1.XmlPullParser;
public abstract class JingleContentDescriptionProvider<D extends JingleContentDescription>
extends ExtensionElementProvider<D> {
@Override
public abstract D parse(XmlPullParser parser, int initialDepth) throws Exception;
}

View File

@ -0,0 +1,45 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class JingleContentProviderManager {
private static final Map<String, JingleContentDescriptionProvider<?>> jingleContentDescriptionProviders = new ConcurrentHashMap<>();
private static final Map<String, JingleContentTransportProvider<?>> jingleContentTransportProviders = new ConcurrentHashMap<>();
public static JingleContentDescriptionProvider<?> addJingleContentDescrptionProvider(String namespace,
JingleContentDescriptionProvider<?> provider) {
return jingleContentDescriptionProviders.put(namespace, provider);
}
public static JingleContentDescriptionProvider<?> getJingleContentDescriptionProvider(String namespace) {
return jingleContentDescriptionProviders.get(namespace);
}
public static JingleContentTransportProvider<?> addJingleContentDescrptionProvider(String namespace,
JingleContentTransportProvider<?> provider) {
return jingleContentTransportProviders.put(namespace, provider);
}
public static JingleContentTransportProvider<?> getJingleContentTransportProvider(String namespace) {
return jingleContentTransportProviders.get(namespace);
}
}

View File

@ -0,0 +1,29 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.jingle.element.JingleContentTransport;
import org.xmlpull.v1.XmlPullParser;
public abstract class JingleContentTransportProvider<T extends JingleContentTransport>
extends ExtensionElementProvider<T> {
@Override
public abstract T parse(XmlPullParser parser, int initialDepth) throws Exception;
}

View File

@ -0,0 +1,31 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.jingle.element.JingleError;
import org.xmlpull.v1.XmlPullParser;
public class JingleErrorProvider extends ExtensionElementProvider<JingleError> {
@Override
public JingleError parse(XmlPullParser parser, int initialDepth) throws Exception {
String errorName = parser.getName();
return JingleError.fromString(errorName);
}
}

View File

@ -0,0 +1,149 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import java.util.logging.Logger;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.util.ParserUtils;
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.JingleReason;
import org.jivesoftware.smackx.jingle.element.JingleReason.Reason;
import org.jxmpp.jid.FullJid;
import org.xmlpull.v1.XmlPullParser;
public class JingleProvider extends IQProvider<Jingle> {
private static final Logger LOGGER = Logger.getLogger(JingleProvider.class.getName());
@Override
public Jingle parse(XmlPullParser parser, int initialDepth) throws Exception {
Jingle.Builder builder = Jingle.getBuilder();
String actionString = parser.getAttributeValue("", Jingle.ACTION_ATTRIBUTE_NAME);
if (actionString != null) {
JingleAction action = JingleAction.fromString(actionString);
builder.setAction(action);
}
FullJid initiator = ParserUtils.getFullJidAttribute(parser, Jingle.INITIATOR_ATTRIBUTE_NAME);
builder.setInitiator(initiator);
FullJid responder = ParserUtils.getFullJidAttribute(parser, Jingle.RESPONDER_ATTRIBUTE_NAME);
builder.setResponder(responder);
String sessionId = parser.getAttributeValue("", Jingle.SESSION_ID_ATTRIBUTE_NAME);
builder.setSessionId(sessionId);
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String tagName = parser.getName();
switch (tagName) {
case JingleContent.ELEMENT:
JingleContent content = parseJingleContent(parser, parser.getDepth());
builder.addJingleContent(content);
break;
case JingleReason.ELEMENT:
parser.next();
String reasonString = parser.getName();
Reason reason = Reason.fromString(reasonString);
builder.setReason(reason);
break;
default:
LOGGER.severe("Unknown Jingle element: " + tagName);
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}
return builder.build();
}
public static JingleContent parseJingleContent(XmlPullParser parser, final int initialDepth)
throws Exception {
JingleContent.Builder builder = JingleContent.getBuilder();
String creatorString = parser.getAttributeValue("", JingleContent.CREATOR_ATTRIBUTE_NAME);
JingleContent.Creator creator = JingleContent.Creator.valueOf(creatorString);
builder.setCreator(creator);
String disposition = parser.getAttributeValue("", JingleContent.DISPOSITION_ATTRIBUTE_NAME);
builder.setDisposition(disposition);
String name = parser.getAttributeValue("", JingleContent.NAME_ATTRIBUTE_NAME);
builder.setName(name);
String sendersString = parser.getAttributeValue("", JingleContent.SENDERS_ATTRIBUTE_NAME);
if (sendersString != null) {
JingleContent.Senders senders = JingleContent.Senders.valueOf(sendersString);
builder.setSenders(senders);
}
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String tagName = parser.getName();
String namespace = parser.getNamespace();
switch (tagName) {
case JingleContentDescription.ELEMENT: {
JingleContentDescriptionProvider<?> provider = JingleContentProviderManager.getJingleContentDescriptionProvider(namespace);
if (provider == null) {
// TODO handle this case (DefaultExtensionElement wrapped in something?)
break;
}
JingleContentDescription description = provider.parse(parser);
builder.setDescription(description);
break;
}
case JingleContentTransport.ELEMENT: {
JingleContentTransportProvider<?> provider = JingleContentProviderManager.getJingleContentTransportProvider(namespace);
if (provider == null) {
// TODO handle this case (DefaultExtensionElement wrapped in something?)
break;
}
JingleContentTransport transport = provider.parse(parser);
builder.addTransport(transport);
break;
}
default:
LOGGER.severe("Unknown Jingle content element: " + tagName);
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
}
}
return builder.build();
}
}

View File

@ -0,0 +1,21 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Providers and parsers for <a href="https://xmpp.org/extensions/xep-0166.html">XEP-0166: Jingle</a>.
*/
package org.jivesoftware.smackx.jingle.provider;

View File

@ -544,4 +544,35 @@
<className>org.jivesoftware.smackx.bob.provider.BoBIQProvider</className>
</iqProvider>
<!-- XEP-0166: Jingle -->
<iqProvider>
<elementName>jingle</elementName>
<namespace>urn:xmpp:jingle:1</namespace>
<className>org.jivesoftware.smackx.jingle.provider.JingleProvider</className>
</iqProvider>
<extensionProvider>
<elementName>out-of-order</elementName>
<namespace>urn:xmpp:jingle:errors:1</namespace>
<className>org.jivesoftware.smackx.jingle.provider.JingleErrorProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>unknown-session</elementName>
<namespace>urn:xmpp:jingle:errors:1</namespace>
<className>org.jivesoftware.smackx.jingle.provider.JingleErrorProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>unsupported-content</elementName>
<namespace>urn:xmpp:jingle:errors:1</namespace>
<className>org.jivesoftware.smackx.jingle.provider.JingleErrorProvider</className>
</extensionProvider>
<extensionProvider>
<elementName>unsupported-transports</elementName>
<namespace>urn:xmpp:jingle:errors:1</namespace>
<className>org.jivesoftware.smackx.jingle.provider.JingleErrorProvider</className>
</extensionProvider>
</smackProviders>