001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.net;
040
041import java.io.Closeable;
042import java.io.IOException;
043import java.io.Serializable;
044import java.net.*;
045import java.security.GeneralSecurityException;
046import java.util.*;
047
048import javax.net.ssl.SSLContext;
049import javax.net.ssl.SSLSocket;
050import javax.net.ssl.SSLSocketFactory;
051
052import org.dcm4che3.conf.core.api.ConfigurableClass;
053import org.dcm4che3.conf.core.api.ConfigurableProperty;
054import org.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType;
055import org.dcm4che3.conf.core.api.ConfigurableProperty.Tag;
056import org.dcm4che3.conf.core.api.LDAP;
057import org.dcm4che3.net.proxy.ProxyManager;
058import org.dcm4che3.net.proxy.ProxyService;
059import org.dcm4che3.util.SafeClose;
060import org.dcm4che3.util.StringUtils;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064
065/**
066 * A DICOM Part 15, Annex H compliant class, <code>NetworkConnection</code>
067 * encapsulates the properties associated with a connection to a TCP/IP network.
068 * <p>
069 * The <i>network connection</i> describes one TCP port on one network device.
070 * This can be used for a TCP connection over which a DICOM association can be
071 * negotiated with one or more Network AEs. It specifies 8 the hostname and TCP
072 * port number. A network connection may support multiple Network AEs. The
073 * Network AE selection takes place during association negotiation based on the
074 * called and calling AE-titles.
075 *
076 * @author Gunter Zeilinger <gunterze@gmail.com>
077 */
078@LDAP(objectClasses = {"dicomNetworkConnection", "dcmNetworkConnection"})
079@ConfigurableClass(referable = true)
080public class Connection implements Serializable {
081
082    private static final long serialVersionUID = -7814748788035232055L;
083
084    public String getUuid() {
085        return uuid;
086    }
087
088    public void setUuid(String uuid) {
089        this.uuid = uuid;
090    }
091
092    public enum Protocol {
093        DICOM, HL7, SYSLOG_TLS, SYSLOG_UDP;
094
095        public boolean isTCP() {
096            return this != SYSLOG_UDP;
097        }
098
099        public boolean isSyslog() {
100            return this == SYSLOG_TLS || this == SYSLOG_UDP;
101        }
102    }
103
104    public static final Logger LOG = LoggerFactory.getLogger(Connection.class);
105
106    public static final String NO_TIMEOUT_STR = "0";
107    public static final int NO_TIMEOUT = Integer.valueOf(NO_TIMEOUT_STR);
108
109    public static final int SYNCHRONOUS_MODE = 1;
110    public static final int NOT_LISTENING = -1;
111    public static final int DEF_BACKLOG = 50;
112    public static final int DEF_SOCKETDELAY = 50;
113
114    public static final String DEF_BUFFERSIZE_STR = "0";
115    public static final int DEF_BUFFERSIZE = Integer.valueOf(DEF_BUFFERSIZE_STR);
116
117
118    public static final int DEF_MAX_PDU_LENGTH = 16378;
119    // to fit into SunJSSE TLS Application Data Length 16408
120
121    public static final String TLS_RSA_WITH_NULL_SHA = "SSL_RSA_WITH_NULL_SHA";
122    public static final String TLS_RSA_WITH_3DES_EDE_CBC_SHA = "SSL_RSA_WITH_3DES_EDE_CBC_SHA";
123    public static final String TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA";
124    private static final String[] DEFAULT_TLS_PROTOCOLS =  { "TLSv1.2", "TLSv1.1", "TLSv1" };
125
126    private Device device;
127
128    @ConfigurableProperty(name = "cn", label = "Name", tags = Tag.PRIMARY)
129    private String commonName;
130
131    @ConfigurableProperty(name = "dicomHostname", label = "Hostname", tags = Tag.PRIMARY)
132    private String hostname;
133
134    @ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash)
135    private String olockHash;
136
137    @ConfigurableProperty(type = ConfigurablePropertyType.UUID)
138    private String uuid = UUID.randomUUID().toString();
139
140    @ConfigurableProperty(name = "dcmBindAddress")
141    private String bindAddress;
142
143    @ConfigurableProperty(name = "dcmClientBindAddress")
144    private String clientBindAddress;
145
146    @ConfigurableProperty(name = "dcmHTTPProxy")
147    private String httpProxy;
148
149    @ConfigurableProperty(
150            name = "dicomPort",
151            defaultValue = "-1",
152            label = "Port",
153            tags = Tag.PRIMARY)
154    private int port = NOT_LISTENING;
155
156    @ConfigurableProperty(name = "dcmTCPBacklog", defaultValue = "50")
157    private int backlog = DEF_BACKLOG;
158
159    @ConfigurableProperty(name = "dcmTCPConnectTimeout", defaultValue = NO_TIMEOUT_STR)
160    private int connectTimeout;
161
162    @ConfigurableProperty(name = "dcmAARQTimeout", defaultValue = NO_TIMEOUT_STR)
163    private int requestTimeout;
164
165    @ConfigurableProperty(name = "dcmAAACTimeout", defaultValue = NO_TIMEOUT_STR)
166    private int acceptTimeout;
167
168    @ConfigurableProperty(name = "dcmARRPTimeout", defaultValue = NO_TIMEOUT_STR)
169    private int releaseTimeout;
170
171    @ConfigurableProperty(name = "dcmResponseTimeout", defaultValue = NO_TIMEOUT_STR)
172    private int responseTimeout;
173
174    @ConfigurableProperty(name = "dcmRetrieveTimeout", defaultValue = NO_TIMEOUT_STR)
175    private int retrieveTimeout;
176
177    @ConfigurableProperty(name = "dcmIdleTimeout", defaultValue = NO_TIMEOUT_STR)
178    private int idleTimeout;
179
180    @ConfigurableProperty(name = "dcmSocketTimeout", defaultValue = NO_TIMEOUT_STR)
181    private int socketTimeout;
182
183    @ConfigurableProperty(name = "dcmTCPCloseDelay", defaultValue = "50")
184    private int socketCloseDelay = DEF_SOCKETDELAY;
185
186    @ConfigurableProperty(name = "dcmTCPSendBufferSize", defaultValue = DEF_BUFFERSIZE_STR)
187    private int sendBufferSize;
188
189    @ConfigurableProperty(name = "dcmTCPReceiveBufferSize", defaultValue = DEF_BUFFERSIZE_STR)
190    private int receiveBufferSize;
191
192    @ConfigurableProperty(name = "dcmSendPDULength", defaultValue = "16378")
193    private int sendPDULength = DEF_MAX_PDU_LENGTH;
194
195    @ConfigurableProperty(name = "dcmReceivePDULength", defaultValue = "16378")
196    private int receivePDULength = DEF_MAX_PDU_LENGTH;
197
198    @ConfigurableProperty(name = "dcmMaxOpsPerformed", defaultValue = "1")
199    private int maxOpsPerformed = SYNCHRONOUS_MODE;
200
201    @ConfigurableProperty(name = "dcmMaxOpsInvoked", defaultValue = "1")
202    private int maxOpsInvoked = SYNCHRONOUS_MODE;
203
204    @ConfigurableProperty(name = "dcmPackPDV", defaultValue = "true")
205    private boolean packPDV = true;
206
207    @ConfigurableProperty(name = "dcmTCPNoDelay", defaultValue = "true")
208    private boolean tcpNoDelay = true;
209
210    @ConfigurableProperty(name = "dcmTLSNeedClientAuth", defaultValue = "true")
211    private boolean tlsNeedClientAuth = true;
212
213    @ConfigurableProperty(name = "dicomTLSCipherSuite")
214    private String[] tlsCipherSuites = {};
215
216    @ConfigurableProperty(name = "dcmHTTPProxyProviderName", defaultValue = ProxyService.DEFAULT_PROVIDER_NAME)
217    private String httpProxyProviderName = ProxyService.DEFAULT_PROVIDER_NAME; 
218
219    @ConfigurableProperty(name = "dcmHTTPProxyProviderVersion", defaultValue = ProxyService.DEFAULT_VERSION)
220    private String httpProxyProviderVersion = ProxyService.DEFAULT_VERSION;
221
222    @ConfigurableProperty(name = "dcmTLSProtocol")
223    private String[] tlsProtocols = {};
224
225    @ConfigurableProperty(name = "dcmBlacklistedHostname")
226    private String[] blacklist = {};
227
228    @ConfigurableProperty(name = "dicomInstalled")
229    private Boolean connectionInstalled;
230
231    @ConfigurableProperty(name = "connectionExtensions", isExtensionsProperty = true)
232    private Map<Class<? extends ConnectionExtension>, ConnectionExtension> extensions =
233            new HashMap<Class<? extends ConnectionExtension>, ConnectionExtension>();
234
235    @ConfigurableProperty(
236            name = "dcmProtocol",
237            defaultValue = "DICOM",
238            label = "Protocol",
239            tags = Tag.PRIMARY
240    )
241    private Protocol protocol = Protocol.DICOM;
242
243    private static final EnumMap<Protocol, TCPProtocolHandler> tcpHandlers =
244            new EnumMap<Protocol, TCPProtocolHandler>(Protocol.class);
245    private static final EnumMap<Protocol, UDPProtocolHandler> udpHandlers =
246            new EnumMap<Protocol, UDPProtocolHandler>(Protocol.class);
247
248    private transient List<InetAddress> blacklistAddrs;
249    private transient InetAddress hostAddr;
250    private transient InetAddress bindAddr;
251    private transient InetAddress clientBindAddr;
252    private transient volatile Listener listener;
253    private transient boolean rebindNeeded;
254    
255    static {
256        registerTCPProtocolHandler(Protocol.DICOM, DicomProtocolHandler.INSTANCE);
257    }
258
259    public Connection() {
260    }
261
262    public Connection(String commonName, String hostname) {
263        this(commonName, hostname, NOT_LISTENING);
264    }
265
266    public Connection(String commonName, String hostname, int port) {
267        this.commonName = commonName;
268        this.hostname = hostname;
269        this.port = port;
270    }
271
272    /**
273     *
274     * @param commonName
275     * @param hostname
276     * @param port
277     * @param timeout value in seconds to assign to all timeouts
278     */
279    public Connection(String commonName, String hostname, int port, int timeout) {
280        this(commonName, hostname, port);
281
282        setConnectTimeout(timeout);
283        setRequestTimeout(timeout);
284        setAcceptTimeout(timeout);
285        setReleaseTimeout(timeout);
286        setResponseTimeout(timeout);
287        setRetrieveTimeout(timeout);
288        setIdleTimeout(timeout);
289        setSocketTimeout(timeout);
290    }
291
292
293    public static TCPProtocolHandler registerTCPProtocolHandler(
294            Protocol protocol, TCPProtocolHandler handler) {
295        return tcpHandlers.put(protocol, handler);
296    }
297
298    public static TCPProtocolHandler unregisterTCPProtocolHandler(
299            Protocol protocol) {
300        return tcpHandlers.remove(protocol);
301    }
302
303    public static UDPProtocolHandler registerUDPProtocolHandler(
304            Protocol protocol, UDPProtocolHandler handler) {
305        return udpHandlers.put(protocol, handler);
306    }
307
308    public static UDPProtocolHandler unregisterUDPProtocolHandler(
309            Protocol protocol) {
310        return udpHandlers.remove(protocol);
311    }
312
313    /**
314     * Get the <code>Device</code> object that this Network Connection belongs
315     * to.
316     *
317     * @return Device
318     */
319    public Device getDevice() {
320        return device;
321    }
322
323    /**
324     * Set the <code>Device</code> object that this Network Connection belongs
325     * to.
326     *
327     * @param device The owning <code>Device</code> object.
328     */
329    public void setDevice(Device device) {
330        if (device != null && this.device != null)
331            throw new IllegalStateException("already owned by " + device);
332        this.device = device;
333    }
334
335    /**
336     * This is the DNS name for this particular connection. This is used to
337     * obtain the current IP address for connections. Hostname must be
338     * sufficiently qualified to be unambiguous for any client DNS user.
339     *
340     * @return A String containing the host name.
341     */
342    public final String getHostname() {
343        return hostname;
344    }
345
346    public String getHttpProxyProviderName() {
347        return httpProxyProviderName;
348    }
349
350    public String getHttpProxyProviderVersion() {
351        return httpProxyProviderVersion;
352    }
353
354    /**
355     * This is the DNS name for this particular connection. This is used to
356     * obtain the current IP address for connections. Hostname must be
357     * sufficiently qualified to be unambiguous for any client DNS user.
358     *
359     * @param hostname A String containing the host name.
360     */
361    public final void setHostname(String hostname) {
362        if (hostname != null
363                ? hostname.equals(this.hostname)
364                : this.hostname == null)
365            return;
366
367        this.hostname = hostname;
368        needRebind();
369    }
370
371    /**
372     * Bind address of listening socket or {@code null}. If {@code null}, bind
373     * listening socket to {@link #getHostname()}. This is the default.
374     * <p>
375     * The bind address can also include system properties with the
376     * <em>${system.property}</em> syntax, that need to be resolved.
377     *
378     * @return Bind address of the connection or {@code null}
379     */
380    public final String getBindAddress() {
381        return bindAddress;
382    }
383
384    /**
385     * Bind address of listening socket or {@code null}. If {@code null}, bind
386     * listening socket to {@link #getHostname()}.
387     * <p>
388     * The bind address can also include system properties with the
389     * <em>${system.property}</em> syntax, that will be resolved.
390     *
391     * @param bindAddress
392     *            Bind address of listening socket or {@code null}
393     */
394    public final void setBindAddress(String bindAddress) {
395        if (bindAddress != null
396                ? bindAddress.equals(this.bindAddress)
397                : this.bindAddress == null)
398            return;
399
400        this.bindAddress = bindAddress;
401        this.bindAddr = null;
402        needRebind();
403    }
404
405    /**
406     * Bind address of outgoing connections, {@code "0.0.0.0"} or {@code null}.
407     * If {@code "0.0.0.0"} the system pick up any local ip for outgoing
408     * connections. If {@code null}, bind outgoing connections to
409     * {@link #getHostname()}. This is the default.
410     *
411     * @return Bind address of outgoing connection, {@code 0.0.0.0} or
412     *         {@code null}
413     */
414    public String getClientBindAddress() {
415        return clientBindAddress;
416    }
417
418
419    public ProxyManager getProxyManager() {
420         return ProxyService.getInstance().getProxyManager(this.httpProxyProviderName,this.httpProxyProviderVersion);
421        }
422
423    /**
424     * Bind address of outgoing connections, {@code "0.0.0.0"}  or {@code null}.
425     * If {@code "0.0.0.0"} the system pick up any local ip for outgoing
426     * connections. If {@code null}, bind outgoing connections to
427     * {@link #getHostname()}.
428     *
429     * @param bindAddress Bind address of outgoing connection or {@code null}
430     */
431    public void setClientBindAddress(String bindAddress) {
432        if (bindAddress != null
433                ? bindAddress.equals(this.clientBindAddress)
434                : this.clientBindAddress == null)
435            return;
436
437        this.clientBindAddress = bindAddress;
438        this.clientBindAddr = null;
439    }
440
441    public Protocol getProtocol() {
442        return protocol;
443    }
444
445    public void setProtocol(Protocol protocol) {
446        if (protocol == null)
447            throw new NullPointerException();
448
449        if (this.protocol == protocol)
450            return;
451
452        this.protocol = protocol;
453        needRebind();
454    }
455
456    boolean isRebindNeeded() {
457        return rebindNeeded;
458    }
459
460    void needRebind() {
461        this.rebindNeeded = true;
462    }
463
464    /**
465     * An arbitrary name for the Network Connections object. Can be a meaningful
466     * name or any unique sequence of characters.
467     *
468     * @return A String containing the name.
469     */
470    public final String getCommonName() {
471        return commonName;
472    }
473
474    /**
475     * An arbitrary name for the Network Connections object. Can be a meaningful
476     * name or any unique sequence of characters.
477     *
478     * @param name A String containing the name.
479     */
480    public final void setCommonName(String name) {
481        this.commonName = name;
482    }
483
484    /**
485     * The TCP port that the AE is listening on or <code>-1</code> for a
486     * network connection that only initiates associations.
487     *
488     * @return An int containing the port number or <code>-1</code>.
489     */
490    public final int getPort() {
491        return port;
492    }
493
494    /**
495     * The TCP port that the AE is listening on or <code>0</code> for a
496     * network connection that only initiates associations.
497     * <p>
498     * A valid port value is between 0 and 65535.
499     *
500     * @param port The port number or <code>-1</code>.
501     */
502    public final void setPort(int port) {
503        if (this.port == port)
504            return;
505
506        if ((port <= 0 || port > 0xFFFF) && port != NOT_LISTENING)
507            throw new IllegalArgumentException("port out of range:" + port);
508
509        this.port = port;
510        needRebind();
511    }
512
513    public Map<Class<? extends ConnectionExtension>, ConnectionExtension> getExtensions() {
514        return extensions;
515    }
516
517    public void setExtensions(Map<Class<? extends ConnectionExtension>, ConnectionExtension> extensions) {
518        this.extensions = extensions;
519    }
520
521    public <T> T getExtension(Class<T> clazz) {
522        return (T) extensions.get(clazz);
523    }
524
525    public void addExtension(ConnectionExtension connectionExtension) {
526        connectionExtension.setConnection(this);
527        extensions.put(connectionExtension.getClass(), connectionExtension);
528    }
529
530    public void setHttpProxyProviderName(String httpProxyProviderName) {
531        this.httpProxyProviderName = httpProxyProviderName;
532    }
533
534    public void setHttpProxyProviderVersion(String httpProxyProviderVersion) {
535        this.httpProxyProviderVersion = httpProxyProviderVersion;
536    }
537
538    private void reconfigureExtensions(Connection from) {
539        for (Iterator<Class<? extends ConnectionExtension>> it =
540             extensions.keySet().iterator(); it.hasNext(); ) {
541            if (!from.extensions.containsKey(it.next()))
542                it.remove();
543        }
544        for (ConnectionExtension src : from.extensions.values()) {
545            Class<? extends ConnectionExtension> clazz = src.getClass();
546            ConnectionExtension ext = extensions.get(clazz);
547            if (ext == null)
548                try {
549                    addExtension(ext = clazz.newInstance());
550                } catch (Exception e) {
551                    throw new RuntimeException(
552                            "Failed to instantiate " + clazz.getName(), e);
553                }
554            ext.reconfigure(src);
555        }
556    }
557
558    public final String getHttpProxy() {
559        return httpProxy;
560    }
561
562    public final void setHttpProxy(String proxy) {
563        this.httpProxy = proxy;
564    }
565
566    public final boolean useHttpProxy() {
567        return httpProxy != null;
568    }
569
570    public final boolean isServer() {
571        return port > 0;
572    }
573
574    public final int getBacklog() {
575        return backlog;
576    }
577
578    public final void setBacklog(int backlog) {
579        if (this.backlog == backlog)
580            return;
581
582        if (backlog < 1)
583            throw new IllegalArgumentException("backlog: " + backlog);
584
585        this.backlog = backlog;
586        needRebind();
587    }
588
589    public final int getConnectTimeout() {
590        return connectTimeout;
591    }
592
593    public final void setConnectTimeout(int timeout) {
594        if (timeout < 0)
595            throw new IllegalArgumentException("timeout: " + timeout);
596        this.connectTimeout = timeout;
597    }
598
599    /**
600     * Timeout in ms for receiving A-ASSOCIATE-RQ, 5000 by default
601     *
602     * @return An int value containing the milliseconds.
603     */
604    public final int getRequestTimeout() {
605        return requestTimeout;
606    }
607
608    /**
609     * Timeout in ms for receiving A-ASSOCIATE-RQ, 5000 by default
610     *
611     * @param timeout An int value containing the milliseconds.
612     */
613    public final void setRequestTimeout(int timeout) {
614        if (timeout < 0)
615            throw new IllegalArgumentException("timeout: " + timeout);
616        this.requestTimeout = timeout;
617    }
618
619    public final int getAcceptTimeout() {
620        return acceptTimeout;
621    }
622
623    public final void setAcceptTimeout(int timeout) {
624        if (timeout < 0)
625            throw new IllegalArgumentException("timeout: " + timeout);
626        this.acceptTimeout = timeout;
627    }
628
629
630    /**
631     * Timeout in ms for receiving A-RELEASE-RP, 5000 by default.
632     *
633     * @return An int value containing the milliseconds.
634     */
635    public final int getReleaseTimeout() {
636        return releaseTimeout;
637    }
638
639    /**
640     * Timeout in ms for receiving A-RELEASE-RP, 5000 by default.
641     *
642     * @param timeout An int value containing the milliseconds.
643     */
644    public final void setReleaseTimeout(int timeout) {
645        if (timeout < 0)
646            throw new IllegalArgumentException("timeout: " + timeout);
647        this.releaseTimeout = timeout;
648    }
649
650    /**
651     * Delay in ms for Socket close after sending A-ABORT, 50ms by default.
652     *
653     * @return An int value containing the milliseconds.
654     */
655    public final int getSocketCloseDelay() {
656        return socketCloseDelay;
657    }
658
659    /**
660     * Delay in ms for Socket close after sending A-ABORT, 50ms by default.
661     *
662     * @param delay An int value containing the milliseconds.
663     */
664    public final void setSocketCloseDelay(int delay) {
665        if (delay < 0)
666            throw new IllegalArgumentException("delay: " + delay);
667        this.socketCloseDelay = delay;
668    }
669
670    public final void setResponseTimeout(int timeout) {
671        this.responseTimeout = timeout;
672    }
673
674    public final int getResponseTimeout() {
675        return responseTimeout;
676    }
677
678    public final int getRetrieveTimeout() {
679        return retrieveTimeout;
680    }
681
682    public final void setRetrieveTimeout(int timeout) {
683        this.retrieveTimeout = timeout;
684    }
685
686    public final int getIdleTimeout() {
687        return idleTimeout;
688    }
689
690    public final void setIdleTimeout(int idleTimeout) {
691        this.idleTimeout = idleTimeout;
692    }
693
694    public final int getSocketTimeout() {
695        return socketTimeout;
696    }
697
698    public final void setSocketTimeout(int socketTimeout) {
699        if (socketTimeout < 0)
700            throw new IllegalArgumentException("timeout: " + socketTimeout);
701        this.socketTimeout = socketTimeout;
702    }
703
704    /**
705     * The TLS CipherSuites that are supported on this particular connection.
706     * TLS CipherSuites shall be described using an RFC-2246 string
707     * representation (e.g. 'SSL_RSA_WITH_3DES_EDE_CBC_SHA')
708     *
709     * @return A String array containing the supported cipher suites
710     */
711    public String[] getTlsCipherSuites() {
712        return tlsCipherSuites;
713    }
714
715    /**
716     * The TLS CipherSuites that are supported on this particular connection.
717     * TLS CipherSuites shall be described using an RFC-2246 string
718     * representation (e.g. 'SSL_RSA_WITH_3DES_EDE_CBC_SHA')
719     *
720     * @param tlsCipherSuites A String array containing the supported cipher suites
721     */
722    public void setTlsCipherSuites(String... tlsCipherSuites) {
723        if (Arrays.equals(this.tlsCipherSuites, tlsCipherSuites))
724            return;
725
726        this.tlsCipherSuites = tlsCipherSuites;
727        needRebind();
728    }
729
730    public final boolean isTls() {
731        return tlsCipherSuites.length > 0;
732    }
733
734    public final String[] tlsProtocols() {
735        return tlsProtocols.length != 0 ? tlsProtocols : DEFAULT_TLS_PROTOCOLS;
736    }
737
738    public final String[] getTlsProtocols() {
739        return tlsProtocols;
740    }
741
742    public final void setTlsProtocols(String... tlsProtocols) {
743        if (Arrays.equals(this.tlsProtocols, tlsProtocols))
744            return;
745
746        this.tlsProtocols = tlsProtocols;
747        needRebind();
748    }
749
750    public final boolean isTlsNeedClientAuth() {
751        return tlsNeedClientAuth;
752    }
753
754    public final void setTlsNeedClientAuth(boolean tlsNeedClientAuth) {
755        if (this.tlsNeedClientAuth == tlsNeedClientAuth)
756            return;
757
758        this.tlsNeedClientAuth = tlsNeedClientAuth;
759        needRebind();
760    }
761
762    /**
763     * Get the SO_RCVBUF socket value in KB.
764     *
765     * @return An int value containing the buffer size in KB.
766     */
767    public final int getReceiveBufferSize() {
768        return receiveBufferSize;
769    }
770
771    /**
772     * Set the SO_RCVBUF socket option to specified value in KB.
773     *
774     * @param size An int value containing the buffer size in KB.
775     */
776    public final void setReceiveBufferSize(int size) {
777        if (size < 0)
778            throw new IllegalArgumentException("size: " + size);
779        this.receiveBufferSize = size;
780    }
781
782    /**
783     * Get the SO_SNDBUF socket option value in KB,
784     *
785     * @return An int value containing the buffer size in KB.
786     */
787    public final int getSendBufferSize() {
788        return sendBufferSize;
789    }
790
791    /**
792     * Set the SO_SNDBUF socket option to specified value in KB,
793     *
794     * @param size An int value containing the buffer size in KB.
795     */
796    public final void setSendBufferSize(int size) {
797        if (size < 0)
798            throw new IllegalArgumentException("size: " + size);
799        this.sendBufferSize = size;
800    }
801
802    public final int getSendPDULength() {
803        return sendPDULength;
804    }
805
806    public final void setSendPDULength(int sendPDULength) {
807        this.sendPDULength = sendPDULength;
808    }
809
810    public final int getReceivePDULength() {
811        return receivePDULength;
812    }
813
814    public final void setReceivePDULength(int receivePDULength) {
815        this.receivePDULength = receivePDULength;
816    }
817
818    public final int getMaxOpsPerformed() {
819        return maxOpsPerformed;
820    }
821
822    public final void setMaxOpsPerformed(int maxOpsPerformed) {
823        this.maxOpsPerformed = maxOpsPerformed;
824    }
825
826    public final int getMaxOpsInvoked() {
827        return maxOpsInvoked;
828    }
829
830    public final void setMaxOpsInvoked(int maxOpsInvoked) {
831        this.maxOpsInvoked = maxOpsInvoked;
832    }
833
834    public final boolean isPackPDV() {
835        return packPDV;
836    }
837
838    public final void setPackPDV(boolean packPDV) {
839        this.packPDV = packPDV;
840    }
841
842    /**
843     * Determine if this network connection is using Nagle's algorithm as part
844     * of its network communication.
845     *
846     * @return boolean True if TCP no delay (disable Nagle's algorithm) is used.
847     */
848    public final boolean isTcpNoDelay() {
849        return tcpNoDelay;
850    }
851
852    /**
853     * Set whether or not this network connection should use Nagle's algorithm
854     * as part of its network communication.
855     *
856     * @param tcpNoDelay boolean True if TCP no delay (disable Nagle's algorithm)
857     *                   should be used.
858     */
859    public final void setTcpNoDelay(boolean tcpNoDelay) {
860        this.tcpNoDelay = tcpNoDelay;
861    }
862
863    /**
864     * True if the Network Connection is installed on the network. If not
865     * present, information about the installed status of the Network Connection
866     * is inherited from the device.
867     *
868     * @return boolean True if the NetworkConnection is installed on the
869     * network.
870     */
871    public boolean isInstalled() {
872        return device != null && device.isInstalled()
873                && (connectionInstalled == null || connectionInstalled.booleanValue());
874    }
875
876    public Boolean getConnectionInstalled() {
877        return connectionInstalled;
878    }
879
880    /**
881     * True if the Network Connection is installed on the network. If not
882     * present, information about the installed status of the Network Connection
883     * is inherited from the device.
884     *
885     * @param installed True if the NetworkConnection is installed on the network.
886     * @throws GeneralSecurityException
887     */
888    public void setConnectionInstalled(Boolean installed) {
889        if (this.connectionInstalled == installed)
890            return;
891
892        boolean prev = isInstalled();
893        this.connectionInstalled = installed;
894        if (isInstalled() != prev)
895            needRebind();
896    }
897
898    synchronized void rebind() throws IOException, GeneralSecurityException {
899        unbind();
900        bind();
901    }
902
903    /**
904     * Get a list of IP addresses from which we should ignore connections.
905     * Useful in an environment that utilizes a load balancer. In the case of a
906     * TCP ping from a load balancing switch, we don't want to spin off a new
907     * thread and try to negotiate an association.
908     *
909     * @return Returns the list of IP addresses which should be ignored.
910     */
911    public final String[] getBlacklist() {
912        return blacklist;
913    }
914
915    /**
916     * Set a list of IP addresses from which we should ignore connections.
917     * Useful in an environment that utilizes a load balancer. In the case of a
918     * TCP ping from a load balancing switch, we don't want to spin off a new
919     * thread and try to negotiate an association.
920     *
921     * @param blacklist the list of IP addresses which should be ignored.
922     */
923    public final void setBlacklist(String[] blacklist) {
924        this.blacklist = blacklist;
925        this.blacklistAddrs = null;
926    }
927
928    @Override
929    public String toString() {
930        return promptTo(new StringBuilder(), "").toString();
931    }
932
933    public StringBuilder promptTo(StringBuilder sb, String indent) {
934        String indent2 = indent + "  ";
935        StringUtils.appendLine(sb, indent, "Connection[cn: ", commonName);
936        StringUtils.appendLine(sb, indent2, "host: ", hostname);
937        StringUtils.appendLine(sb, indent2, "port: ", port);
938        StringUtils.appendLine(sb, indent2, "ciphers: ", Arrays.toString(tlsCipherSuites));
939        StringUtils.appendLine(sb, indent2, "installed: ", getConnectionInstalled());
940        return sb.append(indent).append(']');
941    }
942
943    void setSocketSendOptions(Socket s) throws SocketException {
944        int size = s.getSendBufferSize();
945        if (sendBufferSize == 0) {
946            sendBufferSize = size;
947        } else if (sendBufferSize != size) {
948            s.setSendBufferSize(sendBufferSize);
949            sendBufferSize = s.getSendBufferSize();
950        }
951        if (s.getTcpNoDelay() != tcpNoDelay) {
952            s.setTcpNoDelay(tcpNoDelay);
953        }
954        if (s.getSoTimeout() != socketTimeout) {
955            s.setSoTimeout(socketTimeout);
956        }
957    }
958
959    private void setReceiveBufferSize(Socket s) throws SocketException {
960        int size = s.getReceiveBufferSize();
961        if (receiveBufferSize == 0) {
962            receiveBufferSize = size;
963        } else if (receiveBufferSize != size) {
964            s.setReceiveBufferSize(receiveBufferSize);
965            receiveBufferSize = s.getReceiveBufferSize();
966        }
967    }
968
969    void setReceiveBufferSize(ServerSocket ss) throws SocketException {
970        int size = ss.getReceiveBufferSize();
971        if (receiveBufferSize == 0) {
972            receiveBufferSize = size;
973        } else if (receiveBufferSize != size) {
974            ss.setReceiveBufferSize(receiveBufferSize);
975            receiveBufferSize = ss.getReceiveBufferSize();
976        }
977    }
978
979    public void setReceiveBufferSize(DatagramSocket ds) throws SocketException {
980        int size = ds.getReceiveBufferSize();
981        if (receiveBufferSize == 0) {
982            receiveBufferSize = size;
983        } else if (receiveBufferSize != size) {
984            ds.setReceiveBufferSize(receiveBufferSize);
985            receiveBufferSize = ds.getReceiveBufferSize();
986        }
987    }
988
989    private InetAddress hostAddr() throws UnknownHostException {
990        if (hostAddr == null && hostname != null)
991            hostAddr = InetAddress.getByName(hostname);
992
993        return hostAddr;
994    }
995
996    private InetAddress bindAddr() throws UnknownHostException {
997        if (bindAddress == null)
998            return hostAddr();
999
1000        if (bindAddr == null) {
1001            String resolvedBindAddress = StringUtils.replaceSystemProperties(bindAddress);
1002            bindAddr = InetAddress.getByName(resolvedBindAddress);
1003        }
1004
1005        return bindAddr;
1006    }
1007
1008    private InetAddress clientBindAddr() throws UnknownHostException {
1009        if (clientBindAddress == null)
1010            return hostAddr();
1011
1012        if (clientBindAddr == null)
1013            clientBindAddr = InetAddress.getByName(clientBindAddress);
1014
1015        return clientBindAddr;
1016    }
1017
1018    private List<InetAddress> blacklistAddrs() {
1019        if (blacklistAddrs == null) {
1020            blacklistAddrs = new ArrayList<InetAddress>(blacklist.length);
1021            for (String hostname : blacklist)
1022                try {
1023                    blacklistAddrs.add(InetAddress.getByName(hostname));
1024                } catch (UnknownHostException e) {
1025                    LOG.warn("Failed to lookup InetAddress of " + hostname, e);
1026                }
1027        }
1028        return blacklistAddrs;
1029    }
1030
1031
1032    public InetSocketAddress getEndPoint() throws UnknownHostException {
1033        return new InetSocketAddress(hostAddr(), port);
1034    }
1035
1036    public InetSocketAddress getBindPoint() throws UnknownHostException {
1037        return new InetSocketAddress(bindAddr(), port);
1038    }
1039
1040    public InetSocketAddress getClientBindPoint() throws UnknownHostException {
1041        return new InetSocketAddress(clientBindAddr(), 0);
1042    }
1043
1044    private void checkInstalled() {
1045        if (!isInstalled())
1046            throw new IllegalStateException("Not installed");
1047    }
1048
1049    private void checkCompatible(Connection remoteConn) throws IncompatibleConnectionException {
1050        if (!isCompatible(remoteConn))
1051            throw new IncompatibleConnectionException(remoteConn.toString());
1052    }
1053
1054    /**
1055     * Bind this network connection to a TCP port and start a server socket
1056     * accept loop.
1057     *
1058     * @throws IOException              If there is a problem with the network interaction.
1059     * @throws GeneralSecurityException
1060     */
1061    public synchronized boolean bind() throws IOException, GeneralSecurityException {
1062        if (!(isInstalled() && isServer())) {
1063            rebindNeeded = false;
1064            return false;
1065        }
1066        if (device == null)
1067            throw new IllegalStateException("Not attached to Device");
1068        if (isListening())
1069            throw new IllegalStateException("Already listening - " + listener);
1070        if (protocol.isTCP()) {
1071            TCPProtocolHandler handler = tcpHandlers.get(protocol);
1072            if (handler == null)
1073                throw new IllegalStateException("No TCP Protocol Handler for protocol " + protocol);
1074            listener = new TCPListener(this, handler);
1075        } else {
1076            UDPProtocolHandler handler = udpHandlers.get(protocol);
1077            if (handler == null)
1078                throw new IllegalStateException("No UDP Protocol Handler for protocol " + protocol);
1079            listener = new UDPListener(this, handler);
1080        }
1081        rebindNeeded = false;
1082        return true;
1083    }
1084
1085    public final boolean isListening() {
1086        return listener != null;
1087    }
1088
1089    public boolean isBlackListed(InetAddress ia) {
1090        return blacklistAddrs().contains(ia);
1091    }
1092
1093    public synchronized void unbind() {
1094        Closeable tmp = listener;
1095        if (tmp == null)
1096            return;
1097        listener = null;
1098        try {
1099            tmp.close();
1100        } catch (Throwable e) {
1101            // Ignore errors when closing the server socket.
1102        }
1103    }
1104
1105    public Socket connect(Connection remoteConn)
1106            throws IOException, IncompatibleConnectionException, GeneralSecurityException {
1107        checkInstalled();
1108        if (!protocol.isTCP())
1109            throw new IllegalStateException("Not a TCP Connection");
1110        checkCompatible(remoteConn);
1111        SocketAddress bindPoint = getClientBindPoint();
1112        String remoteHostname = remoteConn.getHostname();
1113        int remotePort = remoteConn.getPort();
1114        LOG.info("Initiate connection from {} to {}:{}",
1115                bindPoint, remoteHostname, remotePort);
1116        Socket s = new Socket();
1117        ConnectionMonitor monitor = device != null
1118                ? device.getConnectionMonitor()
1119                : null;
1120        try {
1121            s.bind(bindPoint);
1122            setReceiveBufferSize(s);
1123            setSocketSendOptions(s);
1124            String remoteProxy = remoteConn.getHttpProxy();
1125            if (remoteProxy != null) {
1126                String userauth = null;
1127                String[] ss = StringUtils.split(remoteProxy, '@');
1128                if (ss.length > 1) {
1129                    userauth = ss[0];
1130                    remoteProxy = ss[1];
1131                }
1132                ss = StringUtils.split(remoteProxy, ':');
1133                int proxyPort = ss.length > 1 ? Integer.parseInt(ss[1]) : 8080;
1134                s.connect(new InetSocketAddress(ss[0], proxyPort), connectTimeout);
1135                try {
1136                    getProxyManager().doProxyHandshake(s, remoteHostname, remotePort, userauth,
1137                            connectTimeout);
1138                } catch (IOException e) {
1139                    SafeClose.close(s);
1140                    throw e;
1141                }
1142            } else {
1143                s.connect(remoteConn.getEndPoint(), connectTimeout);
1144            }
1145            if (isTls())
1146                s = createTLSSocket(s, remoteConn);
1147            if (monitor != null)
1148                monitor.onConnectionEstablished(this, remoteConn, s);
1149            LOG.info("Established connection {}", s);
1150            return s;
1151        } catch (GeneralSecurityException e) {
1152            if (monitor != null)
1153                monitor.onConnectionFailed(this, remoteConn, s, e);
1154            SafeClose.close(s);
1155            throw e;
1156        } catch (IOException e) {
1157            if (monitor != null)
1158                monitor.onConnectionFailed(this, remoteConn, s, e);
1159            SafeClose.close(s);
1160            throw new IOException("Error while trying to establish connection "+getHostname()+" -> "+remoteHostname+":"+remotePort,e);
1161        }
1162    }
1163
1164    public DatagramSocket createDatagramSocket() throws IOException {
1165        checkInstalled();
1166        if (protocol.isTCP())
1167            throw new IllegalStateException("Not a UDP Connection");
1168
1169        DatagramSocket ds = new DatagramSocket(getClientBindPoint());
1170        int size = ds.getSendBufferSize();
1171        if (sendBufferSize == 0) {
1172            sendBufferSize = size;
1173        } else if (sendBufferSize != size) {
1174            ds.setSendBufferSize(sendBufferSize);
1175            sendBufferSize = ds.getSendBufferSize();
1176        }
1177        return ds;
1178    }
1179
1180    public Listener getListener() {
1181        return listener;
1182    }
1183
1184//    private void doProxyHandshake(Socket s, String hostname, int port,
1185//                                  String userauth, int connectTimeout) throws IOException {
1186//
1187//        StringBuilder request = new StringBuilder(128);
1188//        request.append("CONNECT ")
1189//                .append(hostname).append(':').append(port)
1190//                .append(" HTTP/1.1\r\nHost: ")
1191//                .append(hostname).append(':').append(port);
1192//        if (userauth != null) {
1193//            byte[] b = userauth.getBytes("UTF-8");
1194//            char[] base64 = new char[(b.length + 2) / 3 * 4];
1195//            Base64.encode(b, 0, b.length, base64, 0);
1196//            request.append("\r\nProxy-Authorization: basic ")
1197//                    .append(base64);
1198//        }
1199//        request.append("\r\n\r\n");
1200//        OutputStream out = s.getOutputStream();
1201//        out.write(request.toString().getBytes("US-ASCII"));
1202//        out.flush();
1203//
1204//        s.setSoTimeout(connectTimeout);
1205//        @SuppressWarnings("resource")
1206//        String response = new HTTPResponse(s).toString();
1207//        s.setSoTimeout(socketTimeout);
1208//        if (!response.startsWith("HTTP/1.1 2"))
1209//            throw new IOException("Unable to tunnel through " + s
1210//                    + ". Proxy returns \"" + response + '\"');
1211//    }
1212//
1213//    private static class HTTPResponse extends ByteArrayOutputStream {
1214//
1215//        private final String rsp;
1216//
1217//        public HTTPResponse(Socket s) throws IOException {
1218//            super(64);
1219//            InputStream in = s.getInputStream();
1220//            boolean eol = false;
1221//            int b;
1222//            while ((b = in.read()) != -1) {
1223//                write(b);
1224//                if (b == '\n') {
1225//                    if (eol) {
1226//                        rsp = new String(super.buf, 0, super.count, "US-ASCII");
1227//                        return;
1228//                    }
1229//                    eol = true;
1230//                } else if (b != '\r') {
1231//                    eol = false;
1232//                }
1233//            }
1234//            throw new IOException("Unexpected EOF from " + s);
1235//        }
1236//
1237//        @Override
1238//        public String toString() {
1239//            return rsp;
1240//        }
1241//    }
1242
1243    private SSLSocket createTLSSocket(Socket s, Connection remoteConn)
1244            throws GeneralSecurityException, IOException {
1245        SSLContext sslContext = device.sslContext();
1246        SSLSocketFactory sf = sslContext.getSocketFactory();
1247        SSLSocket ssl = (SSLSocket) sf.createSocket(s,
1248                remoteConn.getHostname(), remoteConn.getPort(), true);
1249        ssl.setEnabledProtocols(
1250                intersect(remoteConn.tlsProtocols(), tlsProtocols()));
1251        ssl.setEnabledCipherSuites(
1252                intersect(remoteConn.tlsCipherSuites, tlsCipherSuites));
1253        ssl.startHandshake();
1254        return ssl;
1255    }
1256
1257    public void close(Socket s) {
1258        LOG.info("Close connection {}", s);
1259        SafeClose.close(s);
1260    }
1261
1262    public boolean isCompatible(Connection remoteConn) {
1263        if (remoteConn.protocol != protocol)
1264            return false;
1265
1266        if (!protocol.isTCP())
1267            return true;
1268
1269        if (!isTls())
1270            return !remoteConn.isTls();
1271
1272        return hasCommon(remoteConn.tlsProtocols(), tlsProtocols())
1273            && hasCommon(remoteConn.tlsCipherSuites, tlsCipherSuites);
1274    }
1275
1276    private boolean hasCommon(String[] ss1, String[] ss2) {
1277        for (String s1 : ss1)
1278            for (String s2 : ss2)
1279                if (s1.equals(s2))
1280                    return true;
1281        return false;
1282    }
1283
1284    private static String[] intersect(String[] ss1, String[] ss2) {
1285        String[] ss = new String[Math.min(ss1.length, ss2.length)];
1286        int len = 0;
1287        for (String s1 : ss1)
1288            for (String s2 : ss2)
1289                if (s1.equals(s2)) {
1290                    ss[len++] = s1;
1291                    break;
1292                }
1293        ;
1294        if (len == ss.length)
1295            return ss;
1296
1297        String[] dest = new String[len];
1298        System.arraycopy(ss, 0, dest, 0, len);
1299        return dest;
1300    }
1301
1302    boolean equalsRDN(Connection other) {
1303        return commonName != null
1304                ? commonName.equals(other.commonName)
1305                : other.commonName == null
1306                && hostname.equals(other.hostname)
1307                && port == other.port
1308                && protocol == other.protocol;
1309    }
1310
1311    public String getOlockHash() {
1312        return olockHash;
1313    }
1314
1315    public void setOlockHash(String olockHash) {
1316        this.olockHash = olockHash;
1317    }
1318
1319    void reconfigure(Connection from) {
1320        setOlockHash(from.olockHash);
1321        setUuid(from.uuid);
1322        setCommonName(from.commonName);
1323        setHostname(from.hostname);
1324        setPort(from.port);
1325        setBindAddress(from.bindAddress);
1326        setClientBindAddress(from.clientBindAddress);
1327        setProtocol(from.protocol);
1328        setHttpProxy(from.httpProxy);
1329        setBacklog(from.backlog);
1330        setConnectTimeout(from.connectTimeout);
1331        setRequestTimeout(from.requestTimeout);
1332        setAcceptTimeout(from.acceptTimeout);
1333        setReleaseTimeout(from.releaseTimeout);
1334        setResponseTimeout(from.responseTimeout);
1335        setRetrieveTimeout(from.retrieveTimeout);
1336        setIdleTimeout(from.idleTimeout);
1337        setSocketTimeout(from.socketTimeout);
1338        setSocketCloseDelay(from.socketCloseDelay);
1339        setSendBufferSize(from.sendBufferSize);
1340        setReceiveBufferSize(from.receiveBufferSize);
1341        setSendPDULength(from.sendPDULength);
1342        setReceivePDULength(from.receivePDULength);
1343        setMaxOpsPerformed(from.maxOpsPerformed);
1344        setMaxOpsPerformed(from.maxOpsInvoked);
1345        setPackPDV(from.packPDV);
1346        setTcpNoDelay(from.tcpNoDelay);
1347        setTlsNeedClientAuth(from.tlsNeedClientAuth);
1348        setTlsCipherSuites(from.tlsCipherSuites);
1349        setTlsProtocols(from.tlsProtocols);
1350        setBlacklist(from.blacklist);
1351        setConnectionInstalled(from.connectionInstalled);
1352        reconfigureExtensions(from);
1353    }
1354
1355}