1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-27 06:22:07 +01:00

Compare commits

..

47 commits

Author SHA1 Message Date
Florian Schmaus
525f27abf1 Merge branch '4.4' 2020-08-29 16:39:15 +02:00
Florian Schmaus
64d3e804a7
Merge pull request #427 from Flowdalic/elements
Add XmppElementUtil.castOrThrow()
2020-08-29 16:38:54 +02:00
Florian Schmaus
e78ef2b668
Merge pull request #399 from adiaholic/websocket
Websocket support through RFC 7395
2020-08-29 16:34:32 +02:00
Florian Schmaus
1ebe8b0309
Merge pull request #422 from Flowdalic/drop-sm-state-on-clean-shutdown
[tcp] Drop Stream Management state on clean shutdown
2020-08-29 16:33:50 +02:00
Florian Schmaus
b09cd06053 [core] Use XmppElementUtil.castOrThrow() in StanzaView.getExtension(Class)
This means that users get now exceptions with helpful error messages
instead of the dreaded ClassCastException, like

java.lang.ClassCastException: org.jivesoftware.smack.packet.StandardExtensionElement cannot be cast to org.jivesoftware.smackx.mam.element.MamElements$MamResultExtension
at

when StanzaView.getExtension(Class) is used to retrieve the extension.
2020-08-29 16:29:22 +02:00
Florian Schmaus
d06e9499e8 [core] Add XmppElementUtil.castOrThrow(ExtensionElement, Class)
This method throws an IllegalStateException if the provided extension
element is not of the expected type and hints users towards potential
causes.
2020-08-29 16:29:22 +02:00
adiaholic
7ed29b9d5f Introduce websocket module into smack 2020-08-28 23:40:40 +05:30
Florian Schmaus
99297e5a76 [mam] Improve MamResultExtension: use MessageView in from() and add QNAME 2020-08-28 09:47:54 +02:00
Florian Schmaus
1aab0b8aac [core] Add cache to XmppElementUtil.getQNameFor(Class) 2020-08-28 09:47:09 +02:00
Florian Schmaus
cf4c9725b7 [core] Add ProviderManager.getExtensionProvider(QName) 2020-08-28 09:37:29 +02:00
Florian Schmaus
5d32735ad7
Merge pull request #426 from Flowdalic/getstreamopen
Get stream-open-like element from transport
2020-08-26 12:52:01 +02:00
Florian Schmaus
f892ba1369 [core] Get stream-open-like element from transport
When sending a stream-open-like element, it depends on the actual used
transport which element is send. For example, RFC6120-style TCP uses
<stream>, whereas the Websocket binding for XMPP uses <open/>.
2020-08-26 11:38:24 +02:00
Florian Schmaus
e6a60213b6 [core] Add convenience constructor to AbstractStreamOpen
Most of the times when we construct a stream-open-like element, we
want the jabber:client namespace. Hence add a constructor that does
select the namespace implicitly.
2020-08-26 11:36:55 +02:00
Florian Schmaus
2a9671ca93 [core] Change type of XmlStringBuilder cosntructor to FullyQualifiedElement
There is no reason we should do this only for ExtensionElements, this
behavior is sane for every FulllyQualifiedElement.
2020-08-26 11:35:22 +02:00
Florian Schmaus
d00656493a [jingle] Use correct XmlStringBuilder constructor in JingleReason 2020-08-26 11:34:57 +02:00
Florian Schmaus
7f10a82fd9
Merge pull request #425 from adiaholic/docFix
XMPPTCPConnection: Add missing `to` in comment
2020-08-25 14:52:54 +02:00
Aditya Borikar
0bb0884512 XMPPTCPConnection: Add missing to in comment 2020-08-23 13:25:18 +05:30
Florian Schmaus
53d66261af
Merge pull request #423 from adiaholic/streamFactory
Use AbstractStreamOpen instead of StreamOpen to open stream
2020-08-19 13:40:21 +02:00
Florian Schmaus
61799c5951
Merge pull request #424 from adiaholic/plugModule
Make ModularXmppClientToServerConnectionConfiguration.addModule() public
2020-08-19 13:39:39 +02:00
Aditya Borikar
648a1cfab1 Use AbstractStreamOpen instead of StreamOpen to open stream
Before the existence of AbstractStreamOpen, StreamOpen sufficed our need
during sending an open stream element. Since the intention behind
introducing AbstractStreamOpen is to allow underlying transports provide
transport specific opening streams, these changes will further support
the cause.

This commit will allow us to send transport specific open element
which should be inherited from AbstractStreamOpen.
2020-08-19 11:48:00 +05:30
Aditya Borikar
db385e6595 Make ModularXmppClientToServerConnectionConfiguration.addModule() public
This commit will allow users to plug their module descriptors inside
modular architecture.
2020-08-19 11:32:50 +05:30
Florian Schmaus
9cec02b5e3
Merge pull request #420 from adiaholic/streamFactory
Introduce StreamOpenFactory for modular architecture
2020-08-18 16:22:27 +02:00
Aditya Borikar
0e49adff1d Introduce StreamOpenAndCloseFactory for modular architecture 2020-08-18 19:04:34 +05:30
Aditya Borikar
9fcc97836b Introduce AbstractStreamOpen and AbstractStreamClose
- Inherit StreamOpen and StreamClose from AbstractStream classes
2020-08-18 10:35:22 +05:30
Florian Schmaus
49ebe8c587 [tcp] Drop Stream Management state on clean shutdown
We previously only set the SM session ID to zero, but that is not
enough. On a clean shutdown, i.e. where we send a </stream> close tag,
we also have to nullify the unacknowledgedStanzas queue.
2020-08-17 22:11:50 +02:00
Florian Schmaus
9fe1fc6689 Merge branch '4.4' 2020-08-17 17:25:26 +02:00
Florian Schmaus
3f3590b42b
Merge pull request #421 from Flowdalic/smack-streammanagement
Add smack-streammanagement project
2020-08-17 15:55:37 +02:00
Florian Schmaus
317e391da5 Create smack-streammanagement project and move o.j.smack.sm code there 2020-08-15 14:03:57 +02:00
Florian Schmaus
45e865757a [gradle] Use 'projectDirFile' File instance in getGitCommit()
The previous construct

def projectDir = dotGit.getParentFile()

was a little bit strange (and its declaration shadows the projectDir
String property).
2020-08-15 10:08:43 +02:00
Florian Schmaus
f00804ef72
Merge pull request #419 from adiaholic/bugFix
XmlEnvironment: Use correct method to obatain effective namespace.
2020-08-11 14:19:03 +02:00
Aditya Borikar
c9cf4f1541 XmlEnvironment: Use correct method to obatain effective namespace. 2020-08-08 20:14:39 +05:30
Florian Schmaus
11e38f9ba5 Smack 4.5.0-alpha1-SNAPSHOT 2020-08-07 22:07:00 +02:00
Florian Schmaus
89c5895ab3 Smack 4.4.0-beta2-SNAPSHOT 2020-08-07 22:06:32 +02:00
Florian Schmaus
b61426c8d0 Smack 4.4.0-beta1 2020-08-07 21:25:20 +02:00
Florian Schmaus
cf92566e26
Merge pull request #416 from Flowdalic/connected-boolean
Set 'connected' to 'true' as early as possible
2020-08-07 12:43:53 +02:00
Florian Schmaus
36d61d9e52
Merge pull request #413 from adiaholic/xep-0156
HttpLookupMethod: Position parser at START_ELEMENT before parsing
2020-08-07 12:37:04 +02:00
Aditya Borikar
7796b367cc Position parser at START_ELEMENT before parsing
This PR aims to provide parseXrdLinkReferencesFor() method the ability
to parse forward to the first START_ELEMENT tag.The HttpLookupMethodTest
tests the HttpLookupMethod class by parsing String. This makes use of
PacketParserUtils.getParserFor(String), which already does forward
winding to reach START_ELEMENT. However when fetching endpoints from a
remote host meta data, PacketParserUtils.getParserFor(InputStream) is
used which doesn't do winding in any form. And thus, even though
HttpLookupMethodTest tests pass, this implementation would crash while
parsing remote host-meta.
2020-08-07 15:01:53 +05:30
Florian Schmaus
e35175a3d5
Merge pull request #417 from Flowdalic/gradle-dynamic-versions
[build] Use ranged version [1.0.0, 1.0.999] for jXMPP and MiniDNS
2020-08-06 19:58:35 +02:00
Florian Schmaus
92ed777dba
Merge pull request #418 from Flowdalic/rename-waitForCondition
waitForCondition() → waitForConditionOrThrowConnectionException()
2020-08-06 19:58:22 +02:00
Florian Schmaus
ac788592a6 waitForCondition() → waitForConditionOrThrowConnectionException()
The method was already renamed, but not in
ModularXmppClientToServerConnectionInternal.
2020-08-06 18:17:04 +02:00
Florian Schmaus
1a2a613112 Set 'connected' to 'true' as early as possible
We previously only set 'connected' after connectInternal()
returned. This could lead to notifyConnectionError() ignoring stream
error exceptions, e.g. when establishing TLS which happens also in
connectInternal(), because 'connected' was still 'false'.

2020-08-06 13:08:06.265 19830-20423/org.atalk.android D/SMACK: SENT (0):
    <stream:stream xmlns='jabber:client' to='atalk.sytes.net' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xml:lang='en'>
2020-08-06 13:08:06.333 19830-20424/org.atalk.android D/SMACK: RECV (0): ?xml version='1.0'?>
    <stream:stream id='16420577292739412012' version='1.0' xml:lang='en' xmlns:stream='http://etherx.jabber.org/streams' from='atalk.sytes.net' xmlns='jabber:client'>
    <stream:error>
      <policy-violation xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>
      <text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'>
        Too many (20) failed authentications from this IP address (::ffff:42.60.7.13). The address will be unblocked at 05:15:34 06.08.2020 UTC
      </text>
    </stream:error>
    </stream:stream>
2020-08-06 13:08:06.346 19830-20424/org.atalk.android I/aTalk: [241896] org.jivesoftware.smack.AbstractXMPPConnection.notifyConnectionError() Connection was already disconnected when attempting to handle org.jivesoftware.smack.XMPPException$StreamErrorException: policy-violation You can read more about the meaning of this stream error at http://xmpp.org/rfcs/rfc6120.html#streams-error-conditions
    <stream:error><policy-violation xmlns='urn:ietf:params:xml:ns:xmpp-streams'/><text xml:lang='en'>Too many (20) failed authentications from this IP address (::ffff:42.60.7.13). The address will be unblocked at 05:15:34 06.08.2020 UTC</text></stream:error>
    org.jivesoftware.smack.XMPPException$StreamErrorException: policy-violation You can read more about the meaning of this stream error at http://xmpp.org/rfcs/rfc6120.html#streams-error-conditions
    <stream:error><policy-violation xmlns='urn:ietf:params:xml:ns:xmpp-streams'/><text xml:lang='en'>Too many (20) failed authentications from this IP address (::ffff:42.60.7.13). The address will be unblocked at 05:15:34 06.08.2020 UTC</text></stream:error>
        at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.parsePackets(XMPPTCPConnection.java:966)
        at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.access$700(XMPPTCPConnection.java:898)
        at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader$1.run(XMPPTCPConnection.java:921)
        at java.lang.Thread.run(Thread.java:919)

Which eventually leads to a NoResponseException

org.jivesoftware.smack.SmackException$NoResponseException: No response
received within reply timeout. Timeout was 30000ms (~30s). While
waiting for establishing TLS
[XMPPTCPConnection[not-authenticated] (4)]

We now set 'connected' to 'true' as soon as the transport (e.g. TCP,
BOSH, …) is connected. While this is in other ways also sensible, it
also allows notifyConnectionError() to handle exceptions in the early
connection stage.

Thanks to Eng Chong Meng for reporting this.
2020-08-06 16:32:26 +02:00
Florian Schmaus
3c54d5ffcd [build] Use ranged version [1.0.0, 1.0.999] for jXMPP and MiniDNS
This also means that we need to lookup the resovled versions of those
artifacts in order to provide the javadoc link.
2020-08-06 15:19:42 +02:00
Florian Schmaus
a356e91108
Merge pull request #415 from vanitasvitae/NEDEtypo
NoEndpointDiscoveredException: s/Not/No
2020-07-30 08:48:57 +02:00
dd631048a3
NoEndpointDiscoveredException: s/Not/No 2020-07-28 19:00:04 +02:00
Florian Schmaus
d34eda61fe
Merge pull request #414 from Flowdalic/state-string-precedence
StateDescriptor String-based precedence API
2020-07-27 17:13:49 +02:00
Florian Schmaus
ec80d5287b [core] Add String-based StateDescriptor precedence/inferiority declaration methods 2020-07-25 14:27:45 +02:00
Florian Schmaus
64fb47c98b Fix typo in StateDescriptor method: s/Inferiorty/Inferiority/ 2020-07-25 13:55:35 +02:00
83 changed files with 2363 additions and 114 deletions

View file

@ -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()
}

View file

@ -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'

View file

@ -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();

View file

@ -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);
} }

View file

@ -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();

View file

@ -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();

View file

@ -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");

View file

@ -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();
}

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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) {

View file

@ -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 {
}

View file

@ -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);
}
}

View file

@ -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);
} }
/** /**

View file

@ -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();

View file

@ -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

View file

@ -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;

View file

@ -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);
} }
/** /**

View file

@ -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;
} }

View file

@ -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();

View file

@ -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);
} }

View file

@ -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);
}
} }

View file

@ -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;
}
}
} }

View file

@ -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>

View file

@ -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);
} }
} }

View file

@ -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);

View file

@ -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);
} }

View file

@ -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'

View file

@ -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 ]
} }

View file

@ -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();
}
}

View file

@ -0,0 +1,8 @@
description = """\
Smack support for XMPP Stream Management (XEP-0198)."""
dependencies {
api project(':smack-core')
testFixturesApi(testFixtures(project(":smack-core")))
}

View file

@ -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

View file

@ -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")))
} }

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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;

View 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")
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}
}

View file

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

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,47 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.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));
}
}

View file

@ -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));
}
}

View file

@ -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);
});
}
}

View file

@ -0,0 +1 @@
mock-maker-inline

View file

@ -1 +1 @@
4.4.0-alpha6-SNAPSHOT 4.5.0-alpha1-SNAPSHOT