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