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

Compare commits

...

25 commits

Author SHA1 Message Date
Aditya Borikar
1df0763f92 Remove AbstractWebsocketNonza since it is no longer needed 2021-01-25 20:31:51 +01:00
Florian Schmaus
c5a546554b Rework WebSocket code
Related to SMACK-835.
2021-01-25 19:56:54 +01:00
Florian Schmaus
0c013e4f29 Merge branch '4.4' 2021-01-25 19:48:16 +01:00
Florian Schmaus
9ab50c374d [extensions] Use StanzaView as parameter type in BoBDataExtension.from()
BoBDataExtensions can also appear in IQs (Registration IQ), and
potentially also Presence stanzas.

Fixes SMACK-901.
2021-01-25 14:29:21 +01:00
Florian Schmaus
84d73e9623 [core] Fix XmlEnvironment namespace: Use default namespace (not element's)
Assume the element
<foo:bar xmlns='namespace' xmlns:foo='foo-namespace'/>

then the <bar/> element's namespace is 'foo-namespace', but the
default namespace is 'namespace'. And this is the namespace that
scopes into inner elements.
2021-01-25 14:24:55 +01:00
Florian Schmaus
ec80ab4a20
Merge pull request #456 from Flowdalic/coveralls
[gradle] Bump coveralls-gradle-plugin to 2.10.2
2021-01-12 16:04:22 +01:00
Florian Schmaus
9fb4a2bf4f [gradle] Bump coveralls-gradle-plugin to 2.10.2
This should make it work with Github Actions.
2021-01-12 13:13:16 +01:00
Florian Schmaus
ec2ef1facc
Merge pull request #455 from Flowdalic/fix-data-form-npe
[xdata] Fix NPE in DataForm.Builder.addItem()
2021-01-12 12:31:17 +01:00
Florian Schmaus
d64a9d9029 [xdata] Fix NPE in DataForm.Builder.addItem()
Fixes SMACK-900.
2021-01-12 09:41:46 +01:00
Florian Schmaus
1bfb9f34f1 Merge branch '4.4' 2021-01-12 09:22:42 +01:00
Florian Schmaus
9d734c4a3e
Merge pull request #454 from Flowdalic/readme-build-status-github-actions
[README] Switch "build status" badge from Travis to Github Actions
2021-01-12 09:21:54 +01:00
Florian Schmaus
3a661d71b9
Merge pull request #453 from Flowdalic/fix-caps-npe
[caps] Check for null in EntityCapsManager.addCapsExtension()
2021-01-12 09:12:16 +01:00
Florian Schmaus
8cc4ba0a42 [README] Switch "build status" badge from Travis to Github Actions
This also renames the CI workflow from "Smack CI" to just "CI".
2021-01-12 09:08:34 +01:00
Florian Schmaus
df3ca4100b [caps] Check for null in EntityCapsManager.addCapsExtension()
To avoid a NPE, we check for null in addCapsExtension.

Fixes SMACK-899.
2021-01-12 09:01:19 +01:00
Florian Schmaus
c431d84154 Merge branch '4.4' 2021-01-10 20:45:32 +01:00
Florian Schmaus
938a4271f3
Merge pull request #452 from Flowdalic/abstract-provider-element-type
[core] AbstractProvider should also consider TypeVariable
2021-01-10 20:45:12 +01:00
Florian Schmaus
6eda93228f [core] AbstractProvider should also consider TypeVariable
aTalk shows the following exception:

2020-12-14 12:11:13.704 7370-30976/org.atalk.android E/AndroidRuntime: FATAL EXCEPTION: AccountManager.loadStoredAccounts
    Process: org.atalk.android, PID: 7370
    java.lang.AssertionError: Element type 'EE' is neither of type Class or ParameterizedType
        at org.jivesoftware.smack.provider.AbstractProvider.<init>(AbstractProvider.java:46)
        at org.jivesoftware.smack.provider.Provider.<init>(Provider.java:40)
        at org.jivesoftware.smack.provider.ExtensionElementProvider.<init>(ExtensionElementProvider.java:29)
        at org.xmpp.extensions.DefaultExtensionElementProvider.<init>(DefaultExtensionElementProvider.java:43)
        at org.xmpp.extensions.coin.CoinIQProvider.<init>(CoinIQProvider.java:46)
        at net.java.sip.communicator.impl.protocol.jabber.ProtocolProviderServiceJabberImpl.initialize(ProtocolProviderServiceJabberImpl.java:2091)
        at net.java.sip.communicator.impl.protocol.jabber.ProtocolProviderFactoryJabberImpl.createService(ProtocolProviderFactoryJabberImpl.java:121)
        at net.java.sip.communicator.service.protocol.ProtocolProviderFactory.loadAccount(ProtocolProviderFactory.java:934)
        at net.java.sip.communicator.service.protocol.AccountManager.doLoadStoredAccounts(AccountManager.java:139)
        at net.java.sip.communicator.service.protocol.AccountManager.loadStoredAccounts(AccountManager.java:294)
        at net.java.sip.communicator.service.protocol.AccountManager.runInLoadStoredAccountsThread(AccountManager.java:394)
        at net.java.sip.communicator.service.protocol.AccountManager.access$000(AccountManager.java:36)
        at
		net.java.sip.communicator.service.protocol.AccountManager$1.run(AccountManager.java:329)

where CoinIQProvider line 46-47 [1] reads

    private final DefaultExtensionElementProvider<URIsExtension> urisProvider
            = new
			DefaultExtensionElementProvider<>(URIsExtension.class);

This fixes SMACK-898.

1: f61f264312/aTalk/src/main/java/org/xmpp/extensions/coin/CoinIQProvider.java (L47)
2021-01-10 20:37:01 +01:00
Florian Schmaus
1d5949de4d
Merge pull request #451 from Flowdalic/github-actions
Add GitHub CI actions
2021-01-10 20:36:29 +01:00
Dan Caseley
5b73f2c061 Add Github Actions CI workflow, drop Travis CI
Modified-by: Florian Schmaus <flo@geekplace.eu>
2021-01-10 20:28:34 +01:00
Florian Schmaus
35a71f0131 Merge branch '4.4' 2021-01-06 13:51:38 +01:00
Florian Schmaus
65d7c1c1b8 [im] Update DirectoryRosterStore TODO note regarding Android API 26. 2021-01-06 13:48:49 +01:00
Florian Schmaus
40aa9e87b7 [im] DirectoryRosterStore.readEntry() should also catch IllegalArgumentException
Fixes SMACK-897.
2021-01-06 13:48:03 +01:00
Florian Schmaus
d64ee785bd [extensions] Add BoBDataExtension getBobData() and getContentId()
Those two methods where missing after the BoB API redesign.

Fixes SMACK-896.
2021-01-06 13:47:09 +01:00
Florian Schmaus
fcc372754e Fix NPE in BoBIQ by adding XmlStringBuilder.optIntAttribute(String, Integer)
The method was missing and hence BoBIQ used optIntAttribute(String,
int) instead, which resulted in an NPE if the Integer was null.

Fixes SMACK-895.
2021-01-06 13:45:13 +01:00
Florian Schmaus
ca3679add9 Smack 4.4.1-SNAPSHOT 2020-12-06 12:15:19 +01:00
52 changed files with 1149 additions and 615 deletions

97
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,97 @@
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

View file

@ -1,52 +0,0 @@
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 Smack
===== =====
[![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) [![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)
About About
----- -----

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2009 Jive Software, 2018-2020 Florian Schmaus. * Copyright 2009 Jive Software, 2018-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -2201,18 +2201,29 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return SMACK_REACTOR.schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking); return SMACK_REACTOR.schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking);
} }
protected void onStreamOpen(XmlPullParser parser) { /**
// We found an opening stream. * Must be called when a XMPP stream open tag is encountered. Sets values like the stream ID and the incoming stream
if ("jabber:client".equals(parser.getNamespace(null))) { * XML environment.
streamId = parser.getAttributeValue("", "id"); * <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());
streamId = parser.getAttributeValue("id");
incomingStreamXmlEnvironment = XmlEnvironment.from(parser); incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
String reportedServerDomainString = parser.getAttributeValue("", "from"); 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 // 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. // in c2s connections is required or not.
return; if (reportedServerDomainString != null) {
}
DomainBareJid reportedServerDomain; DomainBareJid reportedServerDomain;
try { try {
reportedServerDomain = JidCreate.domainBareFrom(reportedServerDomainString); reportedServerDomain = JidCreate.domainBareFrom(reportedServerDomainString);
@ -2226,6 +2237,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
+ "' as reported by server could not be transformed to a valid JID", e); + "' 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 { protected final void sendStreamOpen() throws NotConnectedException, InterruptedException {
@ -2233,7 +2250,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 // 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 // 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.) // response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
CharSequence to = getXMPPServiceDomain(); DomainBareJid to = getXMPPServiceDomain();
CharSequence from = null; CharSequence from = null;
CharSequence localpart = config.getUsername(); CharSequence localpart = config.getUsername();
if (localpart != null) { if (localpart != null) {
@ -2247,7 +2264,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen); updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
} }
protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) { protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
return new StreamOpen(to, from, id, lang); return new StreamOpen(to, from, id, lang);
} }

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2018-2020 Florian Schmaus * Copyright 2018-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -139,13 +139,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
} }
@Override @Override
public void setCurrentConnectionExceptionAndNotify(Exception exception) { public String onStreamOpen(XmlPullParser parser) {
ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception); return ModularXmppClientToServerConnection.this.onStreamOpen(parser);
}
@Override
public void onStreamOpen(XmlPullParser parser) {
ModularXmppClientToServerConnection.this.onStreamOpen(parser);
} }
@Override @Override
@ -571,7 +566,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
} }
@Override @Override
protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) { protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory(); StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang); return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
} }
@ -720,6 +715,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures); 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 // 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() // 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. // function to close the queue again on failure.
@ -1110,7 +1110,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
XmppClientToServerTransport.Stats stats = entry.getValue(); XmppClientToServerTransport.Stats stats = entry.getValue();
StringUtils.appendHeading(appendable, transportClass.getName()); StringUtils.appendHeading(appendable, transportClass.getName());
appendable.append(stats.toString()).append('\n'); if (stats != null) {
appendable.append(stats.toString());
} else {
appendable.append("No stats available.");
}
appendable.append('\n');
} }
for (Map.Entry<String, Object> entry : filtersStats.entrySet()) { for (Map.Entry<String, Object> entry : filtersStats.entrySet()) {

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019-2020 Florian Schmaus * Copyright 2019-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,6 +37,8 @@ public abstract class XmppClientToServerTransport {
protected abstract void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess); 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 * 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. * explicitly, only if the filters are modified so that they potentially produced new data.

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2018-2020 Florian Schmaus * Copyright 2018-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import java.io.IOException;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext; import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
@ -75,4 +76,24 @@ 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-2020 Florian Schmaus * Copyright 2018-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,8 @@
*/ */
package org.jivesoftware.smack.fsm; package org.jivesoftware.smack.fsm;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
public abstract class StateTransitionResult { public abstract class StateTransitionResult {
private final String message; private final String message;
@ -92,4 +94,10 @@ public abstract class StateTransitionResult {
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)"); 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 Florian Schmaus, Aditya Borikar * Copyright 2020-2021 Florian Schmaus, 2020 Aditya Borikar
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* be achieved through {@link XMPPConnection#sendNonza(Nonza)}. * be achieved through {@link XMPPConnection#sendNonza(Nonza)}.
*/ */
public abstract class AbstractStreamOpen implements 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 CLIENT_NAMESPACE = "jabber:client";
public static final String SERVER_NAMESPACE = "jabber:server"; public static final String SERVER_NAMESPACE = "jabber:server";

View file

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

View file

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

View file

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

View file

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

View file

@ -389,6 +389,22 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return this; 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}. * Add the given attribute if value not null and {@code value => 0}.
* *

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019 Florian Schmaus * Copyright 2019-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,6 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
@ -207,6 +210,34 @@ 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, private static void assertAttributeHolds(XmlPullParser parser, int attributeIndex, String expectedLocalpart,
String expectedPrefix, String expectedNamespace) { String expectedPrefix, String expectedNamespace) {
QName qname = parser.getAttributeQName(attributeIndex); QName qname = parser.getAttributeQName(attributeIndex);

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus * Copyright 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@
package org.jivesoftware.smackx.bob.element; package org.jivesoftware.smackx.bob.element;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.StanzaView;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.bob.BoBData; import org.jivesoftware.smackx.bob.BoBData;
@ -60,6 +60,26 @@ public class BoBDataExtension implements ExtensionElement {
return NAMESPACE; 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 @Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this); XmlStringBuilder xml = new XmlStringBuilder(this);
@ -74,8 +94,8 @@ public class BoBDataExtension implements ExtensionElement {
return xml; return xml;
} }
public static BoBDataExtension from(Message message) { public static BoBDataExtension from(StanzaView stanza) {
return message.getExtension(BoBDataExtension.class); return stanza.getExtension(BoBDataExtension.class);
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,110 @@
/**
*
* 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-2020 Florian Schmaus * Copyright 2018-2021 Florian Schmaus
* *
* This file is part of smack-repl. * This file is part of smack-repl.
* *
@ -25,8 +25,6 @@ import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
@ -38,18 +36,11 @@ import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod; import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod;
import org.jivesoftware.smack.debugger.ConsoleDebugger; import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory; import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor; import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor; import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.util.XmppDateTime;
public class Nio { public class Nio {
private static final Logger LOGGER = Logger.getLogger(Nio.class.getName());
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException { public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException {
doNio(args[0], args[1], args[2]); doNio(args[0], args[1], args[2]);
} }
@ -111,30 +102,7 @@ public class Nio {
connection.setReplyTimeout(5 * 60 * 1000); connection.setReplyTimeout(5 * 60 * 1000);
connection.addConnectionStateMachineListener((event, c) -> { XmppTools.modularConnectionTest(connection, "flo@geekplace.eu");
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 2020 Aditya Borikar * Copyright 2021 Florian Schmaus
* *
* This file is part of smack-repl. * This file is part of smack-repl.
* *
@ -21,7 +21,6 @@
package org.igniterealtime.smack.smackrepl; package org.igniterealtime.smack.smackrepl;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
@ -29,25 +28,51 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor; import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor;
public class WebSocketConnection { public class WebSocketConnection {
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException, URISyntaxException { public static void main(String[] args)
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder(); throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
builder.removeAllModules(); String jid, password, websocketEndpoint, messageTo = null;
builder.setXmppAddressAndPassword(args[0], args[1]); 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 {
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
builder.removeAllModules()
.setXmppAddressAndPassword(jid, password)
;
// Set a fallback uri into websocket transport descriptor and add this descriptor into connection builder.
XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder); XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(new URI(args[2]), false); websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(websocketEndpoint, false);
builder.addModule(websocketBuilder.build()); builder.addModule(websocketBuilder.build());
ModularXmppClientToServerConnectionConfiguration config = builder.build(); ModularXmppClientToServerConnectionConfiguration config = builder.build();
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config); ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
connection.connect(); connection.setReplyTimeout(5 * 60 * 1000);
connection.login();
connection.disconnect(); XmppTools.modularConnectionTest(connection, messageTo);
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2016 Florian Schmaus * Copyright 2016-2021 Florian Schmaus
* *
* This file is part of smack-repl. * This file is part of smack-repl.
* *
@ -23,6 +23,8 @@ package org.igniterealtime.smack.smackrepl;
import java.io.IOException; import java.io.IOException;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
@ -30,15 +32,19 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; 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.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.TLSUtils; import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.iqregister.AccountManager; import org.jivesoftware.smackx.iqregister.AccountManager;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.util.XmppDateTime;
public class XmppTools { public class XmppTools {
@ -106,4 +112,40 @@ public class XmppTools {
connection.disconnect(); 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,6 +83,7 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StartTls; import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.packet.SaslNonza; import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sm.SMUtils; import org.jivesoftware.smack.sm.SMUtils;
@ -961,6 +962,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
switch (eventType) { switch (eventType) {
case START_ELEMENT: case START_ELEMENT:
final String name = parser.getName(); final String name = parser.getName();
final String namespace = parser.getNamespace();
switch (name) { switch (name) {
case Message.ELEMENT: case Message.ELEMENT:
case IQ.IQ_ELEMENT: case IQ.IQ_ELEMENT:
@ -972,7 +975,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
break; break;
case "stream": case "stream":
if (StreamOpen.ETHERX_JABBER_STREAMS_NAMESPACE.equals(namespace)) {
onStreamOpen(parser); onStreamOpen(parser);
}
break; break;
case "error": case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser); StreamError streamError = PacketParserUtils.parseStreamError(parser);
@ -989,7 +994,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
openStreamAndResetParser(); openStreamAndResetParser();
break; break;
case "failure": case "failure":
String namespace = parser.getNamespace(null);
switch (namespace) { switch (namespace) {
case "urn:ietf:params:xml:ns:xmpp-tls": case "urn:ietf:params:xml:ns:xmpp-tls":
// TLS negotiation has failed. The server will close the connection // TLS negotiation has failed. The server will close the connection

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019-2020 Florian Schmaus * Copyright 2019-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -80,14 +80,12 @@ import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints;
import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints.Result; import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints.Result;
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint; import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.UTF8; import org.jivesoftware.smack.util.UTF8;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure; 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.Jid;
import org.jxmpp.jid.util.JidUtil; import org.jxmpp.jid.util.JidUtil;
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter; import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
@ -213,6 +211,8 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
} }
final String prefixXmlns = "xmlns:" + prefix; 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 streamClose = new StringBuilder(32);
final StringBuilder streamOpen = new StringBuilder(256); final StringBuilder streamOpen = new StringBuilder(256);
@ -253,14 +253,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
this.streamOpen = streamOpen.toString(); this.streamOpen = streamOpen.toString();
this.streamClose = streamClose.toString(); this.streamClose = streamClose.toString();
XmlPullParser streamOpenParser; connectionInternal.onStreamOpen(this.streamOpen);
try {
streamOpenParser = PacketParserUtils.getParserFor(this.streamOpen);
} catch (XmlPullParserException | IOException e) {
// Should never happen.
throw new AssertionError(e);
}
connectionInternal.onStreamOpen(streamOpenParser);
} }
@Override @Override
@ -586,7 +579,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() { public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
return new StreamOpenAndCloseFactory() { return new StreamOpenAndCloseFactory() {
@Override @Override
public StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) { public StreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
String xmlLang = connectionInternal.connection.getConfiguration().getXmlLang(); String xmlLang = connectionInternal.connection.getConfiguration().getXmlLang();
StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client); StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client);
return streamOpen; return streamOpen;
@ -603,6 +596,11 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
discoveredTcpEndpoints = null; discoveredTcpEndpoints = null;
} }
@Override
public boolean hasUseableConnectionEndpoints() {
return discoveredTcpEndpoints != null;
}
@Override @Override
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() { protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup. // Assert that there are no stale discovered endpoints prior performing the lookup.
@ -750,10 +748,10 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
return new EstablishingTcpConnectionState(stateDescriptor, connectionInternal); return new EstablishingTcpConnectionState(stateDescriptor, connectionInternal);
} }
final class EstablishingTcpConnectionState extends State { final class EstablishingTcpConnectionState extends State.AbstractTransport {
private EstablishingTcpConnectionState(EstablishingTcpConnectionStateDescriptor stateDescriptor, private EstablishingTcpConnectionState(EstablishingTcpConnectionStateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) { ModularXmppClientToServerConnectionInternal connectionInternal) {
super(stateDescriptor, connectionInternal); super(tcpNioTransport, stateDescriptor, connectionInternal);
} }
@Override @Override
@ -777,6 +775,10 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
connectionInternal.setTransport(tcpNioTransport); 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"); connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
return new TcpSocketConnectedResult(remoteAddress); return new TcpSocketConnectedResult(remoteAddress);

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Aditya Borikar * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,22 +16,17 @@
*/ */
package org.jivesoftware.smack.websocket.okhttp; package org.jivesoftware.smack.websocket.okhttp;
import java.io.IOException; import java.net.URI;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; 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.WebSocketException;
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.xml.XmlPullParserException;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
@ -43,50 +38,49 @@ public final class OkHttpWebSocket extends AbstractWebSocket {
private static final Logger LOGGER = Logger.getLogger(OkHttpWebSocket.class.getName()); private static final Logger LOGGER = Logger.getLogger(OkHttpWebSocket.class.getName());
private static OkHttpClient okHttpClient = null; 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 final ModularXmppClientToServerConnectionInternal connectionInternal;
private final LoggingInterceptor interceptor; private final LoggingInterceptor interceptor;
private String openStreamHeader; private final WebSocket okHttpWebSocket;
private WebSocket currentWebSocket;
private WebSocketConnectionPhase phase;
private WebSocketRemoteConnectionEndpoint connectedEndpoint;
public OkHttpWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) { public OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
this.connectionInternal = connectionInternal; ModularXmppClientToServerConnectionInternal connectionInternal) {
super(endpoint, 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) { if (connectionInternal.smackDebugger != null) {
interceptor = new LoggingInterceptor(connectionInternal.smackDebugger); interceptor = new LoggingInterceptor(connectionInternal.smackDebugger);
} else { } else {
interceptor = null; interceptor = null;
} }
}
@Override final URI uri = endpoint.getUri();
public void connect(WebSocketRemoteConnectionEndpoint endpoint) throws InterruptedException, SmackException, XMPPException { final String url = uri.toString();
final String currentUri = endpoint.getWebSocketEndpoint().toString();
Request request = new Request.Builder() Request request = new Request.Builder()
.url(currentUri) .url(url)
.header("Sec-WebSocket-Protocol", "xmpp") .header("Sec-WebSocket-Protocol", "xmpp")
.build(); .build();
WebSocketListener listener = new WebSocketListener() { okHttpWebSocket = okHttpClient.newWebSocket(request, listener);
}
private final WebSocketListener listener = new WebSocketListener() {
@Override @Override
public void onOpen(WebSocket webSocket, Response response) { public void onOpen(WebSocket webSocket, Response response) {
LOGGER.log(Level.FINER, "WebSocket is open"); LOGGER.log(Level.FINER, "OkHttp invoked onOpen() for {0}. Response: {1}",
phase = WebSocketConnectionPhase.openFrameSent; new Object[] { webSocket, response });
if (interceptor != null) { if (interceptor != null) {
interceptor.interceptOpenResponse(response); interceptor.interceptOpenResponse(response);
} }
send(new WebSocketOpenElement(connectionInternal.connection.getXMPPServiceDomain()));
future.setResult(OkHttpWebSocket.this);
} }
@Override @Override
@ -94,84 +88,69 @@ public final class OkHttpWebSocket extends AbstractWebSocket {
if (interceptor != null) { if (interceptor != null) {
interceptor.interceptReceivedText(text); interceptor.interceptReceivedText(text);
} }
if (isCloseElement(text)) {
connectionInternal.onStreamClosed();
return;
}
String closingStream = "</stream>"; onIncomingWebSocketElement(text);
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);
}
} }
@Override @Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) { public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
LOGGER.log(Level.INFO, "Exception caught", t); LOGGER.log(Level.FINER, "OkHttp invoked onFailure() for " + webSocket + ". Response: " + response, throwable);
WebSocketException websocketException = new WebSocketException(t); WebSocketException websocketException = new WebSocketException(throwable);
if (connectionInternal.connection.isConnected()) {
// 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); connectionInternal.notifyConnectionError(websocketException);
} else { } else {
connectionInternal.setCurrentConnectionExceptionAndNotify(websocketException); future.setException(websocketException);
} }
} }
};
// Creates an instance of websocket through okHttpClient.
currentWebSocket = okHttpClient.newWebSocket(request, listener);
// Open a new stream and wait until features are received.
connectionInternal.waitForFeaturesReceived("Waiting to receive features");
connectedEndpoint = endpoint;
}
@Override @Override
public void send(TopLevelStreamElement element) { public void onClosing(WebSocket webSocket, int code, String reason) {
String textToBeSent = element.toXML().toString(); LOGGER.log(Level.FINER, "OkHttp invoked onClosing() for " + webSocket + ". Code: " + code + ". Reason: " + reason);
if (interceptor != null) {
interceptor.interceptSentText(textToBeSent);
} }
currentWebSocket.send(textToBeSent);
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
LOGGER.log(Level.FINER, "OkHttp invoked onClosed() for " + webSocket + ". Code: " + code + ". Reason: " + reason);
}
};
@Override
public SmackFuture<AbstractWebSocket, Exception> getFuture() {
return future;
}
@Override
public void send(String element) {
if (interceptor != null) {
interceptor.interceptSentText(element);
}
okHttpWebSocket.send(element);
} }
@Override @Override
public void disconnect(int code, String message) { public void disconnect(int code, String message) {
currentWebSocket.close(code, message); LOGGER.log(Level.INFO, "WebSocket closing with code: " + code + " and message: " + message);
LOGGER.log(Level.INFO, "WebSocket has been closed with message: " + message); okHttpWebSocket.close(code, message);
} }
@Override @Override
public boolean isConnectionSecure() { public boolean isConnectionSecure() {
return connectedEndpoint.isSecureEndpoint(); return endpoint.isSecureEndpoint();
} }
@Override @Override
public boolean isConnected() { public boolean isConnected() {
return connectedEndpoint == null ? false : true; // TODO: Do we need this method at all if we create an AbstractWebSocket object for every endpoint?
return true;
} }
@Override @Override
public SSLSession getSSLSession() { 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; return null;
} }
} }

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Aditya Borikar, Florian Schmaus. * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,22 +19,29 @@ package org.jivesoftware.smack.websocket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.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.XmppWebSocketTransportModule.EstablishingWebSocketConnectionState;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService; import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
public final class WebSocketConnectionAttemptState { public final class WebSocketConnectionAttemptState {
private final ModularXmppClientToServerConnectionInternal connectionInternal; private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints; private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
private WebSocketRemoteConnectionEndpoint connectedEndpoint; private AbstractWebSocket webSocket;
WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal, WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints, XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
EstablishingWebSocketConnectionState establishingWebSocketConnectionState) { EstablishingWebSocketConnectionState establishingWebSocketConnectionState) {
assert discoveredWebSocketEndpoints != null; assert discoveredWebSocketEndpoints != null;
assert !discoveredWebSocketEndpoints.result.isEmpty();
this.connectionInternal = connectionInternal; this.connectionInternal = connectionInternal;
this.discoveredEndpoints = discoveredWebSocketEndpoints; this.discoveredEndpoints = discoveredWebSocketEndpoints;
} }
@ -44,48 +51,96 @@ public final class WebSocketConnectionAttemptState {
* *
* @return {@link AbstractWebSocket} with which connection is establised * @return {@link AbstractWebSocket} with which connection is establised
* @throws InterruptedException if the calling thread was interrupted * @throws InterruptedException if the calling thread was interrupted
* @throws WebSocketException if encounters a websocket exception
*/ */
AbstractWebSocket establishWebSocketConnection() throws InterruptedException, WebSocketException { @SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
List<WebSocketRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints; StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException {
final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result;
final List<Exception> failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount());
if (endpoints.isEmpty()) { webSocket = null;
throw new WebSocketException(new Throwable("No Endpoints discovered to establish connection"));
}
List<Throwable> connectionFailureList = new ArrayList<>(); SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode();
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal); switch (securityMode) {
case required:
// Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with. case ifpossible:
for (WebSocketRemoteConnectionEndpoint endpoint : endpoints) { establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures);
try { if (webSocket != null) {
websocket.connect(endpoint); return null;
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,
* Returns the connected websocket endpoint. List<Exception> failures) throws InterruptedException {
* final int endpointCount = webSocketEndpoints.size();
* @return connected websocket endpoint
*/ List<SmackFuture<AbstractWebSocket, Exception>> futures = new ArrayList<>(endpointCount);
public WebSocketRemoteConnectionEndpoint getConnectedEndpoint() { {
return connectedEndpoint; 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);
}
} }
} }

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Aditya Borikar * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,20 +16,16 @@
*/ */
package org.jivesoftware.smack.websocket; package org.jivesoftware.smack.websocket;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.AsyncButOrdered; import org.jivesoftware.smack.AsyncButOrdered;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException; 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;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture; import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
@ -54,13 +50,13 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSock
import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement; import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement;
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement; import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; 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.WebSocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
import org.jxmpp.jid.DomainBareJid; 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. * The websocket transport module that goes with Smack's modular architecture.
@ -101,31 +97,37 @@ public final class XmppWebSocketTransportModule
} }
} }
final class EstablishingWebSocketConnectionState extends State { final class EstablishingWebSocketConnectionState extends State.AbstractTransport {
protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor, protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) { ModularXmppClientToServerConnectionInternal connectionInternal) {
super(stateDescriptor, connectionInternal); super(websocketTransport, stateDescriptor, connectionInternal);
} }
@Override @Override
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws InterruptedException,
throws IOException, SmackException, InterruptedException, XMPPException { NoResponseException, NotConnectedException, SmackException, XMPPException {
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState( WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
connectionInternal, discoveredWebSocketEndpoints, this); connectionInternal, discoveredWebSocketEndpoints, this);
try { StateTransitionResult.Failure failure = connectionAttemptState.establishWebSocketConnection();
websocket = connectionAttemptState.establishWebSocketConnection(); if (failure != null) {
} catch (InterruptedException | WebSocketException e) {
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
return failure; return failure;
} }
websocket = connectionAttemptState.getConnectedWebSocket();
connectionInternal.setTransport(websocketTransport); connectionInternal.setTransport(websocketTransport);
WebSocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint(); // 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");
// Construct a WebSocketConnectedResult using the connected endpoint. // Construct a WebSocketConnectedResult using the connected endpoint.
return new WebSocketConnectedResult(connectedEndpoint); return new WebSocketConnectedResult(websocket.getEndpoint());
} }
} }
@ -140,7 +142,7 @@ public final class XmppWebSocketTransportModule
final WebSocketRemoteConnectionEndpoint connectedEndpoint; final WebSocketRemoteConnectionEndpoint connectedEndpoint;
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) { public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
super("WebSocket connection establised with endpoint: " + connectedEndpoint.getWebSocketEndpoint()); super("WebSocket connection establised with endpoint: " + connectedEndpoint);
this.connectedEndpoint = connectedEndpoint; this.connectedEndpoint = connectedEndpoint;
} }
} }
@ -164,6 +166,12 @@ public final class XmppWebSocketTransportModule
discoveredWebSocketEndpoints = null; discoveredWebSocketEndpoints = null;
} }
@Override
public boolean hasUseableConnectionEndpoints() {
return discoveredWebSocketEndpoints != null;
}
@SuppressWarnings("incomplete-switch")
@Override @Override
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() { protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup. // Assert that there are no stale discovered endpoints prior performing the lookup.
@ -172,51 +180,56 @@ public final class XmppWebSocketTransportModule
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>(); InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
connectionInternal.asyncGo(() -> { connectionInternal.asyncGo(() -> {
Result result = null;
WebSocketRemoteConnectionEndpoint providedEndpoint = null;
// Check if there is a websocket endpoint already configured.
URI uri = moduleDescriptor.getExplicitlyProvidedUri();
if (uri != null) {
providedEndpoint = new WebSocketRemoteConnectionEndpoint(uri);
}
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);
}
// Generate Result for explicitly configured endpoint.
Result manualResult = new Result(Arrays.asList(providedEndpoint), null);
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(manualResult);
websocketEndpointsLookupFuture.setResult(endpointsResult);
} else {
DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain();
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration(); ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
SecurityMode mode = configuration.getSecurityMode(); DomainBareJid host = configuration.getXMPPServiceDomain();
if (moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
// Fetch remote endpoints. // Fetch remote endpoints.
Result xep0156result = WebSocketRemoteConnectionEndpointLookup.lookup(host, mode); result = WebSocketRemoteConnectionEndpointLookup.lookup(host);
}
List<WebSocketRemoteConnectionEndpoint> discoveredEndpoints = xep0156result.discoveredRemoteConnectionEndpoints; 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();
}
// Generate result considering both manual and fetched endpoints. // We insert the provided endpoint at the beginning of the list, so that it is used first.
Result finalResult = new Result(discoveredEndpoints, xep0156result.getLookupFailures()); 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();
}
}
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(finalResult); if (moduleDescriptor.isImplicitWebSocketEndpointEnabled()) {
String urlWithoutScheme = "://" + host + ":5443/ws";
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);
} else {
endpointsResult = new DiscoveredWebSocketEndpoints(result);
}
websocketEndpointsLookupFuture.setResult(endpointsResult); websocketEndpointsLookupFuture.setResult(endpointsResult);
}
}); });
return Collections.singletonList(websocketEndpointsLookupFuture); return Collections.singletonList(websocketEndpointsLookupFuture);
@ -238,11 +251,11 @@ public final class XmppWebSocketTransportModule
@Override @Override
protected void notifyAboutNewOutgoingElements() { protected void notifyAboutNewOutgoingElements() {
Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue; final Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> { asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> {
// Once new outgoingElement is notified, send the top level stream element obtained by polling. for (TopLevelStreamElement topLevelStreamElement; (topLevelStreamElement = outgoingElementsQueue.poll()) != null;) {
TopLevelStreamElement topLevelStreamElement = outgoingElementsQueue.poll();
websocket.send(topLevelStreamElement); websocket.send(topLevelStreamElement);
}
}); });
} }
@ -268,15 +281,11 @@ public final class XmppWebSocketTransportModule
@Override @Override
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() { public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
// TODO: Create extra class for this?
return new StreamOpenAndCloseFactory() { return new StreamOpenAndCloseFactory() {
@Override @Override
public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) { public AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
try { return new WebSocketOpenElement(to);
return new WebSocketOpenElement(JidCreate.domainBareFrom(to));
} catch (XmppStringprepException e) {
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
return null;
}
} }
@Override @Override
public AbstractStreamClose createStreamClose() { public AbstractStreamClose createStreamClose() {
@ -295,10 +304,6 @@ public final class XmppWebSocketTransportModule
assert result != null; assert result != null;
this.result = result; this.result = result;
} }
public WebSocketRemoteConnectionEndpointLookup.Result getResult() {
return result;
}
} }
/** /**
@ -308,10 +313,13 @@ public final class XmppWebSocketTransportModule
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed { final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
final List<RemoteConnectionEndpointLookupFailure> lookupFailures; final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
WebSocketEndpointsDiscoveryFailed( WebSocketEndpointsDiscoveryFailed(RemoteConnectionEndpointLookupFailure lookupFailure) {
WebSocketRemoteConnectionEndpointLookup.Result result) { this(Collections.singletonList(lookupFailure));
assert result != null; }
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
WebSocketEndpointsDiscoveryFailed(List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
assert lookupFailures != null;
this.lookupFailures = Collections.unmodifiableList(lookupFailures);
} }
@Override @Override

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Aditya Borikar * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,6 +21,7 @@ import java.net.URISyntaxException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
@ -29,6 +30,7 @@ import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionIn
import org.jivesoftware.smack.fsm.StateDescriptor; import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor; import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
/** /**
* The descriptor class for {@link XmppWebSocketTransportModule}. * The descriptor class for {@link XmppWebSocketTransportModule}.
@ -37,12 +39,43 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.Establishin
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}. * use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
*/ */
public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor { public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private boolean performWebSocketEndpointDiscovery; private final boolean performWebSocketEndpointDiscovery;
private URI uri; private final boolean implicitWebSocketEndpoint;
private final URI uri;
private final WebSocketRemoteConnectionEndpoint wsRce;
public XmppWebSocketTransportModuleDescriptor(Builder builder) { public XmppWebSocketTransportModuleDescriptor(Builder builder) {
this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery; this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery;
this.implicitWebSocketEndpoint = builder.implicitWebSocketEndpoint;
this.uri = builder.uri; 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;
}
} }
/** /**
@ -53,6 +86,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
return performWebSocketEndpointDiscovery; return performWebSocketEndpointDiscovery;
} }
public boolean isImplicitWebSocketEndpointEnabled() {
return implicitWebSocketEndpoint;
}
/** /**
* Returns explicitly configured websocket endpoint uri. * Returns explicitly configured websocket endpoint uri.
* @return uri * @return uri
@ -61,6 +98,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
return uri; return uri;
} }
WebSocketRemoteConnectionEndpoint getExplicitlyProvidedEndpoint() {
return wsRce;
}
@Override @Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() { protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
Set<Class<? extends StateDescriptor>> res = new HashSet<>(); Set<Class<? extends StateDescriptor>> res = new HashSet<>();
@ -99,6 +140,7 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
*/ */
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder { public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
private boolean performWebSocketEndpointDiscovery = true; private boolean performWebSocketEndpointDiscovery = true;
private boolean implicitWebSocketEndpoint = true;
private URI uri; private URI uri;
private Builder( private Builder(
@ -119,15 +161,20 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException { public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString()); URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, true); return explicitlySetWebSocketEndpoint(endpointUri);
} }
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint, boolean performWebSocketEndpointDiscovery) public Builder explicitlySetWebSocketEndpointAndDiscovery(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
throws URISyntaxException { throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString()); URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery); return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery);
} }
public Builder disableImplicitWebsocketEndpoint() {
implicitWebSocketEndpoint = false;
return this;
}
@Override @Override
public ModularXmppClientToServerConnectionModuleDescriptor build() { public ModularXmppClientToServerConnectionModuleDescriptor build() {
return new XmppWebSocketTransportModuleDescriptor(this); return new XmppWebSocketTransportModuleDescriptor(this);

View file

@ -1,47 +0,0 @@
/**
*
* 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. * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,40 +18,92 @@ package org.jivesoftware.smack.websocket.impl;
import javax.net.ssl.SSLSession; 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.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public abstract class AbstractWebSocket { public abstract class AbstractWebSocket {
protected enum WebSocketConnectionPhase { protected final ModularXmppClientToServerConnectionInternal connectionInternal;
openFrameSent,
exchangingTopLevelStreamElements protected final WebSocketRemoteConnectionEndpoint endpoint;
protected AbstractWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
ModularXmppClientToServerConnectionInternal connectionInternal) {
this.endpoint = endpoint;
this.connectionInternal = connectionInternal;
} }
protected static String getStreamFromOpenElement(String openElement) { 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) {
String streamElement = openElement.replaceFirst("\\A<open ", "<stream ") String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client") .replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
.replaceFirst("/>\\s*\\z", ">"); .replaceFirst("/>\\s*\\z", ">");
return streamElement; return streamElement;
} }
protected static boolean isOpenElement(String text) { // 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) {
if (text.startsWith("<open ")) { if (text.startsWith("<open ")) {
return true; return true;
} }
return false; return false;
} }
protected static boolean isCloseElement(String text) { // 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) {
if (text.startsWith("<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>")) { if (text.startsWith("<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>")) {
return true; return true;
} }
return false; return false;
} }
public abstract void connect(WebSocketRemoteConnectionEndpoint endpoint) throws Throwable; public abstract SmackFuture<AbstractWebSocket, Exception> getFuture();
public abstract void send(TopLevelStreamElement element); 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 disconnect(int code, String message); public abstract void disconnect(int code, String message);

View file

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

View file

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

View file

@ -0,0 +1,39 @@
/**
*
* 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

@ -0,0 +1,39 @@
/**
*
* 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 Aditya Borikar * Copyright 2020-2021 Aditya Borikar, Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,66 +20,100 @@ import java.net.InetAddress;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.datatypes.UInt16; import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint; import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
public final class WebSocketRemoteConnectionEndpoint implements 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";
private static final Logger LOGGER = Logger.getAnonymousLogger(); private static final Logger LOGGER = Logger.getAnonymousLogger();
private final URI uri; private final URI uri;
private final UInt16 port;
public WebSocketRemoteConnectionEndpoint(String uri) throws URISyntaxException { protected WebSocketRemoteConnectionEndpoint(URI uri) {
this(new URI(uri));
}
public WebSocketRemoteConnectionEndpoint(URI uri) {
this.uri = uri; this.uri = uri;
String scheme = uri.getScheme(); int portInt = uri.getPort();
if (!(scheme.equals("ws") || scheme.equals("wss"))) { if (portInt >= 0) {
throw new IllegalArgumentException("Only allowed protocols are ws and wss"); port = UInt16.from(portInt);
} else {
port = null;
} }
} }
public URI getWebSocketEndpoint() { public final URI getUri() {
return uri; return uri;
} }
public boolean isSecureEndpoint() {
if (uri.getScheme().equals("wss")) {
return true;
}
return false;
}
@Override @Override
public CharSequence getHost() { public final String getHost() {
return uri.getHost(); return uri.getHost();
} }
@Override @Override
public UInt16 getPort() { public UInt16 getPort() {
return UInt16.from(uri.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);
} }
@Override @Override
public Collection<? extends InetAddress> getInetAddresses() { public Collection<? extends InetAddress> getInetAddresses() {
try { resolveInetAddressesIfRequired();
InetAddress address = InetAddress.getByName(getHost().toString()); return inetAddresses;
return Collections.singletonList(address);
} catch (UnknownHostException e) {
LOGGER.log(Level.INFO, "Unknown Host Exception ", e);
}
return null;
} }
@Override @Override
public String getDescription() { public String getDescription() {
return null; 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 * Copyright 2020 Aditya Borikar, Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,10 +20,9 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Collections;
import java.util.List; import java.util.List;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.altconnections.HttpLookupMethod; import org.jivesoftware.smack.altconnections.HttpLookupMethod;
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation; import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure; import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
@ -33,9 +32,8 @@ import org.jxmpp.jid.DomainBareJid;
public final class WebSocketRemoteConnectionEndpointLookup { public final class WebSocketRemoteConnectionEndpointLookup {
public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) { public static Result lookup(DomainBareJid domainBareJid) {
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1); List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
List<URI> rcUriList = null; List<URI> rcUriList = null;
try { try {
@ -45,67 +43,69 @@ public final class WebSocketRemoteConnectionEndpointLookup {
} catch (IOException | XmlPullParserException | URISyntaxException e) { } catch (IOException | XmlPullParserException | URISyntaxException e) {
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure( lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
domainBareJid, e)); domainBareJid, e));
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures); return new Result(lookupFailures);
} }
if (rcUriList.isEmpty()) { List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints = new ArrayList<>(rcUriList.size());
throw new IllegalStateException("No endpoints were found inside host-meta"); List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints = new ArrayList<>(rcUriList.size());
}
// Convert rcUriList to List<WebSocketRemoteConnectionEndpoint> for (URI webSocketUri : rcUriList) {
Iterator<URI> iterator = rcUriList.iterator(); WebSocketRemoteConnectionEndpoint wsRce = WebSocketRemoteConnectionEndpoint.from(webSocketUri);
List<WebSocketRemoteConnectionEndpoint> rceList = new ArrayList<>(); if (wsRce instanceof SecureWebSocketRemoteConnectionEndpoint) {
while (iterator.hasNext()) { SecureWebSocketRemoteConnectionEndpoint secureWsRce = (SecureWebSocketRemoteConnectionEndpoint) wsRce;
rceList.add(new WebSocketRemoteConnectionEndpoint(iterator.next())); discoveredSecureEndpoints.add(secureWsRce);
} } else if (wsRce instanceof InsecureWebSocketRemoteConnectionEndpoint) {
InsecureWebSocketRemoteConnectionEndpoint insecureWsRce = (InsecureWebSocketRemoteConnectionEndpoint) wsRce;
switch (securityMode) { discoveredInsecureEndpoints.add(insecureWsRce);
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 { } else {
insecureEndpointsForSecurityModeIfPossible.add(uri); // WebSocketRemoteConnectionEndpoint.from() must return an instance which type is one of the above.
throw new AssertionError();
} }
} }
discoveredRemoteConnectionEndpoints = secureEndpointsForSecurityModeIfPossible;
discoveredRemoteConnectionEndpoints.addAll(insecureEndpointsForSecurityModeIfPossible); return new Result(discoveredSecureEndpoints, discoveredInsecureEndpoints, lookupFailures);
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 static final class Result {
public final List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints; public final List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints;
public final List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints;
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures; public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
public Result(List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints, 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,
List<RemoteConnectionEndpointLookupFailure> lookupFailures) { List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints; this.discoveredSecureEndpoints = discoveredSecureEndpoints;
this.discoveredInsecureEndpoints = discoveredInsecureEndpoints;
this.lookupFailures = lookupFailures; this.lookupFailures = lookupFailures;
} }
public List<WebSocketRemoteConnectionEndpoint> getDiscoveredRemoteConnectionEndpoints() { public boolean isEmpty() {
return discoveredRemoteConnectionEndpoints; 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<RemoteConnectionEndpointLookupFailure> getLookupFailures() { public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {

View file

@ -17,24 +17,15 @@
package org.jivesoftware.smack.websocket; package org.jivesoftware.smack.websocket;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; 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.junit.jupiter.api.Test;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
@ -64,42 +55,6 @@ public class XmppWebSocketTransportModuleTest {
assertNotNull(websocketTransportModuleDescriptor); 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 @Test
public void lookupConnectionEndpointsTest() throws URISyntaxException { public void lookupConnectionEndpointsTest() throws URISyntaxException {
XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor(); XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor();

View file

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

View file

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

View file

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