mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 12:32:06 +01:00
Compare commits
47 commits
3003094130
...
525f27abf1
Author | SHA1 | Date | |
---|---|---|---|
|
525f27abf1 | ||
|
64d3e804a7 | ||
|
e78ef2b668 | ||
|
1ebe8b0309 | ||
|
b09cd06053 | ||
|
d06e9499e8 | ||
|
7ed29b9d5f | ||
|
99297e5a76 | ||
|
1aab0b8aac | ||
|
cf4c9725b7 | ||
|
5d32735ad7 | ||
|
f892ba1369 | ||
|
e6a60213b6 | ||
|
2a9671ca93 | ||
|
d00656493a | ||
|
7f10a82fd9 | ||
|
0bb0884512 | ||
|
53d66261af | ||
|
61799c5951 | ||
|
648a1cfab1 | ||
|
db385e6595 | ||
|
9cec02b5e3 | ||
|
0e49adff1d | ||
|
9fcc97836b | ||
|
49ebe8c587 | ||
|
9fe1fc6689 | ||
|
3f3590b42b | ||
|
317e391da5 | ||
|
45e865757a | ||
|
f00804ef72 | ||
|
c9cf4f1541 | ||
|
11e38f9ba5 | ||
|
89c5895ab3 | ||
|
b61426c8d0 | ||
|
cf92566e26 | ||
|
36d61d9e52 | ||
|
7796b367cc | ||
|
e35175a3d5 | ||
|
92ed777dba | ||
|
ac788592a6 | ||
|
1a2a613112 | ||
|
3c54d5ffcd | ||
|
a356e91108 | ||
dd631048a3 | |||
|
d34eda61fe | ||
|
ec80d5287b | ||
|
64fb47c98b |
83 changed files with 2363 additions and 114 deletions
50
build.gradle
50
build.gradle
|
@ -122,8 +122,8 @@ allprojects {
|
||||||
// See also:
|
// See also:
|
||||||
// - https://issues.apache.org/jira/browse/MNG-6232
|
// - https://issues.apache.org/jira/browse/MNG-6232
|
||||||
// - https://issues.igniterealtime.org/browse/SMACK-858
|
// - https://issues.igniterealtime.org/browse/SMACK-858
|
||||||
jxmppVersion = '1.0.0'
|
jxmppVersion = '[1.0.0, 1.0.999]'
|
||||||
miniDnsVersion = '1.0.0'
|
miniDnsVersion = '[1.0.0, 1.0.999]'
|
||||||
smackMinAndroidSdk = 19
|
smackMinAndroidSdk = 19
|
||||||
junitVersion = '5.6.2'
|
junitVersion = '5.6.2'
|
||||||
commonsIoVersion = '2.6'
|
commonsIoVersion = '2.6'
|
||||||
|
@ -288,6 +288,9 @@ tasks.withType(Javadoc) {
|
||||||
// fixtures, and we want to have mockito also available in
|
// fixtures, and we want to have mockito also available in
|
||||||
// test, so we use API here.
|
// test, so we use API here.
|
||||||
testFixturesApi "org.mockito:mockito-core:3.3.3"
|
testFixturesApi "org.mockito:mockito-core:3.3.3"
|
||||||
|
|
||||||
|
// To mock final classes
|
||||||
|
testImplementation 'org.mockito:mockito-inline:3.3.3'
|
||||||
testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2'
|
testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2'
|
||||||
|
|
||||||
errorprone 'com.google.errorprone:error_prone_core:2.3.4'
|
errorprone 'com.google.errorprone:error_prone_core:2.3.4'
|
||||||
|
@ -305,6 +308,13 @@ configure (junit4Projects) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to evaluate the child projects first because
|
||||||
|
// - javadocAll needs the smack-core child to have already resolved
|
||||||
|
// the jXMPP/MiniDNS dependencies, so that we can the resovled
|
||||||
|
// version to link to those project's javadoc.
|
||||||
|
// - We use the child's project description as description for the
|
||||||
|
// Maven POM.
|
||||||
|
evaluationDependsOnChildren()
|
||||||
task javadocAll(type: Javadoc) {
|
task javadocAll(type: Javadoc) {
|
||||||
source javadocAllProjects.collect {project ->
|
source javadocAllProjects.collect {project ->
|
||||||
project.sourceSets.main.allJava.findAll {
|
project.sourceSets.main.allJava.findAll {
|
||||||
|
@ -319,13 +329,15 @@ task javadocAll(type: Javadoc) {
|
||||||
classpath = files(subprojects.collect {project ->
|
classpath = files(subprojects.collect {project ->
|
||||||
project.sourceSets.main.compileClasspath})
|
project.sourceSets.main.compileClasspath})
|
||||||
classpath += files(androidBootClasspath)
|
classpath += files(androidBootClasspath)
|
||||||
|
def staticJxmppVersion = getResolvedVersion('org.jxmpp:jxmpp-core')
|
||||||
|
def staticMiniDnsVersion = getResolvedVersion('org.minidns:minidns-core')
|
||||||
options {
|
options {
|
||||||
linkSource = true
|
linkSource = true
|
||||||
use = true
|
use = true
|
||||||
links = [
|
links = [
|
||||||
"https://docs.oracle.com/javase/${javaMajor}/docs/api/",
|
"https://docs.oracle.com/javase/${javaMajor}/docs/api/",
|
||||||
"https://jxmpp.org/releases/$jxmppVersion/javadoc/",
|
"https://jxmpp.org/releases/${staticJxmppVersion}/javadoc/",
|
||||||
"https://minidns.org/releases/$miniDnsVersion/javadoc/",
|
"https://minidns.org/releases/${staticMiniDnsVersion}/javadoc/",
|
||||||
] as String[]
|
] as String[]
|
||||||
overview = "$projectDir/resources/javadoc-overview.html"
|
overview = "$projectDir/resources/javadoc-overview.html"
|
||||||
}
|
}
|
||||||
|
@ -408,7 +420,6 @@ description = """\
|
||||||
Smack ${version}
|
Smack ${version}
|
||||||
${oneLineDesc}."""
|
${oneLineDesc}."""
|
||||||
|
|
||||||
evaluationDependsOnChildren()
|
|
||||||
subprojects {
|
subprojects {
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
@ -678,19 +689,19 @@ task sinttestAll {
|
||||||
}
|
}
|
||||||
|
|
||||||
def getGitCommit() {
|
def getGitCommit() {
|
||||||
def dotGit = new File("$projectDir/.git")
|
def projectDirFile = new File("$projectDir")
|
||||||
|
def dotGit = new File(projectDirFile, ".git")
|
||||||
if (!dotGit.isDirectory()) return 'non-git build'
|
if (!dotGit.isDirectory()) return 'non-git build'
|
||||||
|
|
||||||
def projectDir = dotGit.getParentFile()
|
|
||||||
def cmd = 'git describe --always --tags --dirty=+'
|
def cmd = 'git describe --always --tags --dirty=+'
|
||||||
def proc = cmd.execute(null, projectDir)
|
def proc = cmd.execute(null, projectDirFile)
|
||||||
proc.waitForOrKill(10 * 1000)
|
proc.waitForOrKill(10 * 1000)
|
||||||
|
|
||||||
def gitCommit = proc.text.trim()
|
def gitCommit = proc.text.trim()
|
||||||
assert !gitCommit.isEmpty()
|
assert !gitCommit.isEmpty()
|
||||||
|
|
||||||
def srCmd = 'git symbolic-ref --short HEAD'
|
def srCmd = 'git symbolic-ref --short HEAD'
|
||||||
def srProc = srCmd.execute(null, projectDir)
|
def srProc = srCmd.execute(null, projectDirFile)
|
||||||
srProc.waitForOrKill(10 * 1000)
|
srProc.waitForOrKill(10 * 1000)
|
||||||
if (srProc.exitValue() == 0) {
|
if (srProc.exitValue() == 0) {
|
||||||
// Only add the information if the git command was
|
// Only add the information if the git command was
|
||||||
|
@ -740,3 +751,24 @@ def readVersionFile() {
|
||||||
}
|
}
|
||||||
versionFile.text.trim()
|
versionFile.text.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getResolvedVersion(queriedProject = 'smack-core', component) {
|
||||||
|
def configuration = project(queriedProject)
|
||||||
|
.configurations
|
||||||
|
.compileClasspath
|
||||||
|
|
||||||
|
def artifact = configuration
|
||||||
|
.resolvedConfiguration
|
||||||
|
.resolvedArtifacts
|
||||||
|
.findAll {
|
||||||
|
// 'it' is of type ResolvedArtifact, 'id' of
|
||||||
|
// Component*Artifcat*Identifier, and we check the
|
||||||
|
// ComponentIdentifier.
|
||||||
|
it.id.getComponentIdentifier() instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||||
|
}
|
||||||
|
.find {
|
||||||
|
it.id.getComponentIdentifier().toString().startsWith(component + ':')
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact.getModuleVersion().getId().getVersion()
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ include 'smack-core',
|
||||||
'smack-resolver-javax',
|
'smack-resolver-javax',
|
||||||
'smack-sasl-javax',
|
'smack-sasl-javax',
|
||||||
'smack-sasl-provided',
|
'smack-sasl-provided',
|
||||||
|
'smack-streammanagement',
|
||||||
'smack-legacy',
|
'smack-legacy',
|
||||||
'smack-jingle-old',
|
'smack-jingle-old',
|
||||||
'smack-bosh',
|
'smack-bosh',
|
||||||
|
@ -29,6 +30,7 @@ include 'smack-core',
|
||||||
'smack-omemo-signal-integration-test',
|
'smack-omemo-signal-integration-test',
|
||||||
'smack-repl',
|
'smack-repl',
|
||||||
'smack-openpgp',
|
'smack-openpgp',
|
||||||
|
'smack-websocket',
|
||||||
'smack-xmlparser',
|
'smack-xmlparser',
|
||||||
'smack-xmlparser-stax',
|
'smack-xmlparser-stax',
|
||||||
'smack-xmlparser-xpp3'
|
'smack-xmlparser-xpp3'
|
||||||
|
|
|
@ -70,6 +70,7 @@ import org.jivesoftware.smack.filter.StanzaFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaIdFilter;
|
import org.jivesoftware.smack.filter.StanzaIdFilter;
|
||||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||||
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
|
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
||||||
import org.jivesoftware.smack.packet.Bind;
|
import org.jivesoftware.smack.packet.Bind;
|
||||||
import org.jivesoftware.smack.packet.ErrorIQ;
|
import org.jivesoftware.smack.packet.ErrorIQ;
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
@ -522,6 +523,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
closingStreamReceived = false;
|
closingStreamReceived = false;
|
||||||
streamId = null;
|
streamId = null;
|
||||||
|
|
||||||
|
// The connection should not be connected nor marked as such prior calling connectInternal().
|
||||||
|
assert !connected;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Perform the actual connection to the XMPP service
|
// Perform the actual connection to the XMPP service
|
||||||
connectInternal();
|
connectInternal();
|
||||||
|
@ -537,8 +541,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make note of the fact that we're now connected.
|
// If connectInternal() did not throw, then this connection must now be marked as connected.
|
||||||
connected = true;
|
assert connected;
|
||||||
|
|
||||||
callConnectionConnectedListener();
|
callConnectionConnectedListener();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -2224,7 +2229,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendStreamOpen() throws NotConnectedException, InterruptedException {
|
protected final void sendStreamOpen() throws NotConnectedException, InterruptedException {
|
||||||
// If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as
|
// If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as
|
||||||
// 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
|
||||||
|
@ -2236,10 +2241,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
from = XmppStringUtils.completeJidFrom(localpart, to);
|
from = XmppStringUtils.completeJidFrom(localpart, to);
|
||||||
}
|
}
|
||||||
String id = getStreamId();
|
String id = getStreamId();
|
||||||
|
String lang = config.getXmlLang();
|
||||||
|
|
||||||
StreamOpen streamOpen = new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client);
|
AbstractStreamOpen streamOpen = getStreamOpen(to, from, id, lang);
|
||||||
sendNonza(streamOpen);
|
sendNonza(streamOpen);
|
||||||
|
updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||||
|
return new StreamOpen(to, from, id, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateOutgoingStreamXmlEnvironmentOnStreamOpen(AbstractStreamOpen streamOpen) {
|
||||||
XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder();
|
XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder();
|
||||||
xmlEnvironmentBuilder.with(streamOpen);
|
xmlEnvironmentBuilder.with(streamOpen);
|
||||||
outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build();
|
outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build();
|
||||||
|
|
|
@ -364,7 +364,7 @@ public abstract class SmackException extends Exception {
|
||||||
if (lookupFailures.isEmpty()) {
|
if (lookupFailures.isEmpty()) {
|
||||||
sb.append("No endpoint lookup finished within the timeout");
|
sb.append("No endpoint lookup finished within the timeout");
|
||||||
} else {
|
} else {
|
||||||
sb.append("Not endpoints could be discovered due the following lookup failures: ");
|
sb.append("No endpoints could be discovered due the following lookup failures: ");
|
||||||
StringUtils.appendTo(lookupFailures, sb);
|
StringUtils.appendTo(lookupFailures, sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
@ -132,6 +133,7 @@ public final class HttpLookupMethod {
|
||||||
* @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
|
* @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
|
||||||
*/
|
*/
|
||||||
public static List<URI> parseXrdLinkReferencesFor(XmlPullParser parser, String relation) throws IOException, XmlPullParserException, URISyntaxException {
|
public static List<URI> parseXrdLinkReferencesFor(XmlPullParser parser, String relation) throws IOException, XmlPullParserException, URISyntaxException {
|
||||||
|
ParserUtils.forwardToStartElement(parser);
|
||||||
List<URI> uriList = new ArrayList<>();
|
List<URI> uriList = new ArrayList<>();
|
||||||
int initialDepth = parser.getDepth();
|
int initialDepth = parser.getDepth();
|
||||||
|
|
||||||
|
|
|
@ -61,12 +61,13 @@ import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||||
import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
|
import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
|
||||||
import org.jivesoftware.smack.internal.AbstractStats;
|
import org.jivesoftware.smack.internal.AbstractStats;
|
||||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamClose;
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.StreamClose;
|
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
@ -81,7 +82,9 @@ import org.jivesoftware.smack.util.Supplier;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
import org.jxmpp.util.XmppStringUtils;
|
||||||
|
|
||||||
public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection {
|
public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
|
@ -135,6 +138,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
ModularXmppClientToServerConnection.this.notifyConnectionError(e);
|
ModularXmppClientToServerConnection.this.notifyConnectionError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentConnectionExceptionAndNotify(Exception exception) {
|
||||||
|
ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStreamOpen(XmlPullParser parser) {
|
public void onStreamOpen(XmlPullParser parser) {
|
||||||
ModularXmppClientToServerConnection.this.onStreamOpen(parser);
|
ModularXmppClientToServerConnection.this.onStreamOpen(parser);
|
||||||
|
@ -176,6 +184,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
return inputOutputFilters.listIterator(inputOutputFilters.size());
|
return inputOutputFilters.listIterator(inputOutputFilters.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException {
|
||||||
|
ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
||||||
SmackException, XMPPException {
|
SmackException, XMPPException {
|
||||||
|
@ -199,7 +212,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitForCondition(Supplier<Boolean> condition, String waitFor)
|
public void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor)
|
||||||
throws InterruptedException, SmackException, XMPPException {
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
|
ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
|
||||||
}
|
}
|
||||||
|
@ -217,6 +230,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
@Override
|
@Override
|
||||||
public void setTransport(XmppClientToServerTransport xmppTransport) {
|
public void setTransport(XmppClientToServerTransport xmppTransport) {
|
||||||
ModularXmppClientToServerConnection.this.activeTransport = xmppTransport;
|
ModularXmppClientToServerConnection.this.activeTransport = xmppTransport;
|
||||||
|
ModularXmppClientToServerConnection.this.connected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -556,13 +570,35 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
|
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||||
|
StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
|
||||||
|
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
|
||||||
|
}
|
||||||
|
|
||||||
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
||||||
SmackException, XMPPException {
|
SmackException, XMPPException {
|
||||||
prepareToWaitForFeaturesReceived();
|
prepareToWaitForFeaturesReceived();
|
||||||
sendStreamOpen();
|
|
||||||
|
// Create StreamOpen from StreamOpenAndCloseFactory via underlying transport.
|
||||||
|
StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
|
||||||
|
CharSequence from = null;
|
||||||
|
CharSequence localpart = connectionInternal.connection.getConfiguration().getUsername();
|
||||||
|
DomainBareJid xmppServiceDomain = getXMPPServiceDomain();
|
||||||
|
if (localpart != null) {
|
||||||
|
from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain);
|
||||||
|
}
|
||||||
|
AbstractStreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from, getStreamId(), getConfiguration().getXmlLang());
|
||||||
|
sendStreamOpen(streamOpen);
|
||||||
|
|
||||||
waitForFeaturesReceived(waitFor);
|
waitForFeaturesReceived(waitFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendStreamOpen(AbstractStreamOpen streamOpen) throws NotConnectedException, InterruptedException {
|
||||||
|
sendNonza(streamOpen);
|
||||||
|
updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
|
||||||
|
}
|
||||||
|
|
||||||
public static class DisconnectedStateDescriptor extends StateDescriptor {
|
public static class DisconnectedStateDescriptor extends StateDescriptor {
|
||||||
protected DisconnectedStateDescriptor() {
|
protected DisconnectedStateDescriptor() {
|
||||||
super(DisconnectedState.class, StateDescriptor.Property.finalState);
|
super(DisconnectedState.class, StateDescriptor.Property.finalState);
|
||||||
|
@ -904,7 +940,9 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
|
||||||
closingStreamReceived = false;
|
closingStreamReceived = false;
|
||||||
|
|
||||||
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
|
StreamOpenAndCloseFactory openAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
|
||||||
|
AbstractStreamClose closeStreamElement = openAndCloseFactory.createStreamClose();
|
||||||
|
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(closeStreamElement);
|
||||||
|
|
||||||
if (streamCloseIssued) {
|
if (streamCloseIssued) {
|
||||||
activeTransport.notifyAboutNewOutgoingElements();
|
activeTransport.notifyAboutNewOutgoingElements();
|
||||||
|
|
|
@ -96,7 +96,7 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn
|
||||||
return new ModularXmppClientToServerConnectionConfiguration(this);
|
return new ModularXmppClientToServerConnectionConfiguration(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) {
|
public void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) {
|
||||||
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = connectionModule.getClass();
|
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = connectionModule.getClass();
|
||||||
if (modulesDescriptors.containsKey(moduleDescriptorClass)) {
|
if (modulesDescriptors.containsKey(moduleDescriptorClass)) {
|
||||||
throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured");
|
throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured");
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.c2s;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamClose;
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
||||||
|
|
||||||
|
public interface StreamOpenAndCloseFactory {
|
||||||
|
AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang);
|
||||||
|
|
||||||
|
AbstractStreamClose createStreamClose();
|
||||||
|
}
|
|
@ -58,6 +58,8 @@ public abstract class XmppClientToServerTransport {
|
||||||
return getSslSession() != null;
|
return getSslSession() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract StreamOpenAndCloseFactory getStreamOpenAndCloseFactory();
|
||||||
|
|
||||||
public abstract Stats getStats();
|
public abstract Stats getStats();
|
||||||
|
|
||||||
public abstract static class Stats {
|
public abstract static class Stats {
|
||||||
|
|
|
@ -85,6 +85,8 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
|
||||||
public abstract void notifyConnectionError(Exception e);
|
public abstract void notifyConnectionError(Exception e);
|
||||||
|
|
||||||
|
public abstract void setCurrentConnectionExceptionAndNotify(Exception exception);
|
||||||
|
|
||||||
public abstract void onStreamOpen(XmlPullParser parser);
|
public abstract void onStreamOpen(XmlPullParser parser);
|
||||||
|
|
||||||
public abstract void onStreamClosed();
|
public abstract void onStreamClosed();
|
||||||
|
@ -99,6 +101,8 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
|
||||||
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
|
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
|
||||||
|
|
||||||
|
public abstract void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException;
|
||||||
|
|
||||||
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
||||||
NoResponseException, NotConnectedException, SmackException, XMPPException;
|
NoResponseException, NotConnectedException, SmackException, XMPPException;
|
||||||
|
|
||||||
|
@ -110,11 +114,17 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
|
||||||
public abstract void asyncGo(Runnable runnable);
|
public abstract void asyncGo(Runnable runnable);
|
||||||
|
|
||||||
public abstract void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException;
|
public abstract void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException;
|
||||||
|
|
||||||
public abstract void notifyWaitingThreads();
|
public abstract void notifyWaitingThreads();
|
||||||
|
|
||||||
public abstract void setCompressionEnabled(boolean compressionEnabled);
|
public abstract void setCompressionEnabled(boolean compressionEnabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active transport (TCP, BOSH, WebSocket, …) to be used for the XMPP connection. Also marks the connection
|
||||||
|
* as connected.
|
||||||
|
*
|
||||||
|
* @param xmppTransport the active transport.
|
||||||
|
*/
|
||||||
public abstract void setTransport(XmppClientToServerTransport xmppTransport);
|
public abstract void setTransport(XmppClientToServerTransport xmppTransport);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
|
||||||
public abstract class StateDescriptor {
|
public abstract class StateDescriptor {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(StateDescriptor.class.getName());
|
||||||
|
|
||||||
public enum Property {
|
public enum Property {
|
||||||
multiVisitState,
|
multiVisitState,
|
||||||
finalState,
|
finalState,
|
||||||
|
@ -133,10 +137,35 @@ public abstract class StateDescriptor {
|
||||||
addAndCheckNonExistent(precedenceOver, subordinate);
|
addAndCheckNonExistent(precedenceOver, subordinate);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void declareInferiortyTo(Class<? extends StateDescriptor> superior) {
|
protected void declarePrecedenceOver(String subordinate) {
|
||||||
|
addAndCheckNonExistent(precedenceOver, subordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void declareInferiorityTo(Class<? extends StateDescriptor> superior) {
|
||||||
addAndCheckNonExistent(inferiorTo, superior);
|
addAndCheckNonExistent(inferiorTo, superior);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void declareInferiorityTo(String superior) {
|
||||||
|
addAndCheckNonExistent(inferiorTo, superior);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addAndCheckNonExistent(Set<Class<? extends StateDescriptor>> set, String clazzName) {
|
||||||
|
Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz = Class.forName(clazzName);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// The state descriptor class is not in classpath, which probably means that the smack module is not loaded
|
||||||
|
// into the classpath. Hence we can silently ignore that.
|
||||||
|
LOGGER.log(Level.FINEST, "Ignoring unknown state descriptor '" + clazzName + "'", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!StateDescriptor.class.isAssignableFrom(clazz)) {
|
||||||
|
throw new IllegalArgumentException(clazz + " is no state descriptor class");
|
||||||
|
}
|
||||||
|
Class<? extends StateDescriptor> stateDescriptorClass = clazz.asSubclass(StateDescriptor.class);
|
||||||
|
addAndCheckNonExistent(set, stateDescriptorClass);
|
||||||
|
}
|
||||||
|
|
||||||
private static <E> void addAndCheckNonExistent(Set<E> set, E e) {
|
private static <E> void addAndCheckNonExistent(Set<E> set, E e) {
|
||||||
boolean newElement = set.add(e);
|
boolean newElement = set.add(e);
|
||||||
if (!newElement) {
|
if (!newElement) {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
public abstract class AbstractStreamClose implements Nonza {
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2020 Florian Schmaus, Aditya Borikar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractStreamOpen is actually a {@link TopLevelStreamElement}, however we
|
||||||
|
* implement {@link Nonza} here. This is because, {@link XMPPConnection} doesn't
|
||||||
|
* yet support sending {@link TopLevelStreamElement} directly and the same can only
|
||||||
|
* be achieved through {@link XMPPConnection#sendNonza(Nonza)}.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractStreamOpen implements Nonza {
|
||||||
|
public static final String CLIENT_NAMESPACE = "jabber:client";
|
||||||
|
public static final String SERVER_NAMESPACE = "jabber:server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 6120 § 4.7.5.
|
||||||
|
*/
|
||||||
|
public static final String VERSION = "1.0";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 6120 § 4.7.1.
|
||||||
|
*/
|
||||||
|
protected final String from;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 6120 § 4.7.2.
|
||||||
|
*/
|
||||||
|
protected final String to;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 6120 § 4.7.3.
|
||||||
|
*/
|
||||||
|
protected final String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 6120 § 4.7.4.
|
||||||
|
*/
|
||||||
|
protected final String lang;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 6120 § 4.8.2.
|
||||||
|
*/
|
||||||
|
protected final String contentNamespace;
|
||||||
|
|
||||||
|
public AbstractStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||||
|
this(to, from, id, lang, StreamContentNamespace.client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractStreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) {
|
||||||
|
this.to = StringUtils.maybeToString(to);
|
||||||
|
this.from = StringUtils.maybeToString(from);
|
||||||
|
this.id = id;
|
||||||
|
this.lang = lang;
|
||||||
|
switch (ns) {
|
||||||
|
case client:
|
||||||
|
this.contentNamespace = CLIENT_NAMESPACE;
|
||||||
|
break;
|
||||||
|
case server:
|
||||||
|
this.contentNamespace = SERVER_NAMESPACE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void addCommonAttributes(XmlStringBuilder xml) {
|
||||||
|
xml.optAttribute("to", to);
|
||||||
|
xml.optAttribute("version", VERSION);
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,11 +90,12 @@ public interface StanzaView extends XmlLangElement {
|
||||||
default <E extends ExtensionElement> E getExtension(Class<E> extensionElementClass) {
|
default <E extends ExtensionElement> E getExtension(Class<E> extensionElementClass) {
|
||||||
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
|
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
|
||||||
ExtensionElement extensionElement = getExtension(qname);
|
ExtensionElement extensionElement = getExtension(qname);
|
||||||
if (!extensionElementClass.isInstance(extensionElement)) {
|
|
||||||
|
if (extensionElement == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionElementClass.cast(extensionElement);
|
return XmppElementUtil.castOrThrow(extensionElement, extensionElementClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
public final class StreamClose implements Nonza {
|
public final class StreamClose extends AbstractStreamClose {
|
||||||
|
|
||||||
public static final StreamClose INSTANCE = new StreamClose();
|
public static final StreamClose INSTANCE = new StreamClose();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2019 Florian Schmaus
|
* Copyright © 2014-2020 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,49 +17,14 @@
|
||||||
|
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The stream open <b>tag</b>.
|
* The stream open <b>tag</b>.
|
||||||
*/
|
*/
|
||||||
public class StreamOpen implements Nonza {
|
public final class StreamOpen extends AbstractStreamOpen {
|
||||||
|
|
||||||
public static final String ELEMENT = "stream:stream";
|
public static final String ELEMENT = "stream:stream";
|
||||||
|
|
||||||
public static final String CLIENT_NAMESPACE = "jabber:client";
|
|
||||||
public static final String SERVER_NAMESPACE = "jabber:server";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 6120 § 4.7.5.
|
|
||||||
*/
|
|
||||||
public static final String VERSION = "1.0";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 6120 § 4.7.1.
|
|
||||||
*/
|
|
||||||
private final String from;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 6120 § 4.7.2.
|
|
||||||
*/
|
|
||||||
private final String to;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 6120 § 4.7.3.
|
|
||||||
*/
|
|
||||||
private final String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 6120 § 4.7.4.
|
|
||||||
*/
|
|
||||||
private final String lang;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RFC 6120 § 4.8.2.
|
|
||||||
*/
|
|
||||||
private final String contentNamespace;
|
|
||||||
|
|
||||||
public StreamOpen(CharSequence to) {
|
public StreamOpen(CharSequence to) {
|
||||||
this(to, null, null, null, StreamContentNamespace.client);
|
this(to, null, null, null, StreamContentNamespace.client);
|
||||||
}
|
}
|
||||||
|
@ -68,21 +33,12 @@ public class StreamOpen implements Nonza {
|
||||||
this(to, from, id, "en", StreamContentNamespace.client);
|
this(to, from, id, "en", StreamContentNamespace.client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||||
|
super(to, from, id, lang);
|
||||||
|
}
|
||||||
|
|
||||||
public StreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) {
|
public StreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) {
|
||||||
this.to = StringUtils.maybeToString(to);
|
super(to, from, id, lang, ns);
|
||||||
this.from = StringUtils.maybeToString(from);
|
|
||||||
this.id = id;
|
|
||||||
this.lang = lang;
|
|
||||||
switch (ns) {
|
|
||||||
case client:
|
|
||||||
this.contentNamespace = CLIENT_NAMESPACE;
|
|
||||||
break;
|
|
||||||
case server:
|
|
||||||
this.contentNamespace = SERVER_NAMESPACE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class XmlEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEffectiveNamespaceOrUse(String namespace) {
|
public String getEffectiveNamespaceOrUse(String namespace) {
|
||||||
String effectiveNamespace = getEffectiveLanguage();
|
String effectiveNamespace = getEffectiveNamespace();
|
||||||
if (StringUtils.isNullOrEmpty(effectiveNamespace)) {
|
if (StringUtils.isNullOrEmpty(effectiveNamespace)) {
|
||||||
return namespace;
|
return namespace;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ public class XmlEnvironment {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder with(StreamOpen streamOpen) {
|
public Builder with(AbstractStreamOpen streamOpen) {
|
||||||
withNamespace(streamOpen.getNamespace());
|
withNamespace(streamOpen.getNamespace());
|
||||||
withLanguage(streamOpen.getLanguage());
|
withLanguage(streamOpen.getLanguage());
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -243,7 +243,11 @@ public final class ProviderManager {
|
||||||
*/
|
*/
|
||||||
public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(String elementName, String namespace) {
|
public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(String elementName, String namespace) {
|
||||||
QName key = getQName(elementName, namespace);
|
QName key = getQName(elementName, namespace);
|
||||||
return extensionProviders.get(key);
|
return getExtensionProvider(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(QName qname) {
|
||||||
|
return extensionProviders.get(qname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -82,14 +82,7 @@ public class PacketParserUtils {
|
||||||
|
|
||||||
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
|
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
|
||||||
XmlPullParser parser = SmackXmlParser.newXmlParser(reader);
|
XmlPullParser parser = SmackXmlParser.newXmlParser(reader);
|
||||||
// Wind the parser forward to the first start tag
|
ParserUtils.forwardToStartElement(parser);
|
||||||
XmlPullParser.Event event = parser.getEventType();
|
|
||||||
while (event != XmlPullParser.Event.START_ELEMENT) {
|
|
||||||
if (event == XmlPullParser.Event.END_DOCUMENT) {
|
|
||||||
throw new IllegalArgumentException("Document contains no start tag");
|
|
||||||
}
|
|
||||||
event = parser.next();
|
|
||||||
}
|
|
||||||
return parser;
|
return parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,17 @@ public class ParserUtils {
|
||||||
assert parser.getEventType() == XmlPullParser.Event.END_ELEMENT;
|
assert parser.getEventType() == XmlPullParser.Event.END_ELEMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void forwardToStartElement(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
// Wind the parser forward to the first start tag
|
||||||
|
XmlPullParser.Event event = parser.getEventType();
|
||||||
|
while (event != XmlPullParser.Event.START_ELEMENT) {
|
||||||
|
if (event == XmlPullParser.Event.END_DOCUMENT) {
|
||||||
|
throw new IllegalArgumentException("Document contains no start tag");
|
||||||
|
}
|
||||||
|
event = parser.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void forwardToEndTagOfDepth(XmlPullParser parser, int depth)
|
public static void forwardToEndTagOfDepth(XmlPullParser parser, int depth)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
XmlPullParser.Event event = parser.getEventType();
|
XmlPullParser.Event event = parser.getEventType();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2014-2019 Florian Schmaus
|
* Copyright 2014-2020 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.
|
||||||
|
@ -24,7 +24,6 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.Element;
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
|
||||||
import org.jivesoftware.smack.packet.FullyQualifiedElement;
|
import org.jivesoftware.smack.packet.FullyQualifiedElement;
|
||||||
import org.jivesoftware.smack.packet.NamedElement;
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
@ -43,7 +42,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
|
||||||
effectiveXmlEnvironment = null;
|
effectiveXmlEnvironment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmlStringBuilder(ExtensionElement pe) {
|
public XmlStringBuilder(FullyQualifiedElement pe) {
|
||||||
this(pe, null);
|
this(pe, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,17 +23,31 @@ import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.FullyQualifiedElement;
|
import org.jivesoftware.smack.packet.FullyQualifiedElement;
|
||||||
|
import org.jivesoftware.smack.packet.StandardExtensionElement;
|
||||||
|
import org.jivesoftware.smack.provider.ProviderManager;
|
||||||
|
|
||||||
|
import org.jxmpp.util.cache.LruCache;
|
||||||
|
|
||||||
public class XmppElementUtil {
|
public class XmppElementUtil {
|
||||||
|
|
||||||
|
private static final LruCache<Class<? extends FullyQualifiedElement>, QName> CLASS_TO_QNAME_CACHE = new LruCache<>(512);
|
||||||
|
|
||||||
public static final Logger LOGGER = Logger.getLogger(XmppElementUtil.class.getName());
|
public static final Logger LOGGER = Logger.getLogger(XmppElementUtil.class.getName());
|
||||||
|
|
||||||
public static QName getQNameFor(Class<? extends FullyQualifiedElement> fullyQualifiedElement) {
|
public static QName getQNameFor(Class<? extends FullyQualifiedElement> fullyQualifiedElement) {
|
||||||
|
QName qname = CLASS_TO_QNAME_CACHE.get(fullyQualifiedElement);
|
||||||
|
if (qname != null) {
|
||||||
|
return qname;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object qnameObject = fullyQualifiedElement.getField("QNAME").get(null);
|
Object qnameObject = fullyQualifiedElement.getField("QNAME").get(null);
|
||||||
if (QName.class.isAssignableFrom(qnameObject.getClass())) {
|
if (QName.class.isAssignableFrom(qnameObject.getClass())) {
|
||||||
return (QName) qnameObject;
|
qname = (QName) qnameObject;
|
||||||
|
CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname);
|
||||||
|
return qname;
|
||||||
}
|
}
|
||||||
LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME.");
|
LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME.");
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
|
@ -52,24 +66,52 @@ public class XmppElementUtil {
|
||||||
throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e);
|
throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QName(namespace, element);
|
qname = new QName(namespace, element);
|
||||||
|
CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname);
|
||||||
|
return qname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <E extends FullyQualifiedElement, R extends FullyQualifiedElement> List<R> getElementsFrom(
|
public static <E extends ExtensionElement> List<E> getElementsFrom(
|
||||||
MultiMap<QName, E> elementMap, Class<R> extensionElementClass) {
|
MultiMap<QName, ExtensionElement> elementMap, Class<E> extensionElementClass) {
|
||||||
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
|
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
|
||||||
|
|
||||||
List<E> extensionElements = elementMap.getAll(qname);
|
List<ExtensionElement> extensionElements = elementMap.getAll(qname);
|
||||||
|
|
||||||
if (extensionElements.isEmpty()) {
|
if (extensionElements.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<R> res = new ArrayList<>(extensionElements.size());
|
List<E> res = new ArrayList<>(extensionElements.size());
|
||||||
for (E extensionElement : extensionElements) {
|
for (ExtensionElement extensionElement : extensionElements) {
|
||||||
R e = extensionElementClass.cast(extensionElement);
|
E e = castOrThrow(extensionElement, extensionElementClass);
|
||||||
res.add(e);
|
res.add(e);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <E extends ExtensionElement> E castOrThrow(ExtensionElement extensionElement, Class<E> extensionElementClass) {
|
||||||
|
if (!extensionElementClass.isInstance(extensionElement)) {
|
||||||
|
final QName qname = getQNameFor(extensionElementClass);
|
||||||
|
|
||||||
|
final String detailMessage;
|
||||||
|
if (extensionElement instanceof StandardExtensionElement) {
|
||||||
|
detailMessage = "because there is no according extension element provider registered with ProviderManager for "
|
||||||
|
+ qname
|
||||||
|
+ ". WARNING: This indicates a serious problem with your Smack setup, probably causing Smack not being able to properly initialize itself.";
|
||||||
|
} else {
|
||||||
|
Object provider = ProviderManager.getExtensionProvider(qname);
|
||||||
|
detailMessage = "because there is an inconsistency with the provider registered with ProviderManager: the active provider for "
|
||||||
|
+ qname + " '" + provider.getClass()
|
||||||
|
+ "' does not return instances of type " + extensionElementClass
|
||||||
|
+ ", but instead returns instances of type " + extensionElement.getClass() + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = "Extension element is not of expected class '" + extensionElementClass.getName() + "', "
|
||||||
|
+ detailMessage;
|
||||||
|
throw new IllegalStateException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensionElementClass.cast(extensionElement);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.jivesoftware.smack.util.rce;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.ToStringUtil;
|
import org.jivesoftware.smack.util.ToStringUtil;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.minidns.dnsname.DnsName;
|
import org.minidns.dnsname.DnsName;
|
||||||
|
|
||||||
public abstract class RemoteConnectionEndpointLookupFailure {
|
public abstract class RemoteConnectionEndpointLookupFailure {
|
||||||
|
@ -67,4 +68,17 @@ public abstract class RemoteConnectionEndpointLookupFailure {
|
||||||
return dnsName;
|
return dnsName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class HttpLookupFailure extends RemoteConnectionEndpointLookupFailure {
|
||||||
|
private final DomainBareJid host;
|
||||||
|
|
||||||
|
public HttpLookupFailure(DomainBareJid host, Exception exception) {
|
||||||
|
super("Http lookup exception for " + host, exception);
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DomainBareJid getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<className>org.jivesoftware.smack.android.AndroidSmackInitializer</className>
|
<className>org.jivesoftware.smack.android.AndroidSmackInitializer</className>
|
||||||
<className>org.jivesoftware.smack.java7.Java7SmackInitializer</className>
|
<className>org.jivesoftware.smack.java7.Java7SmackInitializer</className>
|
||||||
<className>org.jivesoftware.smack.im.SmackImInitializer</className>
|
<className>org.jivesoftware.smack.im.SmackImInitializer</className>
|
||||||
|
<className>org.jivesoftware.smack.websocket.WebsocketInitializer</className>
|
||||||
<className>org.jivesoftware.smackx.omemo.OmemoInitializer</className>
|
<className>org.jivesoftware.smackx.omemo.OmemoInitializer</className>
|
||||||
<className>org.jivesoftware.smackx.ox.util.OpenPgpInitializer</className>
|
<className>org.jivesoftware.smackx.ox.util.OpenPgpInitializer</className>
|
||||||
</optionalStartupClasses>
|
</optionalStartupClasses>
|
||||||
|
|
|
@ -18,9 +18,11 @@ package org.jivesoftware.smackx.mam.element;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.Element;
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.MessageView;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
@ -54,6 +56,11 @@ public class MamElements {
|
||||||
*/
|
*/
|
||||||
public static final String ELEMENT = "result";
|
public static final String ELEMENT = "result";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The qualified name of the MAM result extension element.
|
||||||
|
*/
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* id of the result.
|
* id of the result.
|
||||||
*/
|
*/
|
||||||
|
@ -139,8 +146,8 @@ public class MamElements {
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MamResultExtension from(Message message) {
|
public static MamResultExtension from(MessageView message) {
|
||||||
return (MamResultExtension) message.getExtensionElement(ELEMENT, NAMESPACE);
|
return message.getExtension(MamResultExtension.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017-2019 Florian Schmaus
|
* Copyright 2017-2020 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.
|
||||||
|
@ -151,7 +151,7 @@ public class JingleReason implements FullyQualifiedElement {
|
||||||
|
|
||||||
@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, enclosingNamespace);
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
|
|
||||||
xml.openElement(reason.asString);
|
xml.openElement(reason.asString);
|
||||||
|
|
|
@ -44,6 +44,7 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||||
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.util.MultiMap;
|
import org.jivesoftware.smack.util.MultiMap;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModuleDescriptor;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
|
import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
|
||||||
import org.jivesoftware.smackx.iqregister.AccountManager;
|
import org.jivesoftware.smackx.iqregister.AccountManager;
|
||||||
|
@ -86,6 +87,15 @@ public class XmppConnectionManager {
|
||||||
.applyExtraConfguration(cb -> cb.removeModule(CompressionModuleDescriptor.class))
|
.applyExtraConfguration(cb -> cb.removeModule(CompressionModuleDescriptor.class))
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
addConnectionDescriptor(
|
||||||
|
XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class)
|
||||||
|
.withNickname("modular-websocket")
|
||||||
|
.applyExtraConfguration(cb -> {
|
||||||
|
cb.removeAllModules();
|
||||||
|
cb.addModule(XmppWebsocketTransportModuleDescriptor.class);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
);
|
||||||
} catch (NoSuchMethodException | SecurityException e) {
|
} catch (NoSuchMethodException | SecurityException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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')
|
||||||
api project(':smack-tcp')
|
api project(':smack-tcp')
|
||||||
|
|
||||||
testImplementation 'com.google.guava:guava:28.2-jre'
|
testImplementation 'com.google.guava:guava:28.2-jre'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
digraph {
|
digraph {
|
||||||
"Disconnected" -> "LookupRemoteConnectionEndpoints";
|
"Disconnected" -> "LookupRemoteConnectionEndpoints";
|
||||||
"LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection";
|
"LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection" [xlabel="1"];
|
||||||
"EstablishingTcpConnection" -> "EstablishTls (RFC 6120 § 5)" [xlabel="1"];
|
"EstablishingTcpConnection" -> "EstablishTls (RFC 6120 § 5)" [xlabel="1"];
|
||||||
"EstablishTls (RFC 6120 § 5)" -> "ConnectedButUnauthenticated";
|
"EstablishTls (RFC 6120 § 5)" -> "ConnectedButUnauthenticated";
|
||||||
"ConnectedButUnauthenticated" -> "Bind2 (XEP-0386)" [xlabel="1"];
|
"ConnectedButUnauthenticated" -> "Bind2 (XEP-0386)" [xlabel="1"];
|
||||||
|
@ -32,5 +32,7 @@ digraph {
|
||||||
"ConnectedButUnauthenticated" -> "InstantShutdown" [xlabel="5"];
|
"ConnectedButUnauthenticated" -> "InstantShutdown" [xlabel="5"];
|
||||||
"ConnectedButUnauthenticated" [ style=filled ]
|
"ConnectedButUnauthenticated" [ style=filled ]
|
||||||
"EstablishingTcpConnection" -> "ConnectedButUnauthenticated" [xlabel="2"];
|
"EstablishingTcpConnection" -> "ConnectedButUnauthenticated" [xlabel="2"];
|
||||||
|
"LookupRemoteConnectionEndpoints" -> "EstablishingWebsocketConnection" [xlabel="2"];
|
||||||
|
"EstablishingWebsocketConnection" -> "ConnectedButUnauthenticated";
|
||||||
"Disconnected" [ style=filled ]
|
"Disconnected" [ style=filled ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2020 Aditya Borikar
|
||||||
|
*
|
||||||
|
* This file is part of smack-repl.
|
||||||
|
*
|
||||||
|
* smack-repl is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.igniterealtime.smack.smackrepl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
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.websocket.XmppWebsocketTransportModuleDescriptor;
|
||||||
|
|
||||||
|
public class WebsocketConnection {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException, URISyntaxException {
|
||||||
|
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
|
||||||
|
builder.removeAllModules();
|
||||||
|
builder.setXmppAddressAndPassword(args[0], args[1]);
|
||||||
|
|
||||||
|
// Set a fallback uri into websocket transport descriptor and add this descriptor into connection builder.
|
||||||
|
XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder);
|
||||||
|
websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI(args[2]), false);
|
||||||
|
builder.addModule(websocketBuilder.build());
|
||||||
|
|
||||||
|
ModularXmppClientToServerConnectionConfiguration config = builder.build();
|
||||||
|
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
|
||||||
|
|
||||||
|
connection.connect();
|
||||||
|
connection.login();
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
8
smack-streammanagement/build.gradle
Normal file
8
smack-streammanagement/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
description = """\
|
||||||
|
Smack support for XMPP Stream Management (XEP-0198)."""
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(':smack-core')
|
||||||
|
|
||||||
|
testFixturesApi(testFixtures(project(":smack-core")))
|
||||||
|
}
|
|
@ -91,7 +91,7 @@ public class StreamManagementModule extends ModularXmppClientToServerConnectionM
|
||||||
addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
|
addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
|
||||||
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
|
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
|
||||||
declarePrecedenceOver(ResourceBindingStateDescriptor.class);
|
declarePrecedenceOver(ResourceBindingStateDescriptor.class);
|
||||||
declareInferiortyTo(CompressionStateDescriptor.class);
|
declareInferiorityTo(CompressionStateDescriptor.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -3,6 +3,7 @@ Smack for standard XMPP connections over TCP."""
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':smack-core')
|
compile project(':smack-core')
|
||||||
|
api project(':smack-streammanagement')
|
||||||
|
|
||||||
testFixturesApi(testFixtures(project(":smack-core")))
|
testFixturesApi(testFixtures(project(":smack-core")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014 Florian Schmaus
|
* Copyright © 2014-2020 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.
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sm.predicates;
|
package org.jivesoftware.smack.sm.predicates.tcp;
|
||||||
|
|
||||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2020 Florian Schmaus
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPPTCPConnection Stream Managment Predicates.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.sm.predicates.tcp;
|
|
@ -538,15 +538,24 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are able to resume the stream, then don't set
|
// If we are able to resume the stream, then don't set
|
||||||
// connected/authenticated/usingTLS to false since we like behave like we are still
|
// connected/authenticated/usingTLS to false since we like to behave like we are still
|
||||||
// connected (e.g. sendStanza should not throw a NotConnectedException).
|
// connected (e.g. sendStanza should not throw a NotConnectedException).
|
||||||
if (isSmResumptionPossible() && instant) {
|
if (instant) {
|
||||||
disconnectedButResumeable = true;
|
disconnectedButResumeable = isSmResumptionPossible();
|
||||||
|
if (!disconnectedButResumeable) {
|
||||||
|
// Reset the stream management session id to null, since the stream is no longer resumable. Note that we
|
||||||
|
// keep the unacknowledgedStanzas queue, because we want to resend them when we are reconnected.
|
||||||
|
smSessionId = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
disconnectedButResumeable = false;
|
disconnectedButResumeable = false;
|
||||||
// Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing
|
|
||||||
// stream tag, there is no longer a stream to resume.
|
// Drop the stream management state if this is not an instant shutdown. We send
|
||||||
smSessionId = null;
|
// a </stream> close tag and now the stream management state is no longer valid.
|
||||||
|
// This also prevents that we will potentially (re-)send any unavailable presence we
|
||||||
|
// may have send, because it got put into the unacknowledged queue and was not acknowledged before the
|
||||||
|
// connection terminated.
|
||||||
|
dropSmState();
|
||||||
// Note that we deliberately do not reset authenticatedConnectionInitiallyEstablishedTimestamp here, so that the
|
// Note that we deliberately do not reset authenticatedConnectionInitiallyEstablishedTimestamp here, so that the
|
||||||
// information is available in the connectionClosedOnError() listeners.
|
// information is available in the connectionClosedOnError() listeners.
|
||||||
}
|
}
|
||||||
|
@ -836,6 +845,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// there is an error establishing the connection
|
// there is an error establishing the connection
|
||||||
connectUsingConfiguration();
|
connectUsingConfiguration();
|
||||||
|
|
||||||
|
connected = true;
|
||||||
|
|
||||||
// We connected successfully to the servers TCP port
|
// We connected successfully to the servers TCP port
|
||||||
initConnection();
|
initConnection();
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.jivesoftware.smack.XmppInputOutputFilter;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
||||||
|
import org.jivesoftware.smack.c2s.StreamOpenAndCloseFactory;
|
||||||
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
|
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;
|
||||||
|
@ -68,6 +69,7 @@ import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||||
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.StreamClose;
|
||||||
import org.jivesoftware.smack.packet.StreamOpen;
|
import org.jivesoftware.smack.packet.StreamOpen;
|
||||||
import org.jivesoftware.smack.packet.TlsFailure;
|
import org.jivesoftware.smack.packet.TlsFailure;
|
||||||
import org.jivesoftware.smack.packet.TlsProceed;
|
import org.jivesoftware.smack.packet.TlsProceed;
|
||||||
|
@ -580,6 +582,22 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
super(connectionInternal);
|
super(connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
||||||
|
return new StreamOpenAndCloseFactory() {
|
||||||
|
@Override
|
||||||
|
public StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||||
|
String xmlLang = connectionInternal.connection.getConfiguration().getXmlLang();
|
||||||
|
StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client);
|
||||||
|
return streamOpen;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public StreamClose createStreamClose() {
|
||||||
|
return StreamClose.INSTANCE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetDiscoveredConnectionEndpoints() {
|
protected void resetDiscoveredConnectionEndpoints() {
|
||||||
discoveredTcpEndpoints = null;
|
discoveredTcpEndpoints = null;
|
||||||
|
@ -1165,7 +1183,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, SmackException, XMPPException {
|
private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, SmackException, XMPPException {
|
||||||
connectionInternal.waitForCondition(() -> isHandshakeFinished(), "TLS handshake to finish");
|
connectionInternal.waitForConditionOrThrowConnectionException(() -> isHandshakeFinished(), "TLS handshake to finish");
|
||||||
|
|
||||||
if (handshakeStatus == TlsHandshakeStatus.failed) {
|
if (handshakeStatus == TlsHandshakeStatus.failed) {
|
||||||
throw handshakeException;
|
throw handshakeException;
|
||||||
|
|
10
smack-websocket/build.gradle
Normal file
10
smack-websocket/build.gradle
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
description = """\
|
||||||
|
Smack for standard XMPP connections over Websockets."""
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':smack-core')
|
||||||
|
|
||||||
|
testFixturesApi(testFixtures(project(":smack-core")))
|
||||||
|
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.6.0")
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.EstablishingWebsocketConnectionState;
|
||||||
|
import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket;
|
||||||
|
import org.jivesoftware.smack.websocket.implementations.WebsocketImplProvider;
|
||||||
|
import org.jivesoftware.smack.websocket.implementations.okhttp.OkHttpWebsocket;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
|
public final class WebsocketConnectionAttemptState {
|
||||||
|
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||||
|
private final XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints discoveredEndpoints;
|
||||||
|
|
||||||
|
private WebsocketRemoteConnectionEndpoint connectedEndpoint;
|
||||||
|
|
||||||
|
WebsocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
|
||||||
|
XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints,
|
||||||
|
EstablishingWebsocketConnectionState establishingWebsocketConnectionState) {
|
||||||
|
assert discoveredWebsocketEndpoints != null;
|
||||||
|
this.connectionInternal = connectionInternal;
|
||||||
|
this.discoveredEndpoints = discoveredWebsocketEndpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish a websocket connection with one of the discoveredRemoteConnectionEndpoints.<br>
|
||||||
|
*
|
||||||
|
* @return {@link AbstractWebsocket} with which connection is establised
|
||||||
|
* @throws InterruptedException if the calling thread was interrupted
|
||||||
|
* @throws WebsocketException if encounters a websocket exception
|
||||||
|
*/
|
||||||
|
AbstractWebsocket establishWebsocketConnection() throws InterruptedException, WebsocketException {
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
|
||||||
|
|
||||||
|
if (endpoints.isEmpty()) {
|
||||||
|
throw new WebsocketException(new Throwable("No Endpoints discovered to establish connection"));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Throwable> connectionFailureList = new ArrayList<>();
|
||||||
|
AbstractWebsocket websocket;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Obtain desired websocket implementation by using WebsocketImplProvider
|
||||||
|
websocket = WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, connectionInternal, discoveredEndpoints);
|
||||||
|
} catch (NoSuchMethodException | SecurityException | InstantiationException |
|
||||||
|
IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
|
||||||
|
throw new WebsocketException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with.
|
||||||
|
for (WebsocketRemoteConnectionEndpoint endpoint : endpoints) {
|
||||||
|
try {
|
||||||
|
websocket.connect(endpoint);
|
||||||
|
connectedEndpoint = endpoint;
|
||||||
|
break;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
connectionFailureList.add(t);
|
||||||
|
|
||||||
|
// If the number of entries in connectionFailureList is equal to the number of endpoints,
|
||||||
|
// it means that all endpoints have been tried and have been unsuccessful.
|
||||||
|
if (connectionFailureList.size() == endpoints.size()) {
|
||||||
|
WebsocketException websocketException = new WebsocketException(connectionFailureList);
|
||||||
|
throw new WebsocketException(websocketException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert connectedEndpoint != null;
|
||||||
|
|
||||||
|
// Return connected websocket when no failure occurs.
|
||||||
|
return websocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the connected websocket endpoint.
|
||||||
|
*
|
||||||
|
* @return connected websocket endpoint
|
||||||
|
*/
|
||||||
|
public WebsocketRemoteConnectionEndpoint getConnectedEndpoint() {
|
||||||
|
return connectedEndpoint;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class WebsocketException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final List<Throwable> throwableList;
|
||||||
|
|
||||||
|
public WebsocketException(List<Throwable> throwableList) {
|
||||||
|
this.throwableList = throwableList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebsocketException(Throwable throwable) {
|
||||||
|
this.throwableList = Collections.singletonList(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Throwable> getThrowableList() {
|
||||||
|
return throwableList;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
|
import org.jivesoftware.smack.initializer.UrlInitializer;
|
||||||
|
|
||||||
|
public final class WebsocketInitializer extends UrlInitializer {
|
||||||
|
|
||||||
|
static {
|
||||||
|
SmackConfiguration.addModule(XmppWebsocketTransportModuleDescriptor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,325 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.AsyncButOrdered;
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
|
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
||||||
|
import org.jivesoftware.smack.c2s.StreamOpenAndCloseFactory;
|
||||||
|
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
|
||||||
|
import org.jivesoftware.smack.fsm.State;
|
||||||
|
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||||
|
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||||
|
import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamClose;
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
|
||||||
|
import org.jivesoftware.smack.websocket.elements.WebsocketCloseElement;
|
||||||
|
import org.jivesoftware.smack.websocket.elements.WebsocketOpenElement;
|
||||||
|
import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The websocket transport module that goes with Smack's modular architecture.
|
||||||
|
*/
|
||||||
|
public final class XmppWebsocketTransportModule
|
||||||
|
extends ModularXmppClientToServerConnectionModule<XmppWebsocketTransportModuleDescriptor> {
|
||||||
|
private final XmppWebsocketTransport websocketTransport;
|
||||||
|
|
||||||
|
private AbstractWebsocket websocket;
|
||||||
|
|
||||||
|
protected XmppWebsocketTransportModule(XmppWebsocketTransportModuleDescriptor moduleDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
|
super(moduleDescriptor, connectionInternal);
|
||||||
|
|
||||||
|
websocketTransport = new XmppWebsocketTransport(connectionInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected XmppWebsocketTransport getTransport() {
|
||||||
|
return websocketTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class EstablishingWebsocketConnectionStateDescriptor extends StateDescriptor {
|
||||||
|
private EstablishingWebsocketConnectionStateDescriptor() {
|
||||||
|
super(XmppWebsocketTransportModule.EstablishingWebsocketConnectionState.class);
|
||||||
|
addPredeccessor(LookupRemoteConnectionEndpointsStateDescriptor.class);
|
||||||
|
addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class);
|
||||||
|
|
||||||
|
// This states preference to TCP transports over this Websocket transport implementation.
|
||||||
|
declareInferiorityTo("org.jivesoftware.smack.tcp.XmppTcpTransportModule$EstablishingTcpConnectionStateDescriptor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
|
XmppWebsocketTransportModule websocketTransportModule = connectionInternal.connection.getConnectionModuleFor(
|
||||||
|
XmppWebsocketTransportModuleDescriptor.class);
|
||||||
|
return websocketTransportModule.constructEstablishingWebsocketConnectionState(this, connectionInternal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EstablishingWebsocketConnectionState extends State {
|
||||||
|
protected EstablishingWebsocketConnectionState(StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
|
super(stateDescriptor, connectionInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
|
throws IOException, SmackException, InterruptedException, XMPPException {
|
||||||
|
WebsocketConnectionAttemptState connectionAttemptState = new WebsocketConnectionAttemptState(
|
||||||
|
connectionInternal, discoveredWebsocketEndpoints, this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
websocket = connectionAttemptState.establishWebsocketConnection();
|
||||||
|
} catch (InterruptedException | WebsocketException e) {
|
||||||
|
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionInternal.setTransport(websocketTransport);
|
||||||
|
|
||||||
|
WebsocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint();
|
||||||
|
|
||||||
|
// Construct a WebsocketConnectedResult using the connected endpoint.
|
||||||
|
return new WebsocketConnectedResult(connectedEndpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EstablishingWebsocketConnectionState constructEstablishingWebsocketConnectionState(
|
||||||
|
EstablishingWebsocketConnectionStateDescriptor establishingWebsocketConnectionStateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
|
return new EstablishingWebsocketConnectionState(establishingWebsocketConnectionStateDescriptor,
|
||||||
|
connectionInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class WebsocketConnectedResult extends StateTransitionResult.Success {
|
||||||
|
final WebsocketRemoteConnectionEndpoint connectedEndpoint;
|
||||||
|
|
||||||
|
public WebsocketConnectedResult(WebsocketRemoteConnectionEndpoint connectedEndpoint) {
|
||||||
|
super("Websocket connection establised with endpoint: " + connectedEndpoint.getWebsocketEndpoint());
|
||||||
|
this.connectedEndpoint = connectedEndpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport class for {@link ModularXmppClientToServerConnectionModule}'s websocket implementation.
|
||||||
|
*/
|
||||||
|
public final class XmppWebsocketTransport extends XmppClientToServerTransport {
|
||||||
|
|
||||||
|
AsyncButOrdered<Queue<TopLevelStreamElement>> asyncButOrderedOutgoingElementsQueue;
|
||||||
|
|
||||||
|
protected XmppWebsocketTransport(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
|
super(connectionInternal);
|
||||||
|
asyncButOrderedOutgoingElementsQueue = new AsyncButOrdered<Queue<TopLevelStreamElement>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetDiscoveredConnectionEndpoints() {
|
||||||
|
discoveredWebsocketEndpoints = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
||||||
|
// Assert that there are no stale discovered endpoints prior performing the lookup.
|
||||||
|
assert discoveredWebsocketEndpoints == null;
|
||||||
|
|
||||||
|
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
|
||||||
|
|
||||||
|
connectionInternal.asyncGo(() -> {
|
||||||
|
|
||||||
|
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();
|
||||||
|
SecurityMode mode = configuration.getSecurityMode();
|
||||||
|
|
||||||
|
// Fetch remote endpoints.
|
||||||
|
Result xep0156result = WebsocketRemoteConnectionEndpointLookup.lookup(host, mode);
|
||||||
|
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> discoveredEndpoints = xep0156result.discoveredRemoteConnectionEndpoints;
|
||||||
|
|
||||||
|
// Generate result considering both manual and fetched endpoints.
|
||||||
|
Result finalResult = new Result(discoveredEndpoints, xep0156result.getLookupFailures());
|
||||||
|
|
||||||
|
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebsocketEndpoints(finalResult);
|
||||||
|
|
||||||
|
websocketEndpointsLookupFuture.setResult(endpointsResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Collections.singletonList(websocketEndpointsLookupFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess) {
|
||||||
|
discoveredWebsocketEndpoints = (DiscoveredWebsocketEndpoints) lookupConnectionEndpointsSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterFiltersClosed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void disconnect() {
|
||||||
|
websocket.disconnect(1000, "Websocket closed normally");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notifyAboutNewOutgoingElements() {
|
||||||
|
Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
|
||||||
|
asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> {
|
||||||
|
// Once new outgoingElement is notified, send the top level stream element obtained by polling.
|
||||||
|
TopLevelStreamElement topLevelStreamElement = outgoingElementsQueue.poll();
|
||||||
|
websocket.send(topLevelStreamElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getSslSession() {
|
||||||
|
return websocket.getSSLSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTransportSecured() {
|
||||||
|
return websocket.isConnectionSecure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return websocket.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stats getStats() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
||||||
|
return new StreamOpenAndCloseFactory() {
|
||||||
|
@Override
|
||||||
|
public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||||
|
try {
|
||||||
|
return new WebsocketOpenElement(JidCreate.domainBareFrom(to));
|
||||||
|
} catch (XmppStringprepException e) {
|
||||||
|
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public AbstractStreamClose createStreamClose() {
|
||||||
|
return new WebsocketCloseElement();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains {@link Result} for successfully discovered endpoints.
|
||||||
|
*/
|
||||||
|
public final class DiscoveredWebsocketEndpoints implements LookupConnectionEndpointsSuccess {
|
||||||
|
final WebsocketRemoteConnectionEndpointLookup.Result result;
|
||||||
|
|
||||||
|
DiscoveredWebsocketEndpoints(Result result) {
|
||||||
|
assert result != null;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebsocketRemoteConnectionEndpointLookup.Result getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains list of {@link RemoteConnectionEndpointLookupFailure} when no endpoint
|
||||||
|
* could be found during http lookup.
|
||||||
|
*/
|
||||||
|
final class WebsocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
|
||||||
|
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||||
|
|
||||||
|
WebsocketEndpointsDiscoveryFailed(
|
||||||
|
WebsocketRemoteConnectionEndpointLookup.Result result) {
|
||||||
|
assert result != null;
|
||||||
|
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
StringUtils.appendTo(lookupFailures, str);
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||||
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.EstablishingWebsocketConnectionStateDescriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor class for {@link XmppWebsocketTransportModule}.
|
||||||
|
* <br>
|
||||||
|
* To add {@link XmppWebsocketTransportModule} to {@link ModularXmppClientToServerConnection},
|
||||||
|
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
|
||||||
|
*/
|
||||||
|
public final class XmppWebsocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
|
||||||
|
private boolean performWebsocketEndpointDiscovery;
|
||||||
|
private URI uri;
|
||||||
|
|
||||||
|
public XmppWebsocketTransportModuleDescriptor(Builder builder) {
|
||||||
|
this.performWebsocketEndpointDiscovery = builder.performWebsocketEndpointDiscovery;
|
||||||
|
this.uri = builder.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if websocket endpoint discovery is true, returns false otherwise.
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean isWebsocketEndpointDiscoveryEnabled() {
|
||||||
|
return performWebsocketEndpointDiscovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns explicitly configured websocket endpoint uri.
|
||||||
|
* @return uri
|
||||||
|
*/
|
||||||
|
public URI getExplicitlyProvidedUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
|
||||||
|
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
|
||||||
|
res.add(EstablishingWebsocketConnectionStateDescriptor.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> constructXmppConnectionModule(
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
|
return new XmppWebsocketTransportModule(this, connectionInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new instance of {@link Builder}.
|
||||||
|
* <br>
|
||||||
|
* @return Builder
|
||||||
|
* @param connectionConfigurationBuilder {@link ModularXmppClientToServerConnectionConfiguration.Builder}.
|
||||||
|
*/
|
||||||
|
public static Builder getBuilder(
|
||||||
|
ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
|
||||||
|
return new Builder(connectionConfigurationBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link XmppWebsocketTransportModuleDescriptor}.
|
||||||
|
* <br>
|
||||||
|
* To obtain an instance of {@link XmppWebsocketTransportModuleDescriptor.Builder}, use {@link XmppWebsocketTransportModuleDescriptor#getBuilder(ModularXmppClientToServerConnectionConfiguration.Builder)} method.
|
||||||
|
* <br>
|
||||||
|
* Use {@link Builder#explicitlySetWebsocketEndpoint(URI)} to configure the URI of an endpoint as a backup in case connection couldn't be established with endpoints through http lookup.
|
||||||
|
* <br>
|
||||||
|
* Use {@link Builder#explicitlySetWebsocketEndpointAndDiscovery(URI, boolean)} to configure endpoint and disallow websocket endpoint discovery through http lookup.
|
||||||
|
* By default, {@link Builder#performWebsocketEndpointDiscovery} is set to true.
|
||||||
|
* <br>
|
||||||
|
* Use {@link Builder#build()} to obtain {@link XmppWebsocketTransportModuleDescriptor}.
|
||||||
|
*/
|
||||||
|
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
|
||||||
|
private boolean performWebsocketEndpointDiscovery = true;
|
||||||
|
private URI uri;
|
||||||
|
|
||||||
|
private Builder(
|
||||||
|
ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
|
||||||
|
super(connectionConfigurationBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder explicitlySetWebsocketEndpoint(URI endpoint) {
|
||||||
|
return explicitlySetWebsocketEndpointAndDiscovery(endpoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder explicitlySetWebsocketEndpointAndDiscovery(URI endpoint, boolean performWebsocketEndpointDiscovery) {
|
||||||
|
Objects.requireNonNull(endpoint, "Provided endpoint URI must not be null");
|
||||||
|
this.uri = endpoint;
|
||||||
|
this.performWebsocketEndpointDiscovery = performWebsocketEndpointDiscovery;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder explicitlySetWebsocketEndpoint(CharSequence endpoint) throws URISyntaxException {
|
||||||
|
URI endpointUri = new URI(endpoint.toString());
|
||||||
|
return explicitlySetWebsocketEndpointAndDiscovery(endpointUri, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder explicitlySetWebsocketEndpoint(CharSequence endpoint, boolean performWebsocketEndpointDiscovery)
|
||||||
|
throws URISyntaxException {
|
||||||
|
URI endpointUri = new URI(endpoint.toString());
|
||||||
|
return explicitlySetWebsocketEndpointAndDiscovery(endpointUri, performWebsocketEndpointDiscovery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModularXmppClientToServerConnectionModuleDescriptor build() {
|
||||||
|
return new XmppWebsocketTransportModuleDescriptor(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2020 Aditya Borikar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.websocket.elements;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
|
public abstract class AbstractWebsocketNonza implements Nonza {
|
||||||
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
|
||||||
|
private static final String VERSION = "1.0";
|
||||||
|
private final DomainBareJid to;
|
||||||
|
|
||||||
|
public AbstractWebsocketNonza(DomainBareJid jid) {
|
||||||
|
this.to = jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
|
||||||
|
xml.attribute("to", to.toString());
|
||||||
|
xml.attribute("version", VERSION);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamClose;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public final class WebsocketCloseElement extends AbstractStreamClose {
|
||||||
|
public static final String ELEMENT = "close";
|
||||||
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
|
public WebsocketCloseElement() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
||||||
|
import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
|
public final class WebsocketOpenElement extends AbstractStreamOpen {
|
||||||
|
public static final String ELEMENT = "open";
|
||||||
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
|
public WebsocketOpenElement(DomainBareJid to) {
|
||||||
|
super(to, null, null, null, StreamContentNamespace.client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
addCommonAttributes(xml);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* This package contains Stanzas required to open and close stream.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.websocket.elements;
|
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.implementations;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
|
public abstract class AbstractWebsocket {
|
||||||
|
|
||||||
|
protected enum WebsocketConnectionPhase {
|
||||||
|
openFrameSent,
|
||||||
|
exchangingTopLevelStreamElements
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getStreamFromOpenElement(String openElement) {
|
||||||
|
String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
|
||||||
|
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
|
||||||
|
.replaceFirst("/>\\s*\\z", ">");
|
||||||
|
return streamElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean isOpenElement(String text) {
|
||||||
|
if (text.startsWith("<open ")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean isCloseElement(String text) {
|
||||||
|
if (text.startsWith("<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void connect(WebsocketRemoteConnectionEndpoint endpoint) throws Throwable;
|
||||||
|
|
||||||
|
public abstract void send(TopLevelStreamElement element);
|
||||||
|
|
||||||
|
public abstract void disconnect(int code, String message);
|
||||||
|
|
||||||
|
public abstract boolean isConnectionSecure();
|
||||||
|
|
||||||
|
public abstract SSLSession getSSLSession();
|
||||||
|
|
||||||
|
public abstract boolean isConnected();
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.implementations;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
|
||||||
|
|
||||||
|
public final class WebsocketImplProvider {
|
||||||
|
|
||||||
|
public static AbstractWebsocket getWebsocketImpl(Class<? extends AbstractWebsocket> websocketImpl, ModularXmppClientToServerConnectionInternal connectionInternal, DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||||
|
Objects.requireNonNull(connectionInternal, "ConnectionInternal cannot be null");
|
||||||
|
|
||||||
|
// Creates an instance of the constructor for the desired websocket implementation.
|
||||||
|
Constructor<? extends AbstractWebsocket> constructor = websocketImpl.getConstructor(ModularXmppClientToServerConnectionInternal.class, DiscoveredWebsocketEndpoints.class);
|
||||||
|
return (AbstractWebsocket) constructor.newInstance(connectionInternal, discoveredWebsocketEndpoints);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.implementations.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||||
|
|
||||||
|
import okhttp3.Headers;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
|
||||||
|
import org.jxmpp.xml.splitter.XmlPrettyPrinter;
|
||||||
|
import org.jxmpp.xml.splitter.XmppXmlSplitter;
|
||||||
|
|
||||||
|
public final class LoggingInterceptor {
|
||||||
|
private static final Logger LOGGER = Logger.getAnonymousLogger();
|
||||||
|
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) {
|
||||||
|
this.debugger = smackDebugger;
|
||||||
|
|
||||||
|
XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder()
|
||||||
|
.setPrettyWriter(sb -> debugger.incomingStreamSink(sb))
|
||||||
|
.setTabWidth(4)
|
||||||
|
.build();
|
||||||
|
XmppXmlSplitter incomingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
|
||||||
|
incomingTextPrinter);
|
||||||
|
incomingTextSplitter = new Utf8ByteXmppXmlSplitter(incomingXmlSplitter);
|
||||||
|
|
||||||
|
XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder()
|
||||||
|
.setPrettyWriter(sb -> debugger.outgoingStreamSink(sb))
|
||||||
|
.setTabWidth(4)
|
||||||
|
.build();
|
||||||
|
XmppXmlSplitter outgoingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
|
||||||
|
outgoingTextPrinter);
|
||||||
|
outgoingTextSplitter = new Utf8ByteXmppXmlSplitter(outgoingXmlSplitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open response received here isn't in the form of an Xml an so, there isn't much to format.
|
||||||
|
public void interceptOpenResponse(Response response) {
|
||||||
|
Headers headers = response.headers();
|
||||||
|
Iterator<?> iterator = headers.iterator();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Received headers:");
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
sb.append("\n\t" + iterator.next());
|
||||||
|
}
|
||||||
|
debugger.incomingStreamSink(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void interceptReceivedText(String text) {
|
||||||
|
try {
|
||||||
|
incomingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Connections shouldn't be terminated due to exceptions encountered during debugging. hence only log them.
|
||||||
|
LOGGER.log(Level.WARNING, "IOException encountered while parsing received text: " + text, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void interceptSentText(String text) {
|
||||||
|
try {
|
||||||
|
outgoingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Connections shouldn't be terminated due to exceptions encountered during debugging, hence only log them.
|
||||||
|
LOGGER.log(Level.WARNING, "IOException encountered while parsing outgoing text: " + text, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.implementations.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.jivesoftware.smack.websocket.WebsocketException;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
|
||||||
|
import org.jivesoftware.smack.websocket.elements.WebsocketOpenElement;
|
||||||
|
import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.WebSocket;
|
||||||
|
import okhttp3.WebSocketListener;
|
||||||
|
|
||||||
|
public final class OkHttpWebsocket extends AbstractWebsocket {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(OkHttpWebsocket.class.getName());
|
||||||
|
|
||||||
|
private static OkHttpClient okHttpClient = null;
|
||||||
|
|
||||||
|
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||||
|
private final LoggingInterceptor interceptor;
|
||||||
|
|
||||||
|
private String openStreamHeader;
|
||||||
|
private WebSocket currentWebsocket;
|
||||||
|
private WebsocketConnectionPhase phase;
|
||||||
|
private WebsocketRemoteConnectionEndpoint connectedEndpoint;
|
||||||
|
|
||||||
|
public OkHttpWebsocket(ModularXmppClientToServerConnectionInternal connectionInternal,
|
||||||
|
DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints) {
|
||||||
|
this.connectionInternal = connectionInternal;
|
||||||
|
|
||||||
|
if (okHttpClient == null) {
|
||||||
|
// Creates an instance of okHttp client.
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
|
okHttpClient = builder.build();
|
||||||
|
}
|
||||||
|
// Add some mechanism to enable and disable this interceptor.
|
||||||
|
if (connectionInternal.smackDebugger != null) {
|
||||||
|
interceptor = new LoggingInterceptor(connectionInternal.smackDebugger);
|
||||||
|
} else {
|
||||||
|
interceptor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(WebsocketRemoteConnectionEndpoint endpoint) throws InterruptedException, SmackException, XMPPException {
|
||||||
|
final String currentUri = endpoint.getWebsocketEndpoint().toString();
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(currentUri)
|
||||||
|
.header("Sec-WebSocket-Protocol", "xmpp")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
WebSocketListener listener = new WebSocketListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(WebSocket webSocket, Response response) {
|
||||||
|
LOGGER.log(Level.FINER, "Websocket is open");
|
||||||
|
phase = WebsocketConnectionPhase.openFrameSent;
|
||||||
|
if (interceptor != null) {
|
||||||
|
interceptor.interceptOpenResponse(response);
|
||||||
|
}
|
||||||
|
send(new WebsocketOpenElement(connectionInternal.connection.getXMPPServiceDomain()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(WebSocket webSocket, String text) {
|
||||||
|
if (interceptor != null) {
|
||||||
|
interceptor.interceptReceivedText(text);
|
||||||
|
}
|
||||||
|
if (isCloseElement(text)) {
|
||||||
|
connectionInternal.onStreamClosed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String closingStream = "</stream>";
|
||||||
|
switch (phase) {
|
||||||
|
case openFrameSent:
|
||||||
|
if (isOpenElement(text)) {
|
||||||
|
// Converts the <open> element received into <stream> element.
|
||||||
|
openStreamHeader = getStreamFromOpenElement(text);
|
||||||
|
phase = WebsocketConnectionPhase.exchangingTopLevelStreamElements;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connectionInternal.onStreamOpen(PacketParserUtils.getParserFor(openStreamHeader));
|
||||||
|
} catch (XmlPullParserException | IOException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Exception caught:", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.WARNING, "Unexpected Frame received", text);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case exchangingTopLevelStreamElements:
|
||||||
|
connectionInternal.parseAndProcessElement(openStreamHeader + text + closingStream);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.log(Level.INFO, "Default text: " + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||||
|
LOGGER.log(Level.INFO, "Exception caught", t);
|
||||||
|
WebsocketException websocketException = new WebsocketException(t);
|
||||||
|
if (connectionInternal.connection.isConnected()) {
|
||||||
|
connectionInternal.notifyConnectionError(websocketException);
|
||||||
|
} else {
|
||||||
|
connectionInternal.setCurrentConnectionExceptionAndNotify(websocketException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
public void send(TopLevelStreamElement element) {
|
||||||
|
String textToBeSent = element.toXML().toString();
|
||||||
|
if (interceptor != null) {
|
||||||
|
interceptor.interceptSentText(textToBeSent);
|
||||||
|
}
|
||||||
|
currentWebsocket.send(textToBeSent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect(int code, String message) {
|
||||||
|
currentWebsocket.close(code, message);
|
||||||
|
LOGGER.log(Level.INFO, "Websocket has been closed with message: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnectionSecure() {
|
||||||
|
return connectedEndpoint.isSecureEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connectedEndpoint == null ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getSSLSession() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.implementations.okhttp;
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* This package contains websocket implementations to be plugged inside websocket transport.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.websocket.implementations;
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Websocket related classes for Smack.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.websocket;
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.rce;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.datatypes.UInt16;
|
||||||
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||||
|
|
||||||
|
public final class WebsocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getAnonymousLogger();
|
||||||
|
|
||||||
|
private final URI uri;
|
||||||
|
|
||||||
|
public WebsocketRemoteConnectionEndpoint(String uri) throws URISyntaxException {
|
||||||
|
this(new URI(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebsocketRemoteConnectionEndpoint(URI uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
if (!(scheme.equals("ws") || scheme.equals("wss"))) {
|
||||||
|
throw new IllegalArgumentException("Only allowed protocols are ws and wss");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getWebsocketEndpoint() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecureEndpoint() {
|
||||||
|
if (uri.getScheme().equals("wss")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getHost() {
|
||||||
|
return uri.getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UInt16 getPort() {
|
||||||
|
return UInt16.from(uri.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends InetAddress> getInetAddresses() {
|
||||||
|
try {
|
||||||
|
InetAddress address = InetAddress.getByName(getHost().toString());
|
||||||
|
return Collections.singletonList(address);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Unknown Host Exception ", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.rce;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
|
import org.jivesoftware.smack.altconnections.HttpLookupMethod;
|
||||||
|
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
|
||||||
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
|
public final class WebsocketRemoteConnectionEndpointLookup {
|
||||||
|
|
||||||
|
public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) {
|
||||||
|
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
|
||||||
|
|
||||||
|
List<URI> rcUriList = null;
|
||||||
|
try {
|
||||||
|
// Look for remote connection endpoints by making use of http lookup method described inside XEP-0156.
|
||||||
|
rcUriList = HttpLookupMethod.lookup(domainBareJid,
|
||||||
|
LinkRelation.WEBSOCKET);
|
||||||
|
} catch (IOException | XmlPullParserException | URISyntaxException e) {
|
||||||
|
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
|
||||||
|
domainBareJid, e));
|
||||||
|
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rcUriList.isEmpty()) {
|
||||||
|
throw new IllegalStateException("No endpoints were found inside host-meta");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert rcUriList to List<WebsocketRemoteConnectionEndpoint>
|
||||||
|
Iterator<URI> iterator = rcUriList.iterator();
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> rceList = new ArrayList<>();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
rceList.add(new WebsocketRemoteConnectionEndpoint(iterator.next()));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (securityMode) {
|
||||||
|
case ifpossible:
|
||||||
|
// If security mode equals `if-possible`, give priority to secure endpoints over insecure endpoints.
|
||||||
|
|
||||||
|
// Seprate secure and unsecure endpoints.
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> secureEndpointsForSecurityModeIfPossible = new ArrayList<>();
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> insecureEndpointsForSecurityModeIfPossible = new ArrayList<>();
|
||||||
|
for (WebsocketRemoteConnectionEndpoint uri : rceList) {
|
||||||
|
if (uri.isSecureEndpoint()) {
|
||||||
|
secureEndpointsForSecurityModeIfPossible.add(uri);
|
||||||
|
} else {
|
||||||
|
insecureEndpointsForSecurityModeIfPossible.add(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
discoveredRemoteConnectionEndpoints = secureEndpointsForSecurityModeIfPossible;
|
||||||
|
discoveredRemoteConnectionEndpoints.addAll(insecureEndpointsForSecurityModeIfPossible);
|
||||||
|
break;
|
||||||
|
case required:
|
||||||
|
case disabled:
|
||||||
|
/**
|
||||||
|
* If, SecurityMode equals to required, accept wss endpoints (secure endpoints) only or,
|
||||||
|
* if SecurityMode equals to disabled, accept ws endpoints (unsecure endpoints) only.
|
||||||
|
*/
|
||||||
|
for (WebsocketRemoteConnectionEndpoint uri : rceList) {
|
||||||
|
if ((securityMode.equals(SecurityMode.disabled) && !uri.isSecureEndpoint())
|
||||||
|
|| (securityMode.equals(SecurityMode.required) && uri.isSecureEndpoint())) {
|
||||||
|
discoveredRemoteConnectionEndpoints.add(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Result {
|
||||||
|
public final List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints;
|
||||||
|
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||||
|
|
||||||
|
public Result(List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints,
|
||||||
|
List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
|
||||||
|
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
|
||||||
|
this.lookupFailures = lookupFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WebsocketRemoteConnectionEndpoint> getDiscoveredRemoteConnectionEndpoints() {
|
||||||
|
return discoveredRemoteConnectionEndpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
|
||||||
|
return lookupFailures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* This package contains websocket endpoint classes needed by the websocket transport.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.websocket.rce;
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class WebsocketConnectionAttemptStateTest {
|
||||||
|
@Test
|
||||||
|
public void constructorTest() {
|
||||||
|
assertThrows(AssertionError.class, () -> new WebsocketConnectionAttemptState(null, null, null));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class WebsocketInitializerTest {
|
||||||
|
@Test
|
||||||
|
public void testExtensionInitializer() {
|
||||||
|
WebsocketInitializer initializer = new WebsocketInitializer();
|
||||||
|
List<Exception> exceptions = initializer.initialize();
|
||||||
|
assertTrue(exceptions.size() == 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||||
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||||
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure.HttpLookupFailure;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.WebsocketEndpointsDiscoveryFailed;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
public class XmppWebsocketTransportModuleTest {
|
||||||
|
@Test
|
||||||
|
public void createWebsocketModuleConnectionInstanceTest() throws URISyntaxException, XmppStringprepException {
|
||||||
|
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration
|
||||||
|
.builder();
|
||||||
|
|
||||||
|
builder.removeAllModules();
|
||||||
|
builder.addModule(XmppWebsocketTransportModuleDescriptor.class);
|
||||||
|
builder.setXmppAddressAndPassword("user5@localhost.org", "user5");
|
||||||
|
builder.setHost("localhost.org");
|
||||||
|
|
||||||
|
XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder);
|
||||||
|
websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI("wss://localhost.org:7443/ws/"), false);
|
||||||
|
|
||||||
|
ModularXmppClientToServerConnectionConfiguration config = builder.build();
|
||||||
|
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
|
||||||
|
assertNotNull(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDescriptorTest() throws URISyntaxException, XmppStringprepException {
|
||||||
|
XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor();
|
||||||
|
assertNotNull(websocketTransportModuleDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void websocketEndpointDiscoveryTest() throws URISyntaxException {
|
||||||
|
XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor();
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
|
||||||
|
|
||||||
|
XmppWebsocketTransportModule transportModule
|
||||||
|
= new XmppWebsocketTransportModule(websocketTransportModuleDescriptor, connectionInternal);
|
||||||
|
|
||||||
|
XmppWebsocketTransportModule.XmppWebsocketTransport transport = transportModule.getTransport();
|
||||||
|
|
||||||
|
assertThrows(AssertionError.class, () -> transport.new DiscoveredWebsocketEndpoints(null));
|
||||||
|
assertThrows(AssertionError.class, () -> transport.new WebsocketEndpointsDiscoveryFailed(null));
|
||||||
|
|
||||||
|
WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
|
||||||
|
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
|
||||||
|
discoveredRemoteConnectionEndpoints.add(endpoint);
|
||||||
|
|
||||||
|
HttpLookupFailure httpLookupFailure = new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(null, null);
|
||||||
|
List<RemoteConnectionEndpointLookupFailure> failureList = new ArrayList<>();
|
||||||
|
failureList.add(httpLookupFailure);
|
||||||
|
Result result = new Result(discoveredRemoteConnectionEndpoints, failureList);
|
||||||
|
|
||||||
|
DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints = transport.new DiscoveredWebsocketEndpoints(result);
|
||||||
|
assertNotNull(discoveredWebsocketEndpoints.getResult());
|
||||||
|
|
||||||
|
WebsocketEndpointsDiscoveryFailed endpointsDiscoveryFailed = transport.new WebsocketEndpointsDiscoveryFailed(result);
|
||||||
|
assertNotNull(endpointsDiscoveryFailed.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void websocketConnectedResultTest() throws URISyntaxException {
|
||||||
|
WebsocketRemoteConnectionEndpoint connectedEndpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
|
||||||
|
assertNotNull(new XmppWebsocketTransportModule.WebsocketConnectedResult(connectedEndpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void lookupConnectionEndpointsTest() throws URISyntaxException {
|
||||||
|
XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor();
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
|
||||||
|
|
||||||
|
XmppWebsocketTransportModule transportModule
|
||||||
|
= new XmppWebsocketTransportModule(websocketTransportModuleDescriptor, connectionInternal);
|
||||||
|
|
||||||
|
XmppWebsocketTransportModule.XmppWebsocketTransport transport = transportModule.getTransport();
|
||||||
|
assertNotNull(transport.lookupConnectionEndpoints());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmppWebsocketTransportModuleDescriptor getWebsocketDescriptor() throws URISyntaxException {
|
||||||
|
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration
|
||||||
|
.builder();
|
||||||
|
|
||||||
|
XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder);
|
||||||
|
websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI("wss://localhost.org:7443/ws/"), false);
|
||||||
|
return (XmppWebsocketTransportModuleDescriptor) websocketBuilder.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlNotSimilar;
|
||||||
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
public class WebsocketElementTest {
|
||||||
|
private static final String OPEN_ELEMENT = "<open xmlns='urn:ietf:params:xml:ns:xmpp-framing' to='foodomain.foo' version='1.0'/>";
|
||||||
|
private static final String CLOSE_ELEMENT = "<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void websocketOpenElementTest() throws XmppStringprepException {
|
||||||
|
String openElementXml = new WebsocketOpenElement(JidCreate.domainBareFrom("foodomain.foo")).toXML().toString();
|
||||||
|
assertXmlSimilar(OPEN_ELEMENT, openElementXml);
|
||||||
|
assertXmlNotSimilar(CLOSE_ELEMENT, new WebsocketOpenElement(JidCreate.domainBareFrom("foodomain.foo")).toXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void websocketCloseElementTest() throws XmppStringprepException {
|
||||||
|
String closeElementXml = new WebsocketCloseElement().toXML().toString();
|
||||||
|
assertXmlSimilar(CLOSE_ELEMENT, closeElementXml);
|
||||||
|
assertXmlNotSimilar(OPEN_ELEMENT, closeElementXml);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2020 Aditya Borikar.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.websocket.implementations;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public final class AbstractWebsocketTest {
|
||||||
|
private static final String OPEN_ELEMENT = "<open from='localhost.org' id='aov9ihhmmn' xmlns='urn:ietf:params:xml:ns:xmpp-framing' xml:lang='en' version='1.0'/>";
|
||||||
|
private static final String OPEN_STREAM = "<stream from='localhost.org' id='aov9ihhmmn' xmlns='jabber:client' xml:lang='en' version='1.0'>";
|
||||||
|
private static final String CLOSE_ELEMENT = "<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStreamFromOpenElementTest() {
|
||||||
|
String generatedOpenStream = AbstractWebsocket.getStreamFromOpenElement(OPEN_ELEMENT);
|
||||||
|
assertEquals(generatedOpenStream, OPEN_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isOpenElementTest() {
|
||||||
|
assertTrue(AbstractWebsocket.isOpenElement(OPEN_ELEMENT));
|
||||||
|
assertFalse(AbstractWebsocket.isOpenElement(OPEN_STREAM));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isCloseElementTest() {
|
||||||
|
assertTrue(AbstractWebsocket.isCloseElement(CLOSE_ELEMENT));
|
||||||
|
assertFalse(AbstractWebsocket.isCloseElement(OPEN_STREAM));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.implementations;
|
||||||
|
|
||||||
|
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.when;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.websocket.implementations.okhttp.OkHttpWebsocket;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class ProviderTest {
|
||||||
|
@Test
|
||||||
|
public void providerTest() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getImplTest() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, URISyntaxException {
|
||||||
|
WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
|
||||||
|
|
||||||
|
List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
|
||||||
|
discoveredRemoteConnectionEndpoints.add(endpoint);
|
||||||
|
|
||||||
|
Result result = new Result(discoveredRemoteConnectionEndpoints, null);
|
||||||
|
|
||||||
|
DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints = mock(DiscoveredWebsocketEndpoints.class);
|
||||||
|
when(discoveredWebsocketEndpoints.getResult()).thenReturn(result);
|
||||||
|
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
|
||||||
|
|
||||||
|
assertNotNull(WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, connectionInternal, discoveredWebsocketEndpoints));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.rce;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.datatypes.UInt16;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class WebsocketRemoteConnectionEndpointTest {
|
||||||
|
@Test
|
||||||
|
public void endpointTest() throws URISyntaxException {
|
||||||
|
String endpointString = "ws://fooDomain.org:7070/ws/";
|
||||||
|
WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint(endpointString);
|
||||||
|
assertEquals("fooDomain.org", endpoint.getHost());
|
||||||
|
assertEquals(UInt16.from(7070), endpoint.getPort());
|
||||||
|
assertEquals(endpointString, endpoint.getWebsocketEndpoint().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void faultyEndpointTest() {
|
||||||
|
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
new WebsocketRemoteConnectionEndpoint(faultyProtocolString);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
mock-maker-inline
|
2
version
2
version
|
@ -1 +1 @@
|
||||||
4.4.0-alpha6-SNAPSHOT
|
4.5.0-alpha1-SNAPSHOT
|
||||||
|
|
Loading…
Reference in a new issue