Home | History | Annotate | Download | only in jsse
      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.apache.harmony.xnet.provider.jsse;
     18 
     19 import java.util.Iterator;
     20 import java.util.LinkedHashMap;
     21 import java.util.Map;
     22 import java.util.HashMap;
     23 import java.util.ArrayList;
     24 import java.util.Arrays;
     25 
     26 import javax.net.ssl.SSLSession;
     27 
     28 /**
     29  * Caches client sessions. Indexes by host and port. Users are typically
     30  * looking to reuse any session for a given host and port. Users of the
     31  * standard API are forced to iterate over the sessions semi-linearly as
     32  * opposed to in constant time.
     33  */
     34 public class ClientSessionContext extends AbstractSessionContext {
     35 
     36     /*
     37      * We don't care about timeouts in the client implementation. Trying
     38      * to reuse an expired session and having to start a new one requires no
     39      * more effort than starting a new one, so you might as well try to reuse
     40      * one on the off chance it's still valid.
     41      */
     42 
     43     /** Sessions indexed by host and port in access order. */
     44     final Map<HostAndPort, SSLSession> sessions
     45             = new LinkedHashMap<HostAndPort, SSLSession>() {
     46         @Override
     47         protected boolean removeEldestEntry(
     48                 Map.Entry<HostAndPort, SSLSession> eldest) {
     49             // Called while lock is held on sessions.
     50             boolean remove = maximumSize > 0 && size() > maximumSize;
     51             if (remove) {
     52                 removeById(eldest.getValue());
     53             }
     54             return remove;
     55         }
     56     };
     57 
     58     /**
     59      * Sessions indexed by ID. Initialized on demand. Protected from concurrent
     60      * access by holding a lock on sessions.
     61      */
     62     Map<ByteArray, SSLSession> sessionsById;
     63 
     64     final SSLClientSessionCache persistentCache;
     65 
     66     public ClientSessionContext(SSLParameters parameters,
     67             SSLClientSessionCache persistentCache) {
     68         super(parameters, 10, 0);
     69         this.persistentCache = persistentCache;
     70     }
     71 
     72     public final void setSessionTimeout(int seconds)
     73             throws IllegalArgumentException {
     74         if (seconds < 0) {
     75             throw new IllegalArgumentException("seconds < 0");
     76         }
     77         timeout = seconds;
     78     }
     79 
     80     Iterator<SSLSession> sessionIterator() {
     81         synchronized (sessions) {
     82             SSLSession[] array = sessions.values().toArray(
     83                     new SSLSession[sessions.size()]);
     84             return Arrays.asList(array).iterator();
     85         }
     86     }
     87 
     88     void trimToSize() {
     89         synchronized (sessions) {
     90             int size = sessions.size();
     91             if (size > maximumSize) {
     92                 int removals = size - maximumSize;
     93                 Iterator<SSLSession> i = sessions.values().iterator();
     94                 do {
     95                     removeById(i.next());
     96                     i.remove();
     97                 } while (--removals > 0);
     98             }
     99         }
    100     }
    101 
    102     void removeById(SSLSession session) {
    103         if (sessionsById != null) {
    104             sessionsById.remove(new ByteArray(session.getId()));
    105         }
    106     }
    107 
    108     /**
    109      * {@inheritDoc}
    110      *
    111      * @see #getSession(String, int) for an implementation-specific but more
    112      *  efficient approach
    113      */
    114     public SSLSession getSession(byte[] sessionId) {
    115         /*
    116          * This method is typically used in conjunction with getIds() to
    117          * iterate over the sessions linearly, so it doesn't make sense for
    118          * it to impact access order.
    119          *
    120          * It also doesn't load sessions from the persistent cache as doing
    121          * so would likely force every session to load.
    122          */
    123 
    124         ByteArray id = new ByteArray(sessionId);
    125         synchronized (sessions) {
    126             indexById();
    127             return sessionsById.get(id);
    128         }
    129     }
    130 
    131     /**
    132      * Ensures that the ID-based index is initialized.
    133      */
    134     private void indexById() {
    135         if (sessionsById == null) {
    136             sessionsById = new HashMap<ByteArray, SSLSession>();
    137             for (SSLSession session : sessions.values()) {
    138                 sessionsById.put(new ByteArray(session.getId()), session);
    139             }
    140         }
    141     }
    142 
    143     /**
    144      * Adds the given session to the ID-based index if the index has already
    145      * been initialized.
    146      */
    147     private void indexById(SSLSession session) {
    148         if (sessionsById != null) {
    149             sessionsById.put(new ByteArray(session.getId()), session);
    150         }
    151     }
    152 
    153     /**
    154      * Finds a cached session for the given host name and port.
    155      *
    156      * @param host of server
    157      * @param port of server
    158      * @return cached session or null if none found
    159      */
    160     public SSLSession getSession(String host, int port) {
    161         synchronized (sessions) {
    162             SSLSession session = sessions.get(new HostAndPort(host, port));
    163             if (session != null) {
    164                 return session;
    165             }
    166         }
    167 
    168         // Look in persistent cache.
    169         if (persistentCache != null) {
    170             byte[] data = persistentCache.getSessionData(host, port);
    171             if (data != null) {
    172                 SSLSession session = toSession(data, host, port);
    173                 if (session != null) {
    174                     synchronized (sessions) {
    175                         sessions.put(new HostAndPort(host, port), session);
    176                         indexById(session);
    177                     }
    178                     return session;
    179                 }
    180             }
    181         }
    182 
    183         return null;
    184     }
    185 
    186     void putSession(SSLSession session) {
    187         HostAndPort key = new HostAndPort(session.getPeerHost(),
    188                 session.getPeerPort());
    189         synchronized (sessions) {
    190             sessions.put(key, session);
    191             indexById(session);
    192         }
    193 
    194         // TODO: This in a background thread.
    195         if (persistentCache != null) {
    196             byte[] data = toBytes(session);
    197             if (data != null) {
    198                 persistentCache.putSessionData(session, data);
    199             }
    200         }
    201     }
    202 
    203     static class HostAndPort {
    204         final String host;
    205         final int port;
    206 
    207         HostAndPort(String host, int port) {
    208             this.host = host;
    209             this.port = port;
    210         }
    211 
    212         @Override
    213         public int hashCode() {
    214             return host.hashCode() * 31 + port;
    215         }
    216 
    217         @Override
    218         @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
    219         public boolean equals(Object o) {
    220             HostAndPort other = (HostAndPort) o;
    221             return host.equals(other.host) && port == other.port;
    222         }
    223     }
    224 }
    225