1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-26 05:52:06 +01:00

Compare commits

..

No commits in common. "1df0763f92ce1775a1fb508701c3a9e5b91d2a1c" and "d8642847ea730619211c9389dd9b12c48a8b8d23" have entirely different histories.

52 changed files with 611 additions and 1145 deletions

View file

@ -1,97 +0,0 @@
name: CI
env:
GRADLE_VERSION: 6.7.1
on: [push, pull_request]
jobs:
build:
name: Build Smack
runs-on: ubuntu-20.04
strategy:
matrix:
java: [ 1.8, 11 ]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
# Caches
- name: Cache Maven
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/build.gradle') }}
restore-keys: |
maven-
- name: Cache Gradle
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: gradle-caches-${{ hashFiles('**/build.gradle') }}
restore-keys:
gradle-caches
- name: Cache Gradle Binary
uses: actions/cache@v2
with:
path: |
~/gradle-bin-${GRADLE_VERSION}/
key: gradle-bin-${GRADLE_VERSION}
- name: Cache Android SDK
uses: actions/cache@v2
with:
path: |
~/.android/sdk
key: android-${{ hashFiles('build.gradle') }}
restore-keys: |
android-
# Pre-reqs
- name: Grab gradle wrapper
run: |
wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip
unzip gradle-${GRADLE_VERSION}-all.zip
echo "PATH_TO_GRADLE=./gradle-${GRADLE_VERSION}/bin/gradle" >> $GITHUB_ENV
- name: Install GraphViz
run: sudo apt update && sudo apt install graphviz
- name: Install Android SDK Manager
uses: android-actions/setup-android@v2
- name: Install Android SDK
run: |
sdkmanager "platforms;android-19"
# Testing
- name: Gradle Check
run: ${PATH_TO_GRADLE} check --stacktrace
# Test local publish
- name: Gradle publish
run: ${PATH_TO_GRADLE} publishToMavenLocal --stacktrace
# Javadoc
- name: Javadoc
if: ${{ matrix.java == 11 }}
run: ${PATH_TO_GRADLE} javadocAll --stacktrace
# Test Coverage Report
- name: Jacoco Test Coverage
if: ${{ matrix.java == 1.8 }}
run: ${PATH_TO_GRADLE} jacocoRootReport coveralls
env:
COVERALLS_REPO_TOKEN: S2ecSJja2cKJa9yv45C8ZFPohXuRrTXKd
# Upload build artifacts
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: smack-java-${{ matrix.java }}
path: |
smack-*/build/libs/*.jar
!**/*-test-fixtures.jar
!**/*-tests.jar

52
.travis.yml Normal file
View file

@ -0,0 +1,52 @@
language: android
dist: trusty
android:
components:
- android-19
jdk:
- openjdk8
- openjdk11
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.android/build-cache
- $HOME/.m2
before_install:
- export GRADLE_VERSION=6.2
- wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip
- unzip -q gradle-${GRADLE_VERSION}-all.zip
- export PATH="$(pwd)/gradle-${GRADLE_VERSION}/bin:$PATH"
addons:
apt:
update: true
packages:
- graphviz
install: gradle assemble --stacktrace
# Run the test suite and also install the artifacts in the local maven
# archive to additionaly test if artifact creation is
# functional. Which hasn't always be the case in the past, see
# 90cbcaebc7a89f4f771f733a33ac9f389df85be2
# Also run javadocAll to ensure it works.
script:
- |
JAVAC_MAJOR_VERSION=$(javac -version | sed -E 's/javac ([[:digit:]]+).*/\1/')
GRADLE_TASKS=()
GRADLE_TASKS+=(check)
GRADLE_TASKS+=(publishToMavenLocal)
if [[ ${JAVAC_MAJOR_VERSION} -ge 11 ]]; then
GRADLE_TASKS+=(javadocAll)
fi
gradle ${GRADLE_TASKS[@]} --stacktrace
after_success:
- JAVAC_VERSION=$((javac -version) 2>&1)
# Only run jacocoRootReport in the Java 8 build
- if [[ "$JAVAC_VERSION" = javac\ 1.8.* ]]; then gradle jacocoRootReport coveralls; fi

View file

@ -1,7 +1,7 @@
Smack
=====
[![Build Status](https://github.com/igniterealtime/Smack/workflows/CI/badge.svg)](https://github.com/igniterealtime/Smack/actions?query=workflow%3A%22CI%22) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org)
[![Build Status](https://api.travis-ci.com/igniterealtime/Smack.svg?branch=master)](https://travis-ci.com/github/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org)
About
-----

View file

@ -7,6 +7,7 @@ buildscript {
dependencies {
classpath 'org.kordamp:markdown-gradle-plugin:1.0.0'
classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2'
classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.3.1"
}
}
@ -15,7 +16,6 @@ plugins {
id 'net.ltgt.errorprone' version '1.1.1'
// Use e.g. "gradle <task> taskTree" to show its dependency tree.
id 'com.dorongold.task-tree' version '1.5'
id 'com.github.kt3k.coveralls' version '2.10.2'
}
apply plugin: 'org.kordamp.gradle.markdown'

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2009 Jive Software, 2018-2021 Florian Schmaus.
* Copyright 2009 Jive Software, 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -2201,29 +2201,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return SMACK_REACTOR.schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking);
}
/**
* Must be called when a XMPP stream open tag is encountered. Sets values like the stream ID and the incoming stream
* XML environment.
* <p>
* This method also returns a matching stream close tag. For example if the stream open is {@code <stream >}, then
* {@code </stream>} is returned. But if it is {@code <stream:stream>}, then {@code </stream:stream>} is returned.
* Or if it is {@code <foo:stream>}, then {@code </foo:stream>} is returned.
* </p>
*
* @param parser an XML parser that is positioned at the start of the stream open.
* @return a String representing the corresponding stream end tag.
*/
protected String onStreamOpen(XmlPullParser parser) {
assert StreamOpen.ETHERX_JABBER_STREAMS_NAMESPACE.equals(parser.getNamespace());
assert StreamOpen.UNPREFIXED_ELEMENT.equals(parser.getName());
protected void onStreamOpen(XmlPullParser parser) {
// We found an opening stream.
if ("jabber:client".equals(parser.getNamespace(null))) {
streamId = parser.getAttributeValue("", "id");
incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
streamId = parser.getAttributeValue("id");
incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
String reportedServerDomainString = parser.getAttributeValue("from");
// RFC 6120 § 4.7.1. makes no explicit statement whether or not 'from' in the stream open from the server
// in c2s connections is required or not.
if (reportedServerDomainString != null) {
String reportedServerDomainString = parser.getAttributeValue("", "from");
if (reportedServerDomainString == null) {
// RFC 6120 § 4.7.1. makes no explicit statement whether or not 'from' in the stream open from the server
// in c2s connections is required or not.
return;
}
DomainBareJid reportedServerDomain;
try {
reportedServerDomain = JidCreate.domainBareFrom(reportedServerDomainString);
@ -2237,12 +2226,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
+ "' as reported by server could not be transformed to a valid JID", e);
}
}
String prefix = parser.getPrefix();
if (StringUtils.isNotEmpty(prefix)) {
return "</" + prefix + ":stream>";
}
return "</stream>";
}
protected final void sendStreamOpen() throws NotConnectedException, InterruptedException {
@ -2250,7 +2233,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// possible. The 'to' attribute is *always* available. The 'from' attribute if set by the user and no external
// mechanism is used to determine the local entity (user). And the 'id' attribute is available after the first
// response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
DomainBareJid to = getXMPPServiceDomain();
CharSequence to = getXMPPServiceDomain();
CharSequence from = null;
CharSequence localpart = config.getUsername();
if (localpart != null) {
@ -2264,7 +2247,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
}
protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
return new StreamOpen(to, from, id, lang);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2021 Florian Schmaus
* Copyright 2017-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -75,10 +75,6 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
@Override
public final synchronized boolean isDone() {
return result != null || exception != null || cancelled;
}
public final synchronized boolean wasSuccessful() {
return result != null;
}
@ -166,10 +162,6 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return result;
}
public E getExceptionIfAvailable() {
return exception;
}
protected final synchronized void maybeInvokeCallbacks() {
if (cancelled) {
return;
@ -334,11 +326,6 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return future;
}
public static boolean await(Collection<? extends SmackFuture<?, ?>> futures, long timeout)
throws InterruptedException {
return await(futures, timeout, TimeUnit.MILLISECONDS);
}
public static boolean await(Collection<? extends SmackFuture<?, ?>> futures, long timeout, TimeUnit unit) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(futures.size());
for (SmackFuture<?, ?> future : futures) {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2021 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -139,8 +139,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
@Override
public String onStreamOpen(XmlPullParser parser) {
return ModularXmppClientToServerConnection.this.onStreamOpen(parser);
public void setCurrentConnectionExceptionAndNotify(Exception exception) {
ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception);
}
@Override
public void onStreamOpen(XmlPullParser parser) {
ModularXmppClientToServerConnection.this.onStreamOpen(parser);
}
@Override
@ -566,7 +571,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
@Override
protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
}
@ -715,11 +720,6 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures);
}
if (!lookupFailures.isEmpty()) {
// TODO: Put those non-fatal lookup failures into a sink of the connection so that the user is able to
// be aware of them.
}
// Even though the outgoing elements queue is unrelated to the lookup remote connection endpoints state, we
// do start the queue at this point. The transports will need it available, and we use the state's reset()
// function to close the queue again on failure.
@ -1110,12 +1110,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
XmppClientToServerTransport.Stats stats = entry.getValue();
StringUtils.appendHeading(appendable, transportClass.getName());
if (stats != null) {
appendable.append(stats.toString());
} else {
appendable.append("No stats available.");
}
appendable.append('\n');
appendable.append(stats.toString()).append('\n');
}
for (Map.Entry<String, Object> entry : filtersStats.entrySet()) {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -62,10 +62,6 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn
// configuration, e.g. there is no edge from disconnected to connected.
throw new IllegalStateException(e);
}
for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
moduleDescriptor.validateConfiguration(this);
}
}
public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,9 +28,6 @@ public abstract class ModularXmppClientToServerConnectionModuleDescriptor {
protected abstract ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal);
protected void validateConfiguration(ModularXmppClientToServerConnectionConfiguration configuration) {
}
public abstract static class Builder {
private final ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder;

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2021 Florian Schmaus.
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,12 +19,8 @@ package org.jivesoftware.smack.c2s;
import org.jivesoftware.smack.packet.AbstractStreamClose;
import org.jivesoftware.smack.packet.AbstractStreamOpen;
import org.jxmpp.jid.DomainBareJid;
public interface StreamOpenAndCloseFactory {
AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang);
AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang);
AbstractStreamClose createStreamClose();
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -37,8 +37,6 @@ public abstract class XmppClientToServerTransport {
protected abstract void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess);
public abstract boolean hasUseableConnectionEndpoints();
/**
* Notify the transport that new outgoing data is available. Usually this method does not need to be called
* explicitly, only if the filters are modified so that they potentially produced new data.

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,6 @@
*/
package org.jivesoftware.smack.c2s.internal;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
@ -40,10 +39,8 @@ import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.Supplier;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public abstract class ModularXmppClientToServerConnectionInternal {
@ -88,19 +85,9 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract void notifyConnectionError(Exception e);
public final String onStreamOpen(String streamOpen) {
XmlPullParser streamOpenParser;
try {
streamOpenParser = PacketParserUtils.getParserFor(streamOpen);
} catch (XmlPullParserException | IOException e) {
// Should never happen.
throw new AssertionError(e);
}
String streamClose = onStreamOpen(streamOpenParser);
return streamClose;
}
public abstract void setCurrentConnectionExceptionAndNotify(Exception exception);
public abstract String onStreamOpen(XmlPullParser parser);
public abstract void onStreamOpen(XmlPullParser parser);
public abstract void onStreamClosed();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2021 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,7 +20,6 @@ import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
@ -76,24 +75,4 @@ public abstract class State {
}
}
public abstract static class AbstractTransport extends State {
private final XmppClientToServerTransport transport;
protected AbstractTransport(XmppClientToServerTransport transport, StateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(stateDescriptor, connectionInternal);
this.transport = transport;
}
@Override
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext)
throws SmackException {
if (!transport.hasUseableConnectionEndpoints()) {
return new StateTransitionResult.TransitionImpossibleBecauseNoEndpointsDiscovered(transport);
}
return super.isTransitionToPossible(walkStateGraphContext);
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2021 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
*/
package org.jivesoftware.smack.fsm;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
public abstract class StateTransitionResult {
private final String message;
@ -94,10 +92,4 @@ public abstract class StateTransitionResult {
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)");
}
}
public static class TransitionImpossibleBecauseNoEndpointsDiscovered extends TransitionImpossibleReason {
public TransitionImpossibleBecauseNoEndpointsDiscovered(XmppClientToServerTransport transport) {
super("The transport " + transport + " did not discover any endpoints");
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus, 2020 Aditya Borikar
* Copyright 2020 Florian Schmaus, Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,6 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* be achieved through {@link XMPPConnection#sendNonza(Nonza)}.
*/
public abstract class AbstractStreamOpen implements Nonza {
public static final String ETHERX_JABBER_STREAMS_NAMESPACE = "http://etherx.jabber.org/streams";
public static final String CLIENT_NAMESPACE = "jabber:client";
public static final String SERVER_NAMESPACE = "jabber:server";

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2021 Florian Schmaus
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,8 +20,6 @@ public final class StreamClose extends AbstractStreamClose {
public static final StreamClose INSTANCE = new StreamClose();
public static final String STRING = "</" + StreamOpen.ELEMENT + ">";
private StreamClose() {
}
@ -41,8 +39,4 @@ public final class StreamClose extends AbstractStreamClose {
return StreamOpen.ELEMENT;
}
@Override
public String toString() {
return STRING;
}
}

View file

@ -23,9 +23,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* The stream open <b>tag</b>.
*/
public final class StreamOpen extends AbstractStreamOpen {
public static final String UNPREFIXED_ELEMENT = "stream";
public static final String ELEMENT = "stream:" + UNPREFIXED_ELEMENT;
public static final String ELEMENT = "stream:stream";
public StreamOpen(CharSequence to) {
this(to, null, null, null, StreamContentNamespace.client);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -133,7 +133,7 @@ public class XmlEnvironment {
}
public static XmlEnvironment from(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) {
String namespace = parser.getDefaultNamespace();
String namespace = parser.getNamespace();
String xmlLang = ParserUtils.getXmlLang(parser);
return new XmlEnvironment(namespace, xmlLang, outerXmlEnvironment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.jivesoftware.smack.provider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import org.jivesoftware.smack.packet.Element;
@ -43,12 +42,9 @@ public class AbstractProvider<E extends Element> {
} else if (elementType instanceof ParameterizedType) {
ParameterizedType parameteriezedElementType = (ParameterizedType) elementType;
elementClass = (Class<E>) parameteriezedElementType.getRawType();
} else if (elementType instanceof TypeVariable) {
TypeVariable<?> typeVariableElementType = (TypeVariable<?>) elementType;
elementClass = (Class<E>) typeVariableElementType.getClass();
} else {
throw new AssertionError("Element type '" + elementType + "' (" + elementType.getClass()
+ ") is neither of type Class, ParameterizedType or TypeVariable");
throw new AssertionError(
"Element type '" + elementType + "' is neither of type Class or ParameterizedType");
}
}

View file

@ -389,22 +389,6 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return this;
}
/**
* If the provided Integer argument is not null, then add a new XML attribute with the given name and the Integer as
* value.
*
* @param name the XML attribute name.
* @param value the optional integer to use as the attribute's value.
* @return a reference to this object.
* @since 4.4.1
*/
public XmlStringBuilder optIntAttribute(String name, Integer value) {
if (value != null) {
attribute(name, value.toString());
}
return this;
}
/**
* Add the given attribute if value not null and {@code value => 0}.
*

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,9 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.xml.XMLConstants;
@ -210,34 +207,6 @@ public class XmlPullParserTest {
}
}
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void testGetNamespace(SmackTestUtil.XmlPullParserKind parserKind)
throws XmlPullParserException, IOException {
String xml = "<foo:bar xmlns='namespace' xmlns:foo='foo-namespace'/>";
XmlPullParser parser = SmackTestUtil.getParserFor(xml, parserKind);
String parsedName = parser.getName();
assertEquals("bar", parsedName);
String parsedPrefix = parser.getPrefix();
assertEquals("foo", parsedPrefix);
String parsedPrefixNamespace = parser.getNamespace(parsedPrefix);
assertEquals("foo-namespace", parsedPrefixNamespace);
String parsedNamespace = parser.getNamespace();
assertEquals("foo-namespace", parsedNamespace);
List<Function<XmlPullParser, String>> defaultNamespaceRetrievers = new ArrayList<>();
defaultNamespaceRetrievers.add(p -> p.getNamespace(null));
defaultNamespaceRetrievers.add(p -> p.getDefaultNamespace());
for (Function<XmlPullParser, String> defaultNamespaceRetriever : defaultNamespaceRetrievers) {
String defaultNamespace = defaultNamespaceRetriever.apply(parser);
assertEquals("namespace", defaultNamespace);
}
}
private static void assertAttributeHolds(XmlPullParser parser, int attributeIndex, String expectedLocalpart,
String expectedPrefix, String expectedNamespace) {
QName qname = parser.getAttributeQName(attributeIndex);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@
package org.jivesoftware.smackx.bob.element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.StanzaView;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.bob.BoBData;
@ -60,26 +60,6 @@ public class BoBDataExtension implements ExtensionElement {
return NAMESPACE;
}
/**
* Get the content ID.
*
* @return the content ID.
* @since 4.4.1
*/
public final ContentId getContentId() {
return cid;
}
/**
* Get the Bits of Binary (BOB) data.
*
* @return the BoB data.
* @since 4.4.1
*/
public final BoBData getBobData() {
return bobData;
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this);
@ -94,8 +74,8 @@ public class BoBDataExtension implements ExtensionElement {
return xml;
}
public static BoBDataExtension from(StanzaView stanza) {
return stanza.getExtension(BoBDataExtension.class);
public static BoBDataExtension from(Message message) {
return message.getExtension(BoBDataExtension.class);
}
}

View file

@ -312,10 +312,7 @@ public final class EntityCapsManager extends Manager {
// XEP-0115 specifies that a client SHOULD include entity capabilities
// with every presence notification it sends.
private void addCapsExtension(PresenceBuilder presenceBuilder) {
final CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash();
if (capsVersionAndHash == null) {
return;
}
CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash();
CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
presenceBuilder.overrideExtension(caps);
}
@ -437,10 +434,10 @@ public final class EntityCapsManager extends Manager {
}
/**
* Get our own caps version or {@code null} if none is yet set. The version depends on the enabled features.
* Get our own caps version. The version depends on the enabled features.
* A caps version looks like '66/0NaeaBKkwk85efJTGmU47vXI='
*
* @return our own caps version or {@code null}.
* @return our own caps version
*/
public CapsVersionAndHash getCapsVersionAndHash() {
return currentCapsVersion;

View file

@ -494,9 +494,6 @@ public final class DataForm implements ExtensionElement {
* @return a reference to this builder.
*/
public Builder addItem(Item item) {
if (items == null) {
items = new ArrayList<>();
}
items.add(item);
return this;
}

View file

@ -183,7 +183,7 @@ public final class DirectoryRosterStore implements RosterStore {
private static Item readEntry(File file) {
Reader reader;
try {
// TODO: Use Files.newBufferedReader() once Smack's minimum Android API level is 26 or higher.
// TODO: Should use Files.newBufferedReader() but it is not available on Android.
reader = new FileReader(file);
} catch (FileNotFoundException e) {
LOGGER.log(Level.FINE, "Roster entry file not found", e);
@ -195,7 +195,7 @@ public final class DirectoryRosterStore implements RosterStore {
Item item = RosterPacketProvider.parseItem(parser);
reader.close();
return item;
} catch (XmlPullParserException | IOException | IllegalArgumentException e) {
} catch (XmlPullParserException | IOException e) {
boolean deleted = file.delete();
String message = "Exception while parsing roster entry.";
if (deleted) {

View file

@ -12,8 +12,7 @@ dependencies {
api project(':smack-openpgp')
api project(':smack-resolver-minidns')
api project(':smack-resolver-minidns-dox')
// TODO: Change this to smack-websocket-java11 once it arrives.
api project(':smack-websocket-okhttp')
api project(':smack-websocket')
api project(':smack-tcp')
testImplementation(testFixtures(project(":smack-core")))

View file

@ -1,110 +0,0 @@
/**
*
* Copyright 2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.full;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.util.XmppDateTime;
public class WebSocketConnectionTest {
static {
SmackConfiguration.DEBUG = true;
}
public static void main(String[] args)
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
String jid, password, websocketEndpoint, messageTo = null;
if (args.length < 3 || args.length > 4) {
throw new IllegalArgumentException();
}
jid = args[0];
password = args[1];
websocketEndpoint = args[2];
if (args.length >= 4) {
messageTo = args[3];
}
testWebSocketConnection(jid, password, websocketEndpoint, messageTo);
}
public static void testWebSocketConnection(String jid, String password, String websocketEndpoint)
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
testWebSocketConnection(jid, password, websocketEndpoint, null);
}
public static void testWebSocketConnection(String jid, String password, String websocketEndpoint, String messageTo)
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
builder.removeAllModules()
.setXmppAddressAndPassword(jid, password)
.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE)
;
XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(websocketEndpoint, false);
builder.addModule(websocketBuilder.build());
ModularXmppClientToServerConnectionConfiguration config = builder.build();
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
connection.setReplyTimeout(5 * 60 * 1000);
connection.addConnectionStateMachineListener((event, c) -> {
Logger.getAnonymousLogger().info("Connection event: " + event);
});
connection.connect();
connection.login();
if (messageTo != null) {
Message message = connection.getStanzaFactory().buildMessageStanza()
.to(messageTo)
.setBody("It is alive! " + XmppDateTime.formatXEP0082Date(new Date()))
.build()
;
connection.sendStanza(message);
}
Thread.sleep(1000);
connection.disconnect();
ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
// CHECKSTYLE:OFF
System.out.println("WebSocket successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
// CHECKSTYLE:ON
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2021 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* This file is part of smack-repl.
*
@ -25,6 +25,8 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException;
@ -36,11 +38,18 @@ import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.util.XmppDateTime;
public class Nio {
private static final Logger LOGGER = Logger.getLogger(Nio.class.getName());
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException {
doNio(args[0], args[1], args[2]);
}
@ -102,7 +111,30 @@ public class Nio {
connection.setReplyTimeout(5 * 60 * 1000);
XmppTools.modularConnectionTest(connection, "flo@geekplace.eu");
connection.addConnectionStateMachineListener((event, c) -> {
LOGGER.info("Connection event: " + event);
});
connection.connect();
connection.login();
Message message = connection.getStanzaFactory().buildMessageStanza()
.to("flo@geekplace.eu")
.setBody("It is alive! " + XmppDateTime.formatXEP0082Date(new Date()))
.build();
connection.sendStanza(message);
Thread.sleep(1000);
connection.disconnect();
ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
// CHECKSTYLE:OFF
System.out.println("NIO successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
// CHECKSTYLE:ON
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* This file is part of smack-repl.
*
@ -21,6 +21,7 @@
package org.igniterealtime.smack.smackrepl;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.jivesoftware.smack.SmackException;
@ -28,51 +29,25 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor;
public class WebSocketConnection {
public static void main(String[] args)
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
String jid, password, websocketEndpoint, messageTo = null;
if (args.length < 3 || args.length > 4) {
throw new IllegalArgumentException();
}
jid = args[0];
password = args[1];
websocketEndpoint = args[2];
if (args.length >= 4) {
messageTo = args[3];
}
TLSUtils.setDefaultTrustStoreTypeToJksIfRequired();
testWebSocketConnection(jid, password, websocketEndpoint, messageTo);
}
public static void testWebSocketConnection(String jid, String password, String websocketEndpoint)
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
testWebSocketConnection(jid, password, websocketEndpoint, null);
}
public static void testWebSocketConnection(String jid, String password, String websocketEndpoint, String messageTo)
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException, URISyntaxException {
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
builder.removeAllModules()
.setXmppAddressAndPassword(jid, password)
;
builder.removeAllModules();
builder.setXmppAddressAndPassword(args[0], args[1]);
// Set a fallback uri into websocket transport descriptor and add this descriptor into connection builder.
XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(websocketEndpoint, false);
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(new URI(args[2]), false);
builder.addModule(websocketBuilder.build());
ModularXmppClientToServerConnectionConfiguration config = builder.build();
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
connection.setReplyTimeout(5 * 60 * 1000);
XmppTools.modularConnectionTest(connection, messageTo);
connection.connect();
connection.login();
connection.disconnect();
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2021 Florian Schmaus
* Copyright 2016 Florian Schmaus
*
* This file is part of smack-repl.
*
@ -23,8 +23,6 @@ package org.igniterealtime.smack.smackrepl;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
@ -32,19 +30,15 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.iqregister.AccountManager;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.util.XmppDateTime;
public class XmppTools {
@ -112,40 +106,4 @@ public class XmppTools {
connection.disconnect();
}
}
public static void modularConnectionTest(ModularXmppClientToServerConnection connection, String messageTo) throws XMPPException, SmackException, IOException, InterruptedException {
connection.addConnectionStateMachineListener((event, c) -> {
Logger.getAnonymousLogger().info("Connection event: " + event);
});
connection.connect();
connection.login();
XmppTools.sendItsAlive(messageTo, connection);
Thread.sleep(1000);
connection.disconnect();
ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
// CHECKSTYLE:OFF
System.out.println("NIO successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
// CHECKSTYLE:ON
}
public static void sendItsAlive(String to, XMPPConnection connection)
throws XmppStringprepException, NotConnectedException, InterruptedException {
if (to == null) {
return;
}
Message message = connection.getStanzaFactory().buildMessageStanza()
.to(to)
.setBody("It is alive! " + XmppDateTime.formatXEP0082Date(new Date()))
.build();
connection.sendStanza(message);
}
}

View file

@ -83,7 +83,6 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sm.SMUtils;
@ -962,8 +961,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
final String namespace = parser.getNamespace();
switch (name) {
case Message.ELEMENT:
case IQ.IQ_ELEMENT:
@ -975,9 +972,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
break;
case "stream":
if (StreamOpen.ETHERX_JABBER_STREAMS_NAMESPACE.equals(namespace)) {
onStreamOpen(parser);
}
onStreamOpen(parser);
break;
case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser);
@ -994,6 +989,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
openStreamAndResetParser();
break;
case "failure":
String namespace = parser.getNamespace(null);
switch (namespace) {
case "urn:ietf:params:xml:ns:xmpp-tls":
// TLS negotiation has failed. The server will close the connection

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -80,12 +80,14 @@ import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints;
import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints.Result;
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.UTF8;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.util.JidUtil;
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
@ -211,8 +213,6 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
}
final String prefixXmlns = "xmlns:" + prefix;
// TODO: Use the return value of onStreamOpen(), which now returns the
// corresponding stream close tag, instead of creating it here.
final StringBuilder streamClose = new StringBuilder(32);
final StringBuilder streamOpen = new StringBuilder(256);
@ -253,7 +253,14 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
this.streamOpen = streamOpen.toString();
this.streamClose = streamClose.toString();
connectionInternal.onStreamOpen(this.streamOpen);
XmlPullParser streamOpenParser;
try {
streamOpenParser = PacketParserUtils.getParserFor(this.streamOpen);
} catch (XmlPullParserException | IOException e) {
// Should never happen.
throw new AssertionError(e);
}
connectionInternal.onStreamOpen(streamOpenParser);
}
@Override
@ -579,7 +586,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
return new StreamOpenAndCloseFactory() {
@Override
public StreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
public StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
String xmlLang = connectionInternal.connection.getConfiguration().getXmlLang();
StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client);
return streamOpen;
@ -596,11 +603,6 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
discoveredTcpEndpoints = null;
}
@Override
public boolean hasUseableConnectionEndpoints() {
return discoveredTcpEndpoints != null;
}
@Override
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup.
@ -748,10 +750,10 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
return new EstablishingTcpConnectionState(stateDescriptor, connectionInternal);
}
final class EstablishingTcpConnectionState extends State.AbstractTransport {
final class EstablishingTcpConnectionState extends State {
private EstablishingTcpConnectionState(EstablishingTcpConnectionStateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(tcpNioTransport, stateDescriptor, connectionInternal);
super(stateDescriptor, connectionInternal);
}
@Override
@ -775,10 +777,6 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
connectionInternal.setTransport(tcpNioTransport);
// TODO: It appears this should be done in a generic way. I'd assume we always
// have to wait for stream features after the connection was established. If this is true then consider
// moving this into State.AbstractTransport. But I am not yet 100% positive that this is the case for every
// transport. Hence keep it here for now.
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
return new TcpSocketConnectedResult(remoteAddress);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.jivesoftware.smack.websocket.okhttp;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -26,34 +27,39 @@ import org.jivesoftware.smack.debugger.SmackDebugger;
import okhttp3.Headers;
import okhttp3.Response;
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
import org.jxmpp.xml.splitter.XmlPrettyPrinter;
import org.jxmpp.xml.splitter.XmppXmlSplitter;
public final class LoggingInterceptor {
private static final Logger LOGGER = Logger.getLogger(LoggingInterceptor.class.getName());
private static final Logger LOGGER = Logger.getAnonymousLogger();
private static final int MAX_ELEMENT_SIZE = 64 * 1024;
private final SmackDebugger debugger;
private final XmppXmlSplitter incomingXmlSplitter;
private final XmppXmlSplitter outgoingXmlSplitter;
private final Utf8ByteXmppXmlSplitter incomingTextSplitter;
private final Utf8ByteXmppXmlSplitter outgoingTextSplitter;
LoggingInterceptor(SmackDebugger smackDebugger) {
public LoggingInterceptor(SmackDebugger smackDebugger) {
this.debugger = smackDebugger;
XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder()
.setPrettyWriter(sb -> debugger.incomingStreamSink(sb))
.setTabWidth(4)
.build();
incomingXmlSplitter = new XmppXmlSplitter(incomingTextPrinter);
XmppXmlSplitter incomingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
incomingTextPrinter);
incomingTextSplitter = new Utf8ByteXmppXmlSplitter(incomingXmlSplitter);
XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder()
.setPrettyWriter(sb -> debugger.outgoingStreamSink(sb))
.setTabWidth(4)
.build();
outgoingXmlSplitter = new XmppXmlSplitter(outgoingTextPrinter);
XmppXmlSplitter outgoingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
outgoingTextPrinter);
outgoingTextSplitter = new Utf8ByteXmppXmlSplitter(outgoingXmlSplitter);
}
// Open response received here isn't in the form of an Xml an so, there isn't much to format.
void interceptOpenResponse(Response response) {
public void interceptOpenResponse(Response response) {
Headers headers = response.headers();
Iterator<?> iterator = headers.iterator();
StringBuilder sb = new StringBuilder();
@ -64,18 +70,18 @@ public final class LoggingInterceptor {
debugger.incomingStreamSink(sb);
}
void interceptReceivedText(String text) {
public void interceptReceivedText(String text) {
try {
incomingXmlSplitter.write(text);
incomingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
} catch (IOException e) {
// Connections shouldn't be terminated due to exceptions encountered during debugging. hence only log them.
LOGGER.log(Level.WARNING, "IOException encountered while parsing received text: " + text, e);
}
}
void interceptSentText(String text) {
public void interceptSentText(String text) {
try {
outgoingXmlSplitter.write(text);
outgoingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
} catch (IOException e) {
// Connections shouldn't be terminated due to exceptions encountered during debugging, hence only log them.
LOGGER.log(Level.WARNING, "IOException encountered while parsing outgoing text: " + text, e);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,17 +16,22 @@
*/
package org.jivesoftware.smack.websocket.okhttp;
import java.net.URI;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.websocket.WebSocketException;
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.xml.XmlPullParserException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@ -38,119 +43,135 @@ public final class OkHttpWebSocket extends AbstractWebSocket {
private static final Logger LOGGER = Logger.getLogger(OkHttpWebSocket.class.getName());
private static final OkHttpClient okHttpClient = new OkHttpClient();
// This is a potential candidate to be placed into AbstractWebSocket, but I keep it here until smack-websocket-java11
// arrives.
private final SmackFuture.InternalSmackFuture<AbstractWebSocket, Exception> future = new SmackFuture.InternalSmackFuture<>();
private static OkHttpClient okHttpClient = null;
private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final LoggingInterceptor interceptor;
private final WebSocket okHttpWebSocket;
private String openStreamHeader;
private WebSocket currentWebSocket;
private WebSocketConnectionPhase phase;
private WebSocketRemoteConnectionEndpoint connectedEndpoint;
public OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(endpoint, connectionInternal);
public OkHttpWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
this.connectionInternal = connectionInternal;
if (okHttpClient == null) {
// Creates an instance of okHttp client.
OkHttpClient.Builder builder = new OkHttpClient.Builder();
okHttpClient = builder.build();
}
// Add some mechanism to enable and disable this interceptor.
if (connectionInternal.smackDebugger != null) {
interceptor = new LoggingInterceptor(connectionInternal.smackDebugger);
} else {
interceptor = null;
}
}
final URI uri = endpoint.getUri();
final String url = uri.toString();
@Override
public void connect(WebSocketRemoteConnectionEndpoint endpoint) throws InterruptedException, SmackException, XMPPException {
final String currentUri = endpoint.getWebSocketEndpoint().toString();
Request request = new Request.Builder()
.url(url)
.url(currentUri)
.header("Sec-WebSocket-Protocol", "xmpp")
.build();
okHttpWebSocket = okHttpClient.newWebSocket(request, listener);
}
WebSocketListener listener = new WebSocketListener() {
private final WebSocketListener listener = new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
LOGGER.log(Level.FINER, "OkHttp invoked onOpen() for {0}. Response: {1}",
new Object[] { webSocket, response });
if (interceptor != null) {
interceptor.interceptOpenResponse(response);
@Override
public void onOpen(WebSocket webSocket, Response response) {
LOGGER.log(Level.FINER, "WebSocket is open");
phase = WebSocketConnectionPhase.openFrameSent;
if (interceptor != null) {
interceptor.interceptOpenResponse(response);
}
send(new WebSocketOpenElement(connectionInternal.connection.getXMPPServiceDomain()));
}
future.setResult(OkHttpWebSocket.this);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
if (interceptor != null) {
interceptor.interceptReceivedText(text);
}
if (isCloseElement(text)) {
connectionInternal.onStreamClosed();
return;
}
@Override
public void onMessage(WebSocket webSocket, String text) {
if (interceptor != null) {
interceptor.interceptReceivedText(text);
String closingStream = "</stream>";
switch (phase) {
case openFrameSent:
if (isOpenElement(text)) {
// Converts the <open> element received into <stream> element.
openStreamHeader = getStreamFromOpenElement(text);
phase = WebSocketConnectionPhase.exchangingTopLevelStreamElements;
try {
connectionInternal.onStreamOpen(PacketParserUtils.getParserFor(openStreamHeader));
} catch (XmlPullParserException | IOException e) {
LOGGER.log(Level.WARNING, "Exception caught:", e);
}
} else {
LOGGER.log(Level.WARNING, "Unexpected Frame received", text);
}
break;
case exchangingTopLevelStreamElements:
connectionInternal.parseAndProcessElement(openStreamHeader + text + closingStream);
break;
default:
LOGGER.log(Level.INFO, "Default text: " + text);
}
}
onIncomingWebSocketElement(text);
}
@Override
public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
LOGGER.log(Level.FINER, "OkHttp invoked onFailure() for " + webSocket + ". Response: " + response, throwable);
WebSocketException websocketException = new WebSocketException(throwable);
// If we are already connected, then we need to notify the connection that it got tear down. Otherwise we
// need to notify the thread calling connect() that the connection failed.
if (future.wasSuccessful()) {
connectionInternal.notifyConnectionError(websocketException);
} else {
future.setException(websocketException);
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
LOGGER.log(Level.INFO, "Exception caught", t);
WebSocketException websocketException = new WebSocketException(t);
if (connectionInternal.connection.isConnected()) {
connectionInternal.notifyConnectionError(websocketException);
} else {
connectionInternal.setCurrentConnectionExceptionAndNotify(websocketException);
}
}
}
};
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
LOGGER.log(Level.FINER, "OkHttp invoked onClosing() for " + webSocket + ". Code: " + code + ". Reason: " + reason);
}
// Creates an instance of websocket through okHttpClient.
currentWebSocket = okHttpClient.newWebSocket(request, listener);
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
LOGGER.log(Level.FINER, "OkHttp invoked onClosed() for " + webSocket + ". Code: " + code + ". Reason: " + reason);
}
// Open a new stream and wait until features are received.
connectionInternal.waitForFeaturesReceived("Waiting to receive features");
};
@Override
public SmackFuture<AbstractWebSocket, Exception> getFuture() {
return future;
connectedEndpoint = endpoint;
}
@Override
public void send(String element) {
public void send(TopLevelStreamElement element) {
String textToBeSent = element.toXML().toString();
if (interceptor != null) {
interceptor.interceptSentText(element);
interceptor.interceptSentText(textToBeSent);
}
okHttpWebSocket.send(element);
currentWebSocket.send(textToBeSent);
}
@Override
public void disconnect(int code, String message) {
LOGGER.log(Level.INFO, "WebSocket closing with code: " + code + " and message: " + message);
okHttpWebSocket.close(code, message);
currentWebSocket.close(code, message);
LOGGER.log(Level.INFO, "WebSocket has been closed with message: " + message);
}
@Override
public boolean isConnectionSecure() {
return endpoint.isSecureEndpoint();
return connectedEndpoint.isSecureEndpoint();
}
@Override
public boolean isConnected() {
// TODO: Do we need this method at all if we create an AbstractWebSocket object for every endpoint?
return true;
return connectedEndpoint == null ? false : true;
}
@Override
public SSLSession getSSLSession() {
// TODO: What shall we do about this method, as it appears that OkHttp does not provide access to the used SSLSession?
return null;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus.
* Copyright 2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,13 +19,12 @@ package org.jivesoftware.smack.websocket.okhttp;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactory;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public class OkHttpWebSocketFactory implements WebSocketFactory {
@Override
public AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint, ModularXmppClientToServerConnectionInternal connectionInternal) {
return new OkHttpWebSocket(endpoint, connectionInternal);
public AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal) {
return new OkHttpWebSocket(connectionInternal);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus.
* Copyright 2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
*/
package org.jivesoftware.smack.websocket.okhttp;
import java.net.URISyntaxException;
import org.jivesoftware.smack.websocket.test.WebSocketFactoryServiceTestUtil;
import org.junit.jupiter.api.Test;
@ -25,7 +23,7 @@ import org.junit.jupiter.api.Test;
public class OkHttpWebSocketFactoryServiceTest {
@Test
public void createWebSocketTest() throws URISyntaxException {
public void createWebSocketTest() {
WebSocketFactoryServiceTestUtil.createWebSocketTest(OkHttpWebSocket.class);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus.
* Copyright 2020 Aditya Borikar, Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,29 +19,22 @@ package org.jivesoftware.smack.websocket;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateTransitionResult;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionState;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
public final class WebSocketConnectionAttemptState {
private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
private AbstractWebSocket webSocket;
private WebSocketRemoteConnectionEndpoint connectedEndpoint;
WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
EstablishingWebSocketConnectionState establishingWebSocketConnectionState) {
assert discoveredWebSocketEndpoints != null;
assert !discoveredWebSocketEndpoints.result.isEmpty();
this.connectionInternal = connectionInternal;
this.discoveredEndpoints = discoveredWebSocketEndpoints;
}
@ -51,96 +44,48 @@ public final class WebSocketConnectionAttemptState {
*
* @return {@link AbstractWebSocket} with which connection is establised
* @throws InterruptedException if the calling thread was interrupted
* @throws WebSocketException if encounters a websocket exception
*/
@SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException {
final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result;
final List<Exception> failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount());
AbstractWebSocket establishWebSocketConnection() throws InterruptedException, WebSocketException {
List<WebSocketRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
webSocket = null;
if (endpoints.isEmpty()) {
throw new WebSocketException(new Throwable("No Endpoints discovered to establish connection"));
}
SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode();
switch (securityMode) {
case required:
case ifpossible:
establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures);
if (webSocket != null) {
return null;
List<Throwable> connectionFailureList = new ArrayList<>();
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
// Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with.
for (WebSocketRemoteConnectionEndpoint endpoint : endpoints) {
try {
websocket.connect(endpoint);
connectedEndpoint = endpoint;
break;
} catch (Throwable t) {
connectionFailureList.add(t);
// If the number of entries in connectionFailureList is equal to the number of endpoints,
// it means that all endpoints have been tried and have been unsuccessful.
if (connectionFailureList.size() == endpoints.size()) {
WebSocketException websocketException = new WebSocketException(connectionFailureList);
throw new WebSocketException(websocketException);
}
}
}
establishWebSocketConnection(endpointLookupResult.discoveredInsecureEndpoints, failures);
if (webSocket != null) {
return null;
}
assert connectedEndpoint != null;
StateTransitionResult.Failure failure = FailedToConnectToAnyWebSocketEndpoint.create(failures);
return failure;
// Return connected websocket when no failure occurs.
return websocket;
}
private void establishWebSocketConnection(List<? extends WebSocketRemoteConnectionEndpoint> webSocketEndpoints,
List<Exception> failures) throws InterruptedException {
final int endpointCount = webSocketEndpoints.size();
List<SmackFuture<AbstractWebSocket, Exception>> futures = new ArrayList<>(endpointCount);
{
List<AbstractWebSocket> webSockets = new ArrayList<>(endpointCount);
// First only create the AbstractWebSocket instances, in case a constructor throws.
for (WebSocketRemoteConnectionEndpoint endpoint : webSocketEndpoints) {
AbstractWebSocket webSocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal);
webSockets.add(webSocket);
}
for (AbstractWebSocket webSocket : webSockets) {
SmackFuture<AbstractWebSocket, Exception> future = webSocket.getFuture();
futures.add(future);
}
}
SmackFuture.await(futures, connectionInternal.connection.getReplyTimeout());
for (SmackFuture<AbstractWebSocket, Exception> future : futures) {
AbstractWebSocket connectedWebSocket = future.getIfAvailable();
if (connectedWebSocket == null) {
Exception exception = future.getExceptionIfAvailable();
assert exception != null;
failures.add(exception);
continue;
}
if (webSocket == null) {
webSocket = connectedWebSocket;
// Continue here since we still need to read out the failure exceptions from potential further remaining
// futures and close remaining successfully connected ones.
continue;
}
connectedWebSocket.disconnect(1000, "Using other connection endpoint at " + webSocket.getEndpoint());
}
}
public AbstractWebSocket getConnectedWebSocket() {
return webSocket;
}
public static final class FailedToConnectToAnyWebSocketEndpoint extends StateTransitionResult.Failure {
private final List<Exception> failures;
private FailedToConnectToAnyWebSocketEndpoint(String failureMessage, List<Exception> failures) {
super(failureMessage);
this.failures = failures;
}
public List<Exception> getFailures() {
return failures;
}
private static FailedToConnectToAnyWebSocketEndpoint create(List<Exception> failures) {
StringBuilder sb = new StringBuilder(256);
StringUtils.appendTo(failures, sb, e -> sb.append(e.getMessage()));
String message = sb.toString();
return new FailedToConnectToAnyWebSocketEndpoint(message, failures);
}
/**
* Returns the connected websocket endpoint.
*
* @return connected websocket endpoint
*/
public WebSocketRemoteConnectionEndpoint getConnectedEndpoint() {
return connectedEndpoint;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,23 @@
*/
package org.jivesoftware.smack.websocket;
import java.util.Collections;
import java.util.List;
public final class WebSocketException extends Exception {
private static final long serialVersionUID = 1L;
public WebSocketException(Throwable throwable) {
super("WebSocketException: " + throwable.getMessage(), throwable);
private final List<Throwable> throwableList;
public WebSocketException(List<Throwable> throwableList) {
this.throwableList = throwableList;
}
public WebSocketException(Throwable throwable) {
this.throwableList = Collections.singletonList(throwable);
}
public List<Throwable> getThrowableList() {
return throwableList;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,16 +16,20 @@
*/
package org.jivesoftware.smack.websocket;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.AsyncButOrdered;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException;
@ -50,13 +54,13 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSock
import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement;
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.rce.InsecureWebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.SecureWebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* The websocket transport module that goes with Smack's modular architecture.
@ -97,37 +101,31 @@ public final class XmppWebSocketTransportModule
}
}
final class EstablishingWebSocketConnectionState extends State.AbstractTransport {
final class EstablishingWebSocketConnectionState extends State {
protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(websocketTransport, stateDescriptor, connectionInternal);
super(stateDescriptor, connectionInternal);
}
@Override
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws InterruptedException,
NoResponseException, NotConnectedException, SmackException, XMPPException {
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws IOException, SmackException, InterruptedException, XMPPException {
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
connectionInternal, discoveredWebSocketEndpoints, this);
StateTransitionResult.Failure failure = connectionAttemptState.establishWebSocketConnection();
if (failure != null) {
try {
websocket = connectionAttemptState.establishWebSocketConnection();
} catch (InterruptedException | WebSocketException e) {
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
return failure;
}
websocket = connectionAttemptState.getConnectedWebSocket();
connectionInternal.setTransport(websocketTransport);
// TODO: It appears this should be done in a generic way. I'd assume we always
// have to wait for stream features after the connection was established. But I
// am not yet 100% positive that this is the case for every transport. Hence keep it here for now(?).
// See also similar comment in XmppTcpTransportModule.
// Maybe move this into ConnectedButUnauthenticated state's transitionInto() method? That seems to be the
// right place.
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
WebSocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint();
// Construct a WebSocketConnectedResult using the connected endpoint.
return new WebSocketConnectedResult(websocket.getEndpoint());
return new WebSocketConnectedResult(connectedEndpoint);
}
}
@ -142,7 +140,7 @@ public final class XmppWebSocketTransportModule
final WebSocketRemoteConnectionEndpoint connectedEndpoint;
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
super("WebSocket connection establised with endpoint: " + connectedEndpoint);
super("WebSocket connection establised with endpoint: " + connectedEndpoint.getWebSocketEndpoint());
this.connectedEndpoint = connectedEndpoint;
}
}
@ -166,12 +164,6 @@ public final class XmppWebSocketTransportModule
discoveredWebSocketEndpoints = null;
}
@Override
public boolean hasUseableConnectionEndpoints() {
return discoveredWebSocketEndpoints != null;
}
@SuppressWarnings("incomplete-switch")
@Override
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup.
@ -180,56 +172,51 @@ public final class XmppWebSocketTransportModule
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
connectionInternal.asyncGo(() -> {
Result result = null;
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
DomainBareJid host = configuration.getXMPPServiceDomain();
WebSocketRemoteConnectionEndpoint providedEndpoint = null;
if (moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
// Fetch remote endpoints.
result = WebSocketRemoteConnectionEndpointLookup.lookup(host);
// Check if there is a websocket endpoint already configured.
URI uri = moduleDescriptor.getExplicitlyProvidedUri();
if (uri != null) {
providedEndpoint = new WebSocketRemoteConnectionEndpoint(uri);
}
WebSocketRemoteConnectionEndpoint providedEndpoint = moduleDescriptor.getExplicitlyProvidedEndpoint();
if (providedEndpoint != null) {
// If there was not automatic lookup that produced a result, then create a result now.
if (result == null) {
result = new Result();
if (!moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
// If discovery is disabled, assert that the provided endpoint isn't null.
assert providedEndpoint != null;
SecurityMode mode = connectionInternal.connection.getConfiguration().getSecurityMode();
if ((providedEndpoint.isSecureEndpoint() &&
mode.equals(SecurityMode.disabled))
|| (!providedEndpoint.isSecureEndpoint() &&
mode.equals(SecurityMode.required))) {
throw new IllegalStateException("Explicitly configured uri: " + providedEndpoint.getWebSocketEndpoint().toString()
+ " does not comply with the configured security mode: " + mode);
}
// We insert the provided endpoint at the beginning of the list, so that it is used first.
final int INSERT_INDEX = 0;
if (providedEndpoint instanceof SecureWebSocketRemoteConnectionEndpoint) {
SecureWebSocketRemoteConnectionEndpoint secureEndpoint = (SecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
result.discoveredSecureEndpoints.add(INSERT_INDEX, secureEndpoint);
} else if (providedEndpoint instanceof InsecureWebSocketRemoteConnectionEndpoint) {
InsecureWebSocketRemoteConnectionEndpoint insecureEndpoint = (InsecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
result.discoveredInsecureEndpoints.add(INSERT_INDEX, insecureEndpoint);
} else {
throw new AssertionError();
}
}
// Generate Result for explicitly configured endpoint.
Result manualResult = new Result(Arrays.asList(providedEndpoint), null);
if (moduleDescriptor.isImplicitWebSocketEndpointEnabled()) {
String urlWithoutScheme = "://" + host + ":5443/ws";
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(manualResult);
SecureWebSocketRemoteConnectionEndpoint implicitSecureEndpoint = SecureWebSocketRemoteConnectionEndpoint.from(
WebSocketRemoteConnectionEndpoint.SECURE_WEB_SOCKET_SCHEME + urlWithoutScheme);
result.discoveredSecureEndpoints.add(implicitSecureEndpoint);
InsecureWebSocketRemoteConnectionEndpoint implicitInsecureEndpoint = InsecureWebSocketRemoteConnectionEndpoint.from(
WebSocketRemoteConnectionEndpoint.INSECURE_WEB_SOCKET_SCHEME + urlWithoutScheme);
result.discoveredInsecureEndpoints.add(implicitInsecureEndpoint);
}
final LookupConnectionEndpointsResult endpointsResult;
if (result.isEmpty()) {
endpointsResult = new WebSocketEndpointsDiscoveryFailed(result.lookupFailures);
websocketEndpointsLookupFuture.setResult(endpointsResult);
} else {
endpointsResult = new DiscoveredWebSocketEndpoints(result);
}
DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain();
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
SecurityMode mode = configuration.getSecurityMode();
websocketEndpointsLookupFuture.setResult(endpointsResult);
// Fetch remote endpoints.
Result xep0156result = WebSocketRemoteConnectionEndpointLookup.lookup(host, mode);
List<WebSocketRemoteConnectionEndpoint> discoveredEndpoints = xep0156result.discoveredRemoteConnectionEndpoints;
// Generate result considering both manual and fetched endpoints.
Result finalResult = new Result(discoveredEndpoints, xep0156result.getLookupFailures());
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(finalResult);
websocketEndpointsLookupFuture.setResult(endpointsResult);
}
});
return Collections.singletonList(websocketEndpointsLookupFuture);
@ -251,11 +238,11 @@ public final class XmppWebSocketTransportModule
@Override
protected void notifyAboutNewOutgoingElements() {
final Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> {
for (TopLevelStreamElement topLevelStreamElement; (topLevelStreamElement = outgoingElementsQueue.poll()) != null;) {
websocket.send(topLevelStreamElement);
}
// Once new outgoingElement is notified, send the top level stream element obtained by polling.
TopLevelStreamElement topLevelStreamElement = outgoingElementsQueue.poll();
websocket.send(topLevelStreamElement);
});
}
@ -281,11 +268,15 @@ public final class XmppWebSocketTransportModule
@Override
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
// TODO: Create extra class for this?
return new StreamOpenAndCloseFactory() {
@Override
public AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
return new WebSocketOpenElement(to);
public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
try {
return new WebSocketOpenElement(JidCreate.domainBareFrom(to));
} catch (XmppStringprepException e) {
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
return null;
}
}
@Override
public AbstractStreamClose createStreamClose() {
@ -304,6 +295,10 @@ public final class XmppWebSocketTransportModule
assert result != null;
this.result = result;
}
public WebSocketRemoteConnectionEndpointLookup.Result getResult() {
return result;
}
}
/**
@ -313,13 +308,10 @@ public final class XmppWebSocketTransportModule
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
WebSocketEndpointsDiscoveryFailed(RemoteConnectionEndpointLookupFailure lookupFailure) {
this(Collections.singletonList(lookupFailure));
}
WebSocketEndpointsDiscoveryFailed(List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
assert lookupFailures != null;
this.lookupFailures = Collections.unmodifiableList(lookupFailures);
WebSocketEndpointsDiscoveryFailed(
WebSocketRemoteConnectionEndpointLookup.Result result) {
assert result != null;
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
}
@Override

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,7 +21,6 @@ import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
@ -30,7 +29,6 @@ import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionIn
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
/**
* The descriptor class for {@link XmppWebSocketTransportModule}.
@ -39,43 +37,12 @@ import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
*/
public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private final boolean performWebSocketEndpointDiscovery;
private final boolean implicitWebSocketEndpoint;
private final URI uri;
private final WebSocketRemoteConnectionEndpoint wsRce;
private boolean performWebSocketEndpointDiscovery;
private URI uri;
public XmppWebSocketTransportModuleDescriptor(Builder builder) {
this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery;
this.implicitWebSocketEndpoint = builder.implicitWebSocketEndpoint;
this.uri = builder.uri;
if (uri != null) {
wsRce = WebSocketRemoteConnectionEndpoint.from(uri);
} else {
wsRce = null;
}
}
@Override
@SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
protected void validateConfiguration(ModularXmppClientToServerConnectionConfiguration configuration) {
if (wsRce == null) {
return;
}
SecurityMode securityMode = configuration.getSecurityMode();
switch (securityMode) {
case required:
if (!wsRce.isSecureEndpoint()) {
throw new IllegalArgumentException("The provided WebSocket endpoint " + wsRce + " is not a secure endpoint, but the connection configuration requires secure endpoints");
}
break;
case disabled:
if (wsRce.isSecureEndpoint()) {
throw new IllegalArgumentException("The provided WebSocket endpoint " + wsRce + " is a secure endpoint, but the connection configuration has security disabled");
}
break;
}
}
/**
@ -86,10 +53,6 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
return performWebSocketEndpointDiscovery;
}
public boolean isImplicitWebSocketEndpointEnabled() {
return implicitWebSocketEndpoint;
}
/**
* Returns explicitly configured websocket endpoint uri.
* @return uri
@ -98,10 +61,6 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
return uri;
}
WebSocketRemoteConnectionEndpoint getExplicitlyProvidedEndpoint() {
return wsRce;
}
@Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
@ -140,7 +99,6 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
*/
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
private boolean performWebSocketEndpointDiscovery = true;
private boolean implicitWebSocketEndpoint = true;
private URI uri;
private Builder(
@ -161,20 +119,15 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebSocketEndpoint(endpointUri);
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, true);
}
public Builder explicitlySetWebSocketEndpointAndDiscovery(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery);
}
public Builder disableImplicitWebsocketEndpoint() {
implicitWebSocketEndpoint = false;
return this;
}
@Override
public ModularXmppClientToServerConnectionModuleDescriptor build() {
return new XmppWebSocketTransportModuleDescriptor(this);

View file

@ -0,0 +1,47 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.elements;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.DomainBareJid;
public abstract class AbstractWebSocketNonza implements Nonza {
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
private static final String VERSION = "1.0";
private final DomainBareJid to;
public AbstractWebSocketNonza(DomainBareJid jid) {
this.to = jid;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.attribute("to", to.toString());
xml.attribute("version", VERSION);
xml.closeEmptyElement();
return xml;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,92 +18,40 @@ package org.jivesoftware.smack.websocket.impl;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public abstract class AbstractWebSocket {
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
protected final WebSocketRemoteConnectionEndpoint endpoint;
protected AbstractWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
ModularXmppClientToServerConnectionInternal connectionInternal) {
this.endpoint = endpoint;
this.connectionInternal = connectionInternal;
protected enum WebSocketConnectionPhase {
openFrameSent,
exchangingTopLevelStreamElements
}
public final WebSocketRemoteConnectionEndpoint getEndpoint() {
return endpoint;
}
private String streamOpen;
private String streamClose;
protected final void onIncomingWebSocketElement(String element) {
// TODO: Once smack-websocket-java15 is there, we have to re-evaluate if the async operation here is still
// required, or if it should only be performed if OkHTTP is used.
if (isOpenElement(element)) {
// Transform the XMPP WebSocket <open/> element to a RFC 6120 <stream> open tag.
streamOpen = getStreamFromOpenElement(element);
streamClose = connectionInternal.onStreamOpen(streamOpen);
return;
}
if (isCloseElement(element)) {
connectionInternal.onStreamClosed();
return;
}
connectionInternal.withSmackDebugger(debugger -> debugger.onIncomingElementCompleted());
// TODO: Do we need to wrap the element again in the stream open to get the
// correct XML scoping (just like the modular TCP connection does)? It appears
// that this not really required, as onStreamOpen() will set the incomingStreamEnvironment, which is used for
// parsing.
String wrappedCompleteElement = streamOpen + element + streamClose;
connectionInternal.parseAndProcessElement(wrappedCompleteElement);
}
static String getStreamFromOpenElement(String openElement) {
protected static String getStreamFromOpenElement(String openElement) {
String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
.replaceFirst("/>\\s*\\z", ">");
return streamElement;
}
// TODO: Make this method less fragile, e.g. by parsing a little bit into the element to ensure that this is an
// <open/> element qualified by the correct namespace.
static boolean isOpenElement(String text) {
protected static boolean isOpenElement(String text) {
if (text.startsWith("<open ")) {
return true;
}
return false;
}
// TODO: Make this method less fragile, e.g. by parsing a little bit into the element to ensure that this is an
// <close/> element qualified by the correct namespace. The fragility comes due the fact that the element could,
// inter alia, be specified as
// <close:close xmlns:close="urn:ietf:params:xml:ns:xmpp-framing"/>
static boolean isCloseElement(String text) {
protected static boolean isCloseElement(String text) {
if (text.startsWith("<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>")) {
return true;
}
return false;
}
public abstract SmackFuture<AbstractWebSocket, Exception> getFuture();
public abstract void connect(WebSocketRemoteConnectionEndpoint endpoint) throws Throwable;
public final void send(TopLevelStreamElement element) {
XmlEnvironment outgoingStreamXmlEnvironment = connectionInternal.getOutgoingStreamXmlEnvironment();
String elementString = element.toXML(outgoingStreamXmlEnvironment).toString();
send(elementString);
}
protected abstract void send(String element);
public abstract void send(TopLevelStreamElement element);
public abstract void disconnect(int code, String message);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus.
* Copyright 2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,11 +17,9 @@
package org.jivesoftware.smack.websocket.impl;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public interface WebSocketFactory {
AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint,
ModularXmppClientToServerConnectionInternal connectionInternal);
AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus.
* Copyright 2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,14 +20,12 @@ import java.util.Iterator;
import java.util.ServiceLoader;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public final class WebSocketFactoryService {
private static final ServiceLoader<WebSocketFactory> SERVICE_LOADER = ServiceLoader.load(WebSocketFactory.class);
public static AbstractWebSocket createWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
ModularXmppClientToServerConnectionInternal connectionInternal) {
public static AbstractWebSocket createWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
assert connectionInternal != null;
Iterator<WebSocketFactory> websocketFactories = SERVICE_LOADER.iterator();
@ -36,7 +34,7 @@ public final class WebSocketFactoryService {
}
WebSocketFactory websocketFactory = websocketFactories.next();
return websocketFactory.create(endpoint, connectionInternal);
return websocketFactory.create(connectionInternal);
}
}

View file

@ -1,39 +0,0 @@
/**
*
* Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.rce;
import java.net.URI;
public class InsecureWebSocketRemoteConnectionEndpoint extends WebSocketRemoteConnectionEndpoint {
protected InsecureWebSocketRemoteConnectionEndpoint(URI uri) {
super(uri);
}
@Override
public final boolean isSecureEndpoint() {
return false;
}
public static final InsecureWebSocketRemoteConnectionEndpoint from(CharSequence cs) {
URI uri = URI.create(cs.toString());
if (!uri.getScheme().equals(INSECURE_WEB_SOCKET_SCHEME)) {
throw new IllegalArgumentException(uri + " is not a insecure WebSocket");
}
return new InsecureWebSocketRemoteConnectionEndpoint(uri);
}
}

View file

@ -1,39 +0,0 @@
/**
*
* Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.rce;
import java.net.URI;
public class SecureWebSocketRemoteConnectionEndpoint extends WebSocketRemoteConnectionEndpoint {
protected SecureWebSocketRemoteConnectionEndpoint(URI uri) {
super(uri);
}
@Override
public final boolean isSecureEndpoint() {
return true;
}
public static final SecureWebSocketRemoteConnectionEndpoint from(CharSequence cs) {
URI uri = URI.create(cs.toString());
if (!uri.getScheme().equals(SECURE_WEB_SOCKET_SCHEME)) {
throw new IllegalArgumentException(uri + " is not a secure WebSocket");
}
return new SecureWebSocketRemoteConnectionEndpoint(uri);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Aditya Borikar, Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,100 +20,66 @@ import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
public abstract class WebSocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
public static final String INSECURE_WEB_SOCKET_SCHEME = "ws";
public static final String SECURE_WEB_SOCKET_SCHEME = INSECURE_WEB_SOCKET_SCHEME + "s";
public final class WebSocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
private static final Logger LOGGER = Logger.getAnonymousLogger();
private final URI uri;
private final UInt16 port;
protected WebSocketRemoteConnectionEndpoint(URI uri) {
public WebSocketRemoteConnectionEndpoint(String uri) throws URISyntaxException {
this(new URI(uri));
}
public WebSocketRemoteConnectionEndpoint(URI uri) {
this.uri = uri;
int portInt = uri.getPort();
if (portInt >= 0) {
port = UInt16.from(portInt);
} else {
port = null;
String scheme = uri.getScheme();
if (!(scheme.equals("ws") || scheme.equals("wss"))) {
throw new IllegalArgumentException("Only allowed protocols are ws and wss");
}
}
public final URI getUri() {
public URI getWebSocketEndpoint() {
return uri;
}
public boolean isSecureEndpoint() {
if (uri.getScheme().equals("wss")) {
return true;
}
return false;
}
@Override
public final String getHost() {
public CharSequence getHost() {
return uri.getHost();
}
@Override
public UInt16 getPort() {
return port;
}
public abstract boolean isSecureEndpoint();
private List<? extends InetAddress> inetAddresses;
private void resolveInetAddressesIfRequired() {
if (inetAddresses != null) {
return;
}
String host = getHost();
InetAddress[] addresses;
try {
addresses = InetAddress.getAllByName(host);
} catch (UnknownHostException e) {
LOGGER.log(Level.WARNING, "Could not resolve IP addresses of " + host, e);
return;
}
inetAddresses = Arrays.asList(addresses);
return UInt16.from(uri.getPort());
}
@Override
public Collection<? extends InetAddress> getInetAddresses() {
resolveInetAddressesIfRequired();
return inetAddresses;
try {
InetAddress address = InetAddress.getByName(getHost().toString());
return Collections.singletonList(address);
} catch (UnknownHostException e) {
LOGGER.log(Level.INFO, "Unknown Host Exception ", e);
}
return null;
}
@Override
public String getDescription() {
return null;
}
@Override
public String toString() {
return uri.toString();
}
public static WebSocketRemoteConnectionEndpoint from(CharSequence uriCharSequence) throws URISyntaxException {
String uriString = uriCharSequence.toString();
URI uri = URI.create(uriString);
return from(uri);
}
public static WebSocketRemoteConnectionEndpoint from(URI uri) {
String scheme = uri.getScheme();
switch (scheme) {
case INSECURE_WEB_SOCKET_SCHEME:
return new InsecureWebSocketRemoteConnectionEndpoint(uri);
case SECURE_WEB_SOCKET_SCHEME:
return new SecureWebSocketRemoteConnectionEndpoint(uri);
default:
throw new IllegalArgumentException("Only allowed protocols are " + INSECURE_WEB_SOCKET_SCHEME + " and " + SECURE_WEB_SOCKET_SCHEME);
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,9 +20,10 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.altconnections.HttpLookupMethod;
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
@ -32,8 +33,9 @@ import org.jxmpp.jid.DomainBareJid;
public final class WebSocketRemoteConnectionEndpointLookup {
public static Result lookup(DomainBareJid domainBareJid) {
public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) {
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
List<URI> rcUriList = null;
try {
@ -43,69 +45,67 @@ public final class WebSocketRemoteConnectionEndpointLookup {
} catch (IOException | XmlPullParserException | URISyntaxException e) {
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
domainBareJid, e));
return new Result(lookupFailures);
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
}
List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints = new ArrayList<>(rcUriList.size());
List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints = new ArrayList<>(rcUriList.size());
for (URI webSocketUri : rcUriList) {
WebSocketRemoteConnectionEndpoint wsRce = WebSocketRemoteConnectionEndpoint.from(webSocketUri);
if (wsRce instanceof SecureWebSocketRemoteConnectionEndpoint) {
SecureWebSocketRemoteConnectionEndpoint secureWsRce = (SecureWebSocketRemoteConnectionEndpoint) wsRce;
discoveredSecureEndpoints.add(secureWsRce);
} else if (wsRce instanceof InsecureWebSocketRemoteConnectionEndpoint) {
InsecureWebSocketRemoteConnectionEndpoint insecureWsRce = (InsecureWebSocketRemoteConnectionEndpoint) wsRce;
discoveredInsecureEndpoints.add(insecureWsRce);
} else {
// WebSocketRemoteConnectionEndpoint.from() must return an instance which type is one of the above.
throw new AssertionError();
}
if (rcUriList.isEmpty()) {
throw new IllegalStateException("No endpoints were found inside host-meta");
}
return new Result(discoveredSecureEndpoints, discoveredInsecureEndpoints, lookupFailures);
// Convert rcUriList to List<WebSocketRemoteConnectionEndpoint>
Iterator<URI> iterator = rcUriList.iterator();
List<WebSocketRemoteConnectionEndpoint> rceList = new ArrayList<>();
while (iterator.hasNext()) {
rceList.add(new WebSocketRemoteConnectionEndpoint(iterator.next()));
}
switch (securityMode) {
case ifpossible:
// If security mode equals `if-possible`, give priority to secure endpoints over insecure endpoints.
// Seprate secure and unsecure endpoints.
List<WebSocketRemoteConnectionEndpoint> secureEndpointsForSecurityModeIfPossible = new ArrayList<>();
List<WebSocketRemoteConnectionEndpoint> insecureEndpointsForSecurityModeIfPossible = new ArrayList<>();
for (WebSocketRemoteConnectionEndpoint uri : rceList) {
if (uri.isSecureEndpoint()) {
secureEndpointsForSecurityModeIfPossible.add(uri);
} else {
insecureEndpointsForSecurityModeIfPossible.add(uri);
}
}
discoveredRemoteConnectionEndpoints = secureEndpointsForSecurityModeIfPossible;
discoveredRemoteConnectionEndpoints.addAll(insecureEndpointsForSecurityModeIfPossible);
break;
case required:
case disabled:
/**
* If, SecurityMode equals to required, accept wss endpoints (secure endpoints) only or,
* if SecurityMode equals to disabled, accept ws endpoints (unsecure endpoints) only.
*/
for (WebSocketRemoteConnectionEndpoint uri : rceList) {
if ((securityMode.equals(SecurityMode.disabled) && !uri.isSecureEndpoint())
|| (securityMode.equals(SecurityMode.required) && uri.isSecureEndpoint())) {
discoveredRemoteConnectionEndpoints.add(uri);
}
}
break;
default:
}
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
}
public static final class Result {
public final List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints;
public final List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints;
public final List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints;
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
public Result() {
this(Collections.emptyList());
}
public Result(List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
// The list of endpoints needs to be mutable, because maybe a user supplied endpoint will be added to it.
// Hence we do not use Collections.emptyList() as argument for the discovered endpoints.
this(new ArrayList<>(1), new ArrayList<>(1), lookupFailures);
}
public Result(List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints,
List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints,
public Result(List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints,
List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
this.discoveredSecureEndpoints = discoveredSecureEndpoints;
this.discoveredInsecureEndpoints = discoveredInsecureEndpoints;
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
this.lookupFailures = lookupFailures;
}
public boolean isEmpty() {
return discoveredSecureEndpoints.isEmpty() && discoveredInsecureEndpoints.isEmpty();
}
public int discoveredEndpointCount() {
return discoveredSecureEndpoints.size() + discoveredInsecureEndpoints.size();
}
// TODO: Remove the following methods since the fields are already public? Or make the fields private and use
// the methods? I tend to remove the methods, as their method name is pretty long. But OTOH the fields reference
// mutable datastructes, which is uncommon to be public.
public List<SecureWebSocketRemoteConnectionEndpoint> getDiscoveredSecureRemoteConnectionEndpoints() {
return discoveredSecureEndpoints;
}
public List<InsecureWebSocketRemoteConnectionEndpoint> getDiscoveredInsecureRemoteConnectionEndpoints() {
return discoveredInsecureEndpoints;
public List<WebSocketRemoteConnectionEndpoint> getDiscoveredRemoteConnectionEndpoints() {
return discoveredRemoteConnectionEndpoints;
}
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {

View file

@ -17,15 +17,24 @@
package org.jivesoftware.smack.websocket;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure.HttpLookupFailure;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSocketTransport.WebSocketEndpointsDiscoveryFailed;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
import org.junit.jupiter.api.Test;
import org.jxmpp.stringprep.XmppStringprepException;
@ -55,6 +64,42 @@ public class XmppWebSocketTransportModuleTest {
assertNotNull(websocketTransportModuleDescriptor);
}
@Test
public void websocketEndpointDiscoveryTest() throws URISyntaxException {
XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor();
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
XmppWebSocketTransportModule transportModule
= new XmppWebSocketTransportModule(websocketTransportModuleDescriptor, connectionInternal);
XmppWebSocketTransportModule.XmppWebSocketTransport transport = transportModule.getTransport();
assertThrows(AssertionError.class, () -> transport.new DiscoveredWebSocketEndpoints(null));
assertThrows(AssertionError.class, () -> transport.new WebSocketEndpointsDiscoveryFailed(null));
WebSocketRemoteConnectionEndpoint endpoint = new WebSocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
discoveredRemoteConnectionEndpoints.add(endpoint);
HttpLookupFailure httpLookupFailure = new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(null, null);
List<RemoteConnectionEndpointLookupFailure> failureList = new ArrayList<>();
failureList.add(httpLookupFailure);
Result result = new Result(discoveredRemoteConnectionEndpoints, failureList);
DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints = transport.new DiscoveredWebSocketEndpoints(result);
assertNotNull(discoveredWebSocketEndpoints.getResult());
WebSocketEndpointsDiscoveryFailed endpointsDiscoveryFailed = transport.new WebSocketEndpointsDiscoveryFailed(result);
assertNotNull(endpointsDiscoveryFailed.toString());
}
@Test
public void websocketConnectedResultTest() throws URISyntaxException {
WebSocketRemoteConnectionEndpoint connectedEndpoint = new WebSocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
assertNotNull(new XmppWebSocketTransportModule.WebSocketConnectedResult(connectedEndpoint));
}
@Test
public void lookupConnectionEndpointsTest() throws URISyntaxException {
XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar, 2021 Florian Schmaus
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,22 +26,20 @@ import org.jivesoftware.smack.datatypes.UInt16;
import org.junit.jupiter.api.Test;
public class WebSocketRemoteConnectionEndpointTest {
@Test
public void endpointTest() throws URISyntaxException {
String endpointString = "ws://fooDomain.org:7070/ws/";
WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from(endpointString);
WebSocketRemoteConnectionEndpoint endpoint = new WebSocketRemoteConnectionEndpoint(endpointString);
assertEquals("fooDomain.org", endpoint.getHost());
assertEquals(UInt16.from(7070), endpoint.getPort());
assertEquals(endpointString, endpoint.getUri().toString());
assertEquals(endpointString, endpoint.getWebSocketEndpoint().toString());
}
@Test
public void faultyEndpointTest() {
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
assertThrows(IllegalArgumentException.class, () -> {
WebSocketRemoteConnectionEndpoint.from(faultyProtocolString);
new WebSocketRemoteConnectionEndpoint(faultyProtocolString);
});
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021 Florian Schmaus.
* Copyright 2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,21 +20,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import java.net.URISyntaxException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public class WebSocketFactoryServiceTestUtil {
public static void createWebSocketTest(Class<? extends AbstractWebSocket> expected) throws URISyntaxException {
WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from("wss://example.org");
public static void createWebSocketTest(Class<? extends AbstractWebSocket> expected) {
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal);
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
assertEquals(expected, websocket.getClass());
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus.
* Copyright 2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -75,10 +75,6 @@ public interface XmlPullParser {
String getNamespace(String prefix);
default String getDefaultNamespace() {
return getNamespace(null);
}
int getDepth();
String getPositionDescription();