rocks.xmpp.core.stream.StreamFeaturesManager Maven / Gradle / Ivy
/*
* 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.core.stream;
import rocks.xmpp.core.session.Manager;
import rocks.xmpp.core.session.NoResponseException;
import rocks.xmpp.core.session.XmppSession;
import rocks.xmpp.core.stream.model.StreamFeature;
import rocks.xmpp.core.stream.model.StreamFeatures;
import rocks.xmpp.core.tls.model.StartTls;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Manages the various features, which are advertised during stream negotiation.
*
*
* Because the receiving entity for a stream acts as a gatekeeper to the domains it services, it imposes
* certain conditions for connecting as a client or as a peer server. At a minimum, the initiating entity
* needs to authenticate with the receiving entity before it is allowed to send stanzas to the receiving
* entity (for client-to-server streams this means using SASL as described under Section 6). However, the
* receiving entity can consider conditions other than authentication to be mandatory-to-negotiate, such as
* encryption using TLS as described under Section 5. The receiving entity informs the initiating entity
* about such conditions by communicating "stream features": the set of particular protocol interactions that
* the initiating entity needs to complete before the receiving entity will accept XML stanzas from the
* initiating entity, as well as any protocol interactions that are voluntary-to-negotiate but that might
* improve the handling of an XML stream (e.g., establishment of application-layer compression as described
* in [XEP-0138]).
*
* Each feature is associated with a {@linkplain StreamFeatureNegotiator feature negotiator}, which
* negotiates the particular feature.
* This class manages these negotiators, receives XML elements and delegates them to the responsible
* feature negotiator for further processing.
* It negotiates the stream by sequentially negotiating each stream feature.
*
* This class is thread-safe.
*
* @author Christian Schudt
*/
public final class StreamFeaturesManager extends Manager {
private static final EnumSet NEGOTIATION_COMPLETED = EnumSet.of(StreamFeatureNegotiator.Status.SUCCESS, StreamFeatureNegotiator.Status.IGNORE);
private final Lock lock = new ReentrantLock();
private final Map, Condition> featureNegotiationStartedConditions = new ConcurrentHashMap<>();
/**
* The features which have been advertised by the server.
*/
private final HashMap, StreamFeature> advertisedFeatures = new HashMap<>();
/**
* The list of features, which the server advertised and have not yet been negotiated.
*/
private final Queue featuresToNegotiate = new ArrayDeque<>();
/**
* The feature for which negotiation has started.
*/
private final Set> negotiatingFeatures = new HashSet<>();
/**
* The feature negotiators, which are responsible to negotiate each individual feature.
*/
private final Set streamFeatureNegotiators = new CopyOnWriteArraySet<>();
private final Condition negotiationCompleted = lock.newCondition();
private StreamFeaturesManager(XmppSession xmppSession) {
super(xmppSession, false);
}
@Override
protected final void initialize() {
xmppSession.addSessionStatusListener(e -> {
switch (e.getStatus()) {
// If we're (re)connecting, make sure any previous features are forgotten.
case CONNECTING:
synchronized (this) {
featureNegotiationStartedConditions.clear();
advertisedFeatures.clear();
negotiatingFeatures.clear();
}
break;
// If the connection is closed, clear everything.
case CLOSED:
synchronized (this) {
featureNegotiationStartedConditions.clear();
advertisedFeatures.clear();
featuresToNegotiate.clear();
negotiatingFeatures.clear();
streamFeatureNegotiators.clear();
}
break;
}
});
}
/**
* Gets the available features, which the server has advertised.
*
* @return The features.
*/
@SuppressWarnings("unchecked")
public final Map, StreamFeature> getFeatures() {
// return defensive copies of mutable internal fields
return Collections.unmodifiableMap((HashMap, StreamFeature>) advertisedFeatures.clone());
}
/**
* Adds a new feature negotiator, which is responsible for negotiating an individual feature.
*
* @param streamFeatureNegotiator The feature negotiator, which is responsible for the feature.
*/
public final void addFeatureNegotiator(StreamFeatureNegotiator streamFeatureNegotiator) {
streamFeatureNegotiators.add(streamFeatureNegotiator);
}
/**
* Processes the {@code } element and immediately starts negotiating the first feature.
*
* @param featuresElement The {@code } element.
* @throws StreamNegotiationException If an exception occurred during feature negotiation.
*/
public final synchronized void processFeatures(StreamFeatures featuresElement) throws StreamNegotiationException {
List