View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.ssl;
17  
18  import io.netty.internal.tcnative.SSL;
19  import io.netty.util.AsciiString;
20  
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  /**
25   * {@link OpenSslSessionCache} that is used by the client-side.
26   */
27  final class OpenSslClientSessionCache extends OpenSslSessionCache {
28      // TODO: Should we support to have a List of OpenSslSessions for a Host/Port key and so be able to
29      // support sessions for different protocols / ciphers to the same remote peer ?
30      private final Map<HostPort, NativeSslSession> sessions = new HashMap<HostPort, NativeSslSession>();
31  
32      OpenSslClientSessionCache(OpenSslEngineMap engineMap) {
33          super(engineMap);
34      }
35  
36      @Override
37      protected boolean sessionCreated(NativeSslSession session) {
38          assert Thread.holdsLock(this);
39          HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
40          if (hostPort == null || sessions.containsKey(hostPort)) {
41              return false;
42          }
43          sessions.put(hostPort, session);
44          return true;
45      }
46  
47      @Override
48      protected void sessionRemoved(NativeSslSession session) {
49          assert Thread.holdsLock(this);
50          HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
51          if (hostPort == null) {
52              return;
53          }
54          sessions.remove(hostPort);
55      }
56  
57      @Override
58      void setSession(long ssl, String host, int port) {
59          HostPort hostPort = keyFor(host, port);
60          if (hostPort == null) {
61              return;
62          }
63          final NativeSslSession session;
64          final boolean reused;
65          synchronized (this) {
66              session = sessions.get(hostPort);
67              if (session == null) {
68                  return;
69              }
70              if (!session.isValid()) {
71                  removeSessionWithId(session.sessionId());
72                  return;
73              }
74              // Try to set the session, if true is returned OpenSSL incremented the reference count
75              // of the underlying SSL_SESSION*.
76              reused = SSL.setSession(ssl, session.session());
77          }
78  
79          if (reused) {
80              if (session.shouldBeSingleUse()) {
81                  // Should only be used once
82                  session.invalidate();
83              }
84              session.updateLastAccessedTime();
85          }
86      }
87  
88      private static HostPort keyFor(String host, int port) {
89          if (host == null && port < 1) {
90              return null;
91          }
92          return new HostPort(host, port);
93      }
94  
95      @Override
96      synchronized void clear() {
97          super.clear();
98          sessions.clear();
99      }
100 
101     /**
102      * Host / Port tuple used to find a {@link OpenSslSession} in the cache.
103      */
104     private static final class HostPort {
105         private final int hash;
106         private final String host;
107         private final int port;
108 
109         HostPort(String host, int port) {
110             this.host = host;
111             this.port = port;
112             // Calculate a hashCode that does ignore case.
113             this.hash = 31 * AsciiString.hashCode(host) + port;
114         }
115 
116         @Override
117         public int hashCode() {
118             return hash;
119         }
120 
121         @Override
122         public boolean equals(Object obj) {
123             if (!(obj instanceof HostPort)) {
124                 return false;
125             }
126             HostPort other = (HostPort) obj;
127             return port == other.port && host.equalsIgnoreCase(other.host);
128         }
129 
130         @Override
131         public String toString() {
132             return "HostPort{" +
133                     "host='" + host + '\'' +
134                     ", port=" + port +
135                     '}';
136         }
137     }
138 }