rocks.xmpp.extensions.caps.model.EntityCapabilities Maven / Gradle / Ivy
Show all versions of xmpp-core Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.extensions.caps.model;
import rocks.xmpp.core.stream.model.StreamFeature;
import rocks.xmpp.extensions.data.model.DataForm;
import rocks.xmpp.extensions.disco.model.info.Identity;
import rocks.xmpp.extensions.disco.model.info.InfoNode;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
/**
* The implementation of the {@code } element in the {@code http://jabber.org/protocol/caps}.
*
* This class is immutable.
*
* @author Christian Schudt
* @see XEP-0115: Entity Capabilities
* @see XML Schema
*/
@XmlRootElement(name = "c")
public final class EntityCapabilities extends StreamFeature {
/**
* http://jabber.org/protocol/caps
*/
public static final String NAMESPACE = "http://jabber.org/protocol/caps";
/**
* The hashing algorithm used to generate the verification string.
*/
@XmlAttribute
private final String hash;
@XmlAttribute
private final String node;
/**
* The 'ver' attribute is a specially-constructed string (called a "verification string") that represents the entity's service discovery identity.
*/
@XmlAttribute
private final String ver;
private EntityCapabilities() {
this.node = null;
this.hash = null;
this.ver = null;
}
/**
* @param node The node.
* @param hash The hashing algorithm used to generate the verification string, e.g. "sha-1".
* @param ver The verification string.
*/
public EntityCapabilities(String node, String hash, String ver) {
this.node = Objects.requireNonNull(node);
this.hash = Objects.requireNonNull(hash);
this.ver = Objects.requireNonNull(ver);
}
/**
* Gets the verification string as specified in XEP-0115, 5. Verification String.
*
* @param infoNode The root node of the service discovery. It has information about the entities features and identities as well as extended service discovery information.
* @param messageDigest The hashing algorithm. Usually this is SHA-1.
* @return The generated verification string.
*/
public static String getVerificationString(InfoNode infoNode, MessageDigest messageDigest) {
Set identities = new TreeSet<>(infoNode.getIdentities());
Set features = new TreeSet<>(infoNode.getFeatures());
List dataForms = new ArrayList<>(infoNode.getExtensions());
// 1. Initialize an empty string S.
StringBuilder sb = new StringBuilder();
// 2. Sort the service discovery identities [15] by category and then by type and then by xml:lang (if it exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/' [NAME]. [16] Note that each slash is included even if the LANG or NAME is not included (in accordance with XEP-0030, the category and type MUST be included.
// This is done by TreeSet.
// 3. For each identity, append the 'category/type/lang/name' to S, followed by the '<' character.
for (Identity identity : identities) {
if (identity.getCategory() != null) {
sb.append(identity.getCategory());
}
sb.append('/');
if (identity.getType() != null) {
sb.append(identity.getType());
}
sb.append('/');
if (identity.getLanguage() != null) {
sb.append(identity.getLanguage());
}
sb.append('/');
if (identity.getName() != null) {
sb.append(identity.getName());
}
sb.append('<');
}
// 4. Sort the supported service discovery features.
// This is done by TreeSet.
// 5. For each feature, append the feature to S, followed by the '<' character.
for (String feature : features) {
if (feature != null) {
sb.append(feature);
}
sb.append('<');
}
// 6. If the service discovery information response includes XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., by the XML character data of the element).
dataForms.sort(null);
// 7. For each extended service discovery information form:
for (DataForm dataForm : dataForms) {
List fields = new ArrayList<>(dataForm.getFields());
// 7.2. Sort the fields by the value of the "var" attribute.
// This makes sure, that FORM_TYPE fields are always on zero position.
fields.sort(null);
if (!fields.isEmpty()) {
// Also make sure, that we don't send an ill-formed verification string.
// 3.6 If the response includes an extended service discovery information form where the FORM_TYPE field is not of type "hidden" or the form does not include a FORM_TYPE field, ignore the form but continue processing.
if (!DataForm.FORM_TYPE.equals(fields.get(0).getVar()) || fields.get(0).getType() != DataForm.Field.Type.HIDDEN) {
// => Don't include this form in the verification string.
continue;
}
for (DataForm.Field field : fields) {
List values = new ArrayList<>(field.getValues());
// 7.3. For each field other than FORM_TYPE:
if (!DataForm.FORM_TYPE.equals(field.getVar())) {
// 7.3.1. Append the value of the "var" attribute, followed by the '<' character.
sb.append(field.getVar()).append('<');
// 7.3.2. Sort values by the XML character data of the element.
values.sort(null);
}
// 7.1. Append the XML character data of the FORM_TYPE field's element, followed by the '<' character.
// 7.3.3. For each element, append the XML character data, followed by the '<' character.
for (String value : values) {
sb.append(value).append('<');
}
}
}
}
// 8. Ensure that S is encoded according to the UTF-8 encoding
// 9. Compute the verification string by hashing S using the algorithm specified in the 'hash' attribute.
messageDigest.reset();
return DatatypeConverter.printBase64Binary(messageDigest.digest(sb.toString().getBytes(StandardCharsets.UTF_8)));
}
/**
* Gets the hashing algorithm used to generate the verification string.
*
* @return The verification string.
*/
public final String getHashingAlgorithm() {
return hash;
}
/**
* Gets the node.
*
* A URI that uniquely identifies a software application, typically a URL at the website of the project or company that produces the software.
* It is RECOMMENDED for the value of the 'node' attribute to be an HTTP URL at which a user could find further information about the software product, such as "http://psi-im.org" for the Psi client; this enables a processing application to also determine a unique string for the generating application, which it could maintain in a list of known software implementations (e.g., associating the name received via the disco#info reply with the URL found in the caps data).
*
*
* @return The node.
*/
public final String getNode() {
return node;
}
/**
* Gets the verification string that is used to verify the identity and supported features of the entity.
*
* @return The verification string.
*/
public final String getVerificationString() {
return ver;
}
@Override
public final String toString() {
return "Caps: " + ver;
}
}