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
17package org.conscrypt;
18
19import java.util.HashMap;
20import java.util.Map;
21import 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 */
27public 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    final Map<HostAndPort, SSLSession> sessionsByHostAndPort
34        = new HashMap<HostAndPort, SSLSession>();
35
36    private SSLClientSessionCache persistentCache;
37
38    public ClientSessionContext() {
39        super(10);
40    }
41
42    public int size() {
43        return sessionsByHostAndPort.size();
44    }
45
46    public void setPersistentCache(SSLClientSessionCache persistentCache) {
47        this.persistentCache = persistentCache;
48    }
49
50    @Override
51    protected void sessionRemoved(SSLSession session) {
52        String host = session.getPeerHost();
53        int port = session.getPeerPort();
54        if (host == null) {
55            return;
56        }
57        HostAndPort hostAndPortKey = new HostAndPort(host, port);
58        synchronized (sessionsByHostAndPort) {
59            sessionsByHostAndPort.remove(hostAndPortKey);
60        }
61    }
62
63    /**
64     * Finds a cached session for the given host name and port.
65     *
66     * @param host of server
67     * @param port of server
68     * @return cached session or null if none found
69     */
70    public SSLSession getSession(String host, int port) {
71        if (host == null) {
72            return null;
73        }
74        SSLSession session;
75        HostAndPort hostAndPortKey = new HostAndPort(host, port);
76        synchronized (sessionsByHostAndPort) {
77            session = sessionsByHostAndPort.get(hostAndPortKey);
78        }
79        if (session != null && session.isValid()) {
80            return session;
81        }
82
83        // Look in persistent cache.
84        if (persistentCache != null) {
85            byte[] data = persistentCache.getSessionData(host, port);
86            if (data != null) {
87                session = toSession(data, host, port);
88                if (session != null && session.isValid()) {
89                    super.putSession(session);
90                    synchronized (sessionsByHostAndPort) {
91                        sessionsByHostAndPort.put(hostAndPortKey, session);
92                    }
93                    return session;
94                }
95            }
96        }
97
98        return null;
99    }
100
101    @Override
102    public void putSession(SSLSession session) {
103        super.putSession(session);
104
105        String host = session.getPeerHost();
106        int port = session.getPeerPort();
107        if (host == null) {
108            return;
109        }
110
111        HostAndPort hostAndPortKey = new HostAndPort(host, port);
112        synchronized (sessionsByHostAndPort) {
113            sessionsByHostAndPort.put(hostAndPortKey, session);
114        }
115
116        // TODO: This in a background thread.
117        if (persistentCache != null) {
118            byte[] data = toBytes(session);
119            if (data != null) {
120                persistentCache.putSessionData(session, data);
121            }
122        }
123    }
124
125    static class HostAndPort {
126        final String host;
127        final int port;
128
129        HostAndPort(String host, int port) {
130            this.host = host;
131            this.port = port;
132        }
133
134        @Override
135        public int hashCode() {
136            return host.hashCode() * 31 + port;
137        }
138
139        @Override
140        public boolean equals(Object o) {
141            if (!(o instanceof HostAndPort)) {
142                return false;
143            }
144            HostAndPort lhs = (HostAndPort) o;
145            return host.equals(lhs.host) && port == lhs.port;
146        }
147    }
148}
149