1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.conscrypt;
18 
19 import java.util.HashMap;
20 import java.util.Map;
21 import javax.net.ssl.SSLSession;
22 
23 /**
24  * Caches client sessions. Indexes by host and port. Users are typically
25  * looking to reuse any session for a given host and port.
26  */
27 public class ClientSessionContext extends AbstractSessionContext {
28 
29     /**
30      * Sessions indexed by host and port. Protect from concurrent
31      * access by holding a lock on sessionsByHostAndPort.
32      */
33     private final HashMap<HostAndPort, SSLSession> sessionsByHostAndPort = new HashMap<>();
34 
35     private SSLClientSessionCache persistentCache;
36 
ClientSessionContext()37     public ClientSessionContext() {
38         super(10);
39     }
40 
size()41     public int size() {
42         return sessionsByHostAndPort.size();
43     }
44 
setPersistentCache(SSLClientSessionCache persistentCache)45     public void setPersistentCache(SSLClientSessionCache persistentCache) {
46         this.persistentCache = persistentCache;
47     }
48 
49     @Override
sessionRemoved(SSLSession session)50     protected void sessionRemoved(SSLSession session) {
51         String host = session.getPeerHost();
52         int port = session.getPeerPort();
53         if (host == null) {
54             return;
55         }
56         HostAndPort hostAndPortKey = new HostAndPort(host, port);
57         synchronized (sessionsByHostAndPort) {
58             sessionsByHostAndPort.remove(hostAndPortKey);
59         }
60     }
61 
62     /**
63      * Finds a cached session for the given host name and port.
64      *
65      * @param host of server
66      * @param port of server
67      * @return cached session or null if none found
68      */
getSession(String host, int port)69     public SSLSession getSession(String host, int port) {
70         if (host == null) {
71             return null;
72         }
73         SSLSession session;
74         HostAndPort hostAndPortKey = new HostAndPort(host, port);
75         synchronized (sessionsByHostAndPort) {
76             session = sessionsByHostAndPort.get(hostAndPortKey);
77         }
78         if (session != null && session.isValid()) {
79             return wrapSSLSessionIfNeeded(session);
80         }
81 
82         // Look in persistent cache.
83         if (persistentCache != null) {
84             byte[] data = persistentCache.getSessionData(host, port);
85             if (data != null) {
86                 session = toSession(data, host, port);
87                 if (session != null && session.isValid()) {
88                     super.putSession(session);
89                     synchronized (sessionsByHostAndPort) {
90                         sessionsByHostAndPort.put(hostAndPortKey, session);
91                     }
92                     return wrapSSLSessionIfNeeded(session);
93                 }
94             }
95         }
96 
97         return null;
98     }
99 
100     @Override
putSession(SSLSession session)101     public void putSession(SSLSession session) {
102         super.putSession(session);
103 
104         String host = session.getPeerHost();
105         int port = session.getPeerPort();
106         if (host == null) {
107             return;
108         }
109 
110         HostAndPort hostAndPortKey = new HostAndPort(host, port);
111         synchronized (sessionsByHostAndPort) {
112             sessionsByHostAndPort.put(hostAndPortKey, session);
113         }
114 
115         // TODO: This in a background thread.
116         if (persistentCache != null) {
117             byte[] data = toBytes(session);
118             if (data != null) {
119                 persistentCache.putSessionData(session, data);
120             }
121         }
122     }
123 
124     static class HostAndPort {
125         final String host;
126         final int port;
127 
HostAndPort(String host, int port)128         HostAndPort(String host, int port) {
129             this.host = host;
130             this.port = port;
131         }
132 
133         @Override
hashCode()134         public int hashCode() {
135             return host.hashCode() * 31 + port;
136         }
137 
138         @Override
equals(Object o)139         public boolean equals(Object o) {
140             if (!(o instanceof HostAndPort)) {
141                 return false;
142             }
143             HostAndPort lhs = (HostAndPort) o;
144             return host.equals(lhs.host) && port == lhs.port;
145         }
146     }
147 }
148