108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project/*
208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * Copyright (C) 2009 The Android Open Source Project
308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project *
408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * you may not use this file except in compliance with the License.
608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * You may obtain a copy of the License at
708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project *
808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project *
1008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * See the License for the specific language governing permissions and
1408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * limitations under the License.
1508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project */
1608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
17860d2707ce126ef8f66e3eac7ceeab6d24218cd8Kenny Rootpackage org.conscrypt;
1808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
1908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectimport java.io.DataInputStream;
2008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectimport java.io.File;
2108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectimport java.io.FileInputStream;
2208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectimport java.io.FileNotFoundException;
2308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectimport java.io.FileOutputStream;
2408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectimport java.io.IOException;
257a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.Arrays;
267a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.HashMap;
277a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.Iterator;
287a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.LinkedHashMap;
297a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.Map;
307a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.Set;
317a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport java.util.TreeSet;
327a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilsonimport javax.net.ssl.SSLSession;
3308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
3408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project/**
3508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * File-based cache implementation. Only one process should access the
3608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project * underlying directory at a time.
3708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project */
3808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Projectpublic class FileClientSessionCache {
3967ca2b30455d027e830bd09f62cbc03669f08e1aJesse Wilson    public static final int MAX_SIZE = 12; // ~72k
4008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
4108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    private FileClientSessionCache() {}
4208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
4308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    /**
4408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * This cache creates one file per SSL session using "host.port" for
4508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * the file name. Files are created or replaced when session data is put
4608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * in the cache (see {@link #putSessionData}). Files are read on
4708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * cache hits, but not on cache misses.
4808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     *
4908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * <p>When the number of session files exceeds MAX_SIZE, we delete the
5008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * least-recently-used file. We don't current persist the last access time,
5108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * so the ordering actually ends up being least-recently-modified in some
5208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * cases and even just "not accessed in this process" if the filesystem
5308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * doesn't track last modified times.
5408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     */
5508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    static class Impl implements SSLClientSessionCache {
5608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
5708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /** Directory to store session files in. */
5808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        final File directory;
5908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
6008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
6108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * Map of name -> File. Keeps track of the order files were accessed in.
6208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
6308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        Map<String, File> accessOrder = newAccessOrder();
6408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
6508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /** The number of files on disk. */
6608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        int size;
6708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
6808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
6908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * The initial set of files. We use this to defer adding information
7008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * about all files to accessOrder until necessary.
7108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
7208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        String[] initialFiles;
7308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
7408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
7508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * Constructs a new cache backed by the given directory.
7608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
7708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        Impl(File directory) throws IOException {
7808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            boolean exists = directory.exists();
7908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (exists && !directory.isDirectory()) {
80441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                throw new IOException(directory + " exists but is not a directory.");
8108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
8208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
8308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (exists) {
8408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // Read and sort initial list of files. We defer adding
8508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // information about these files to accessOrder until necessary
8608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // (see indexFiles()). Sorting the list enables us to detect
8708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // cache misses in getSessionData().
8808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // Note: Sorting an array here was faster than creating a
8908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // HashSet on Dalvik.
9008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                initialFiles = directory.list();
91441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                if (initialFiles == null) {
92441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                    // File.list() will return null in error cases without throwing IOException
93441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                    // http://b/3363561
94441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                    throw new IOException(directory + " exists but cannot list contents.");
95441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                }
9608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                Arrays.sort(initialFiles);
9708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                size = initialFiles.length;
9808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } else {
9908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // Create directory.
10008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                if (!directory.mkdirs()) {
101441a471b90bccb707862d5f42df58635543eac41Brian Carlstrom                    throw new IOException("Creation of " + directory + " directory failed.");
10208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                }
10308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                size = 0;
10408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
10508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
10608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            this.directory = directory;
10708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
10808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
10908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
11008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * Creates a new access-ordered linked hash map.
11108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
11208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        private static Map<String, File> newAccessOrder() {
11308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            return new LinkedHashMap<String, File>(
11408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    MAX_SIZE, 0.75f, true /* access order */);
11508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
11608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
11708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
11808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * Gets the file name for the given host and port.
11908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
12008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        private static String fileName(String host, int port) {
12108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (host == null) {
122209ed1820719004bcd26d66cc0057f0321e5192eKenny Root                throw new NullPointerException("host == null");
12308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
12408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            return host + "." + port;
12508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
12608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
127f06338c01394610174fe2b3532beac56d61d9e26Kenny Root        @Override
12808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        public synchronized byte[] getSessionData(String host, int port) {
12908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            /*
13008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project             * Note: This method is only called when the in-memory cache
1315006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson             * in SSLSessionContext misses, so it would be unnecessarily
1325006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson             * redundant for this cache to store data in memory.
13308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project             */
13408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
13508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            String name = fileName(host, port);
13608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            File file = accessOrder.get(name);
13708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
13808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (file == null) {
13908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // File wasn't in access order. Check initialFiles...
14008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                if (initialFiles == null) {
14108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    // All files are in accessOrder, so it doesn't exist.
14208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    return null;
14308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                }
14408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
14508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // Look in initialFiles.
14608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                if (Arrays.binarySearch(initialFiles, name) < 0) {
14708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    // Not found.
14808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    return null;
14908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                }
15008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
15108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // The file is on disk but not in accessOrder yet.
15208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                file = new File(directory, name);
15308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                accessOrder.put(name, file);
15408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
15508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
15608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            FileInputStream in;
15708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            try {
15808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                in = new FileInputStream(file);
15908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } catch (FileNotFoundException e) {
1605006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson                logReadError(host, file, e);
16108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                return null;
16208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
16308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            try {
16408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                int size = (int) file.length();
16508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                byte[] data = new byte[size];
16608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                new DataInputStream(in).readFully(data);
16708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                return data;
16808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } catch (IOException e) {
1695006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson                logReadError(host, file, e);
17008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                return null;
17108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } finally {
17247e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                if (in != null) {
17347e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                    try {
17447e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                        in.close();
17547e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                    } catch (RuntimeException rethrown) {
17647e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                        throw rethrown;
17747e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                    } catch (Exception ignored) {
17847e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                    }
17947e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                }
18008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
18108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
18208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
1835006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson        static void logReadError(String host, File file, Throwable t) {
18447e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov            System.err.println("FileClientSessionCache: Error reading session data for " + host + " from " + file + ".");
18547e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov            t.printStackTrace();
18608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
18708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
188f06338c01394610174fe2b3532beac56d61d9e26Kenny Root        @Override
18908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        public synchronized void putSessionData(SSLSession session,
19008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                byte[] sessionData) {
19108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            String host = session.getPeerHost();
19208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (sessionData == null) {
193209ed1820719004bcd26d66cc0057f0321e5192eKenny Root                throw new NullPointerException("sessionData == null");
19408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
19508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
19608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            String name = fileName(host, session.getPeerPort());
19708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            File file = new File(directory, name);
19808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
19908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            // Used to keep track of whether or not we're expanding the cache.
20008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            boolean existedBefore = file.exists();
20108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
20208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            FileOutputStream out;
20308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            try {
20408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                out = new FileOutputStream(file);
20508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } catch (FileNotFoundException e) {
20608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // We can't write to the file.
2075006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson                logWriteError(host, file, e);
20808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                return;
20908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
21008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
21108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            // If we expanded the cache (by creating a new file)...
21208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (!existedBefore) {
21308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                size++;
21408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
21508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // Delete an old file if necessary.
21608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                makeRoom();
21708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
21808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
21908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            boolean writeSuccessful = false;
22008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            try {
22108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                out.write(sessionData);
22208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                writeSuccessful = true;
22308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } catch (IOException e) {
2245006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson                logWriteError(host, file, e);
22508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } finally {
22608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                boolean closeSuccessful = false;
22708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                try {
22808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    out.close();
22908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    closeSuccessful = true;
23008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                } catch (IOException e) {
2315006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson                    logWriteError(host, file, e);
23208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                } finally {
23308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    if (!writeSuccessful || !closeSuccessful) {
23408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                        // Storage failed. Clean up.
23508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                        delete(file);
23608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    } else {
23708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                        // Success!
23808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                        accessOrder.put(name, file);
23908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    }
24008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                }
24108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
24208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
24308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
24408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
24508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * Deletes old files if necessary.
24608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
24708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        private void makeRoom() {
24808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (size <= MAX_SIZE) {
24908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                return;
25008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
25108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
25208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            indexFiles();
25308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
25408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            // Delete LRUed files.
25508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            int removals = size - MAX_SIZE;
25608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            Iterator<File> i = accessOrder.values().iterator();
25708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            do {
25808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                delete(i.next());
25908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                i.remove();
26008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            } while (--removals > 0);
26108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
26208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
26308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        /**
26408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * Lazily updates accessOrder to know about all files as opposed to
26508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         * just the files accessed since this process started.
26608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project         */
26708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        private void indexFiles() {
26808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            String[] initialFiles = this.initialFiles;
26908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (initialFiles != null) {
27008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                this.initialFiles = null;
27108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
27208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // Files on disk only, sorted by last modified time.
27308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                // TODO: Use last access time.
27408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                Set<CacheFile> diskOnly = new TreeSet<CacheFile>();
27508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                for (String name : initialFiles) {
27608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    // If the file hasn't been accessed in this process...
27708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    if (!accessOrder.containsKey(name)) {
27808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                        diskOnly.add(new CacheFile(directory, name));
27908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    }
28008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                }
28108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
28208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                if (!diskOnly.isEmpty()) {
28308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    // Add files not accessed in this process to the beginning
28408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    // of accessOrder.
28508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    Map<String, File> newOrder = newAccessOrder();
28608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    for (CacheFile cacheFile : diskOnly) {
28708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                        newOrder.put(cacheFile.name, cacheFile);
28808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    }
28908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    newOrder.putAll(accessOrder);
29073f1fad27323ed00b318de046cfe43236625af09Elliott Hughes
29108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                    this.accessOrder = newOrder;
29208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                }
29308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
29408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
29508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
29608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        @SuppressWarnings("ThrowableInstanceNeverThrown")
29708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        private void delete(File file) {
29808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (!file.delete()) {
29947e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov                new IOException("FileClientSessionCache: Failed to delete " + file + ".").printStackTrace();
30008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
30108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            size--;
30208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
30308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
3045006f3bedbfd19dc905416bbf28bb0e95807f845Jesse Wilson        static void logWriteError(String host, File file, Throwable t) {
30547e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov            System.err.println("FileClientSessionCache: Error writing session data for " + host + " to " + file + ".");
30647e40a5c56a812ec471f05e9073f86fcceb5563cGeorgi Nikolov            t.printStackTrace();
30708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
30808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    }
30908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
31008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    /**
31108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * Maps directories to the cache instances that are backed by those
31208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * directories. We synchronize access using the cache instance, so it's
31308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * important that everyone shares the same instance.
31408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     */
31508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    static final Map<File, FileClientSessionCache.Impl> caches
31608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            = new HashMap<File, FileClientSessionCache.Impl>();
31708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
31808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    /**
31908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * Returns a cache backed by the given directory. Creates the directory
32008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * (including parent directories) if necessary. This cache should have
32108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * exclusive access to the given directory.
32208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     *
32308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * @param directory to store files in
32408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * @return a cache backed by the given directory
32508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     * @throws IOException if the file exists and is not a directory or if
32608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     *  creating the directories fails
32708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project     */
32808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    public static synchronized SSLClientSessionCache usingDirectory(
32908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            File directory) throws IOException {
33008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        FileClientSessionCache.Impl cache = caches.get(directory);
33108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        if (cache == null) {
33208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            cache = new FileClientSessionCache.Impl(directory);
33308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            caches.put(directory, cache);
33408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
33508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        return cache;
33608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    }
33708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
33808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    /** For testing. */
33908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    static synchronized void reset() {
34008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        caches.clear();
34108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    }
34208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
34308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    /** A file containing a piece of cached data. */
34408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    static class CacheFile extends File {
34508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
34608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        final String name;
34708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
34808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        CacheFile(File dir, String name) {
34908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            super(dir, name);
35008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            this.name = name;
35108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
35208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
35308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        long lastModified = -1;
35408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
35508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        @Override
35608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        public long lastModified() {
35708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            long lastModified = this.lastModified;
35808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (lastModified == -1) {
35908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                lastModified = this.lastModified = super.lastModified();
36008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
36108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            return lastModified;
36208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
36308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project
36408ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        @Override
36508ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        public int compareTo(File another) {
36608ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            // Sort by last modified time.
36708ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            long result = lastModified() - another.lastModified();
36808ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            if (result == 0) {
36908ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project                return super.compareTo(another);
37008ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            }
37108ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project            return result < 0 ? -1 : 1;
37208ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project        }
37308ecc8c0f00f1a7f2258c569187e36606ed73045The Android Open Source Project    }
3747a19bf9f5adcd53b524c0e54c97f7ed9107898cbJesse Wilson}
375