001/**
002 *
003 * Copyright 2020 Aditya Borikar, Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.websocket.rce;
018
019import java.io.IOException;
020import java.net.URI;
021import java.net.URISyntaxException;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025
026import org.jivesoftware.smack.altconnections.HttpLookupMethod;
027import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
028import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
029import org.jivesoftware.smack.xml.XmlPullParserException;
030
031import org.jxmpp.jid.DomainBareJid;
032
033public final class WebSocketRemoteConnectionEndpointLookup {
034
035    public static Result lookup(DomainBareJid domainBareJid) {
036        List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
037
038        List<URI> rcUriList = null;
039        try {
040            // Look for remote connection endpoints by making use of http lookup method described inside XEP-0156.
041            rcUriList = HttpLookupMethod.lookup(domainBareJid,
042                            LinkRelation.WEBSOCKET);
043        } catch (IOException | XmlPullParserException | URISyntaxException e) {
044            lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
045                            domainBareJid, e));
046            return new Result(lookupFailures);
047        }
048
049        List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints = new ArrayList<>(rcUriList.size());
050        List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints = new ArrayList<>(rcUriList.size());
051
052        for (URI webSocketUri : rcUriList) {
053            WebSocketRemoteConnectionEndpoint wsRce = WebSocketRemoteConnectionEndpoint.from(webSocketUri);
054            if (wsRce instanceof SecureWebSocketRemoteConnectionEndpoint) {
055                SecureWebSocketRemoteConnectionEndpoint secureWsRce = (SecureWebSocketRemoteConnectionEndpoint) wsRce;
056                discoveredSecureEndpoints.add(secureWsRce);
057            } else if (wsRce instanceof InsecureWebSocketRemoteConnectionEndpoint) {
058                InsecureWebSocketRemoteConnectionEndpoint insecureWsRce = (InsecureWebSocketRemoteConnectionEndpoint) wsRce;
059                discoveredInsecureEndpoints.add(insecureWsRce);
060            } else {
061                // WebSocketRemoteConnectionEndpoint.from() must return an instance which type is one of the above.
062                throw new AssertionError();
063            }
064        }
065
066        return new Result(discoveredSecureEndpoints, discoveredInsecureEndpoints, lookupFailures);
067    }
068
069    public static final class Result {
070        public final List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints;
071        public final List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints;
072        public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
073
074        public Result() {
075            this(Collections.emptyList());
076        }
077
078        public Result(List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
079            // The list of endpoints needs to be mutable, because maybe a user supplied endpoint will be added to it.
080            // Hence, we do not use Collections.emptyList() as argument for the discovered endpoints.
081            this(new ArrayList<>(1), new ArrayList<>(1), lookupFailures);
082        }
083
084        public Result(List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints,
085                        List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints,
086                        List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
087            this.discoveredSecureEndpoints = discoveredSecureEndpoints;
088            this.discoveredInsecureEndpoints = discoveredInsecureEndpoints;
089            this.lookupFailures = lookupFailures;
090        }
091
092        public boolean isEmpty() {
093            return discoveredSecureEndpoints.isEmpty() && discoveredInsecureEndpoints.isEmpty();
094        }
095
096        public int discoveredEndpointCount() {
097            return discoveredSecureEndpoints.size() + discoveredInsecureEndpoints.size();
098        }
099
100        // TODO: Remove the following methods since the fields are already public? Or make the fields private and use
101        // the methods? I tend to remove the methods, as their method name is pretty long. But OTOH the fields reference
102        // mutable datastructures, which is uncommon to be public.
103        public List<SecureWebSocketRemoteConnectionEndpoint> getDiscoveredSecureRemoteConnectionEndpoints() {
104            return discoveredSecureEndpoints;
105        }
106
107        public List<InsecureWebSocketRemoteConnectionEndpoint> getDiscoveredInsecureRemoteConnectionEndpoints() {
108            return discoveredInsecureEndpoints;
109        }
110
111        public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
112            return lookupFailures;
113        }
114    }
115}