/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.core; import android.test.AndroidTestCase; import android.os.Debug; import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache; import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.conn.SingleClientConnManager; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.ResponseHandler; import org.apache.http.client.ClientProtocolException; import org.apache.http.HttpResponse; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.security.cert.Certificate; import java.security.Principal; import java.security.KeyManagementException; import java.util.Arrays; public class SSLPerformanceTest extends AndroidTestCase { static final byte[] SESSION_DATA = new byte[6000]; static { for (int i = 0; i < SESSION_DATA.length; i++) { SESSION_DATA[i] = (byte) i; } } static final File dataDir = new File("/data/data/android.core/"); static final File filesDir = new File(dataDir, "files"); static final File dbDir = new File(dataDir, "databases"); static final String CACHE_DIR = SSLPerformanceTest.class.getName() + "/cache"; static final int ITERATIONS = 10; public void testCreateNewEmptyDatabase() { deleteDatabase(); Stopwatch stopwatch = new Stopwatch(); DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); cache.getSessionData("crazybob.org", 443); stopwatch.stop(); } public void testCreateNewEmptyDirectory() throws IOException { deleteDirectory(); Stopwatch stopwatch = new Stopwatch(); SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( getCacheDirectory()); cache.getSessionData("crazybob.org", 443); stopwatch.stop(); } public void testOpenDatabaseWith10Sessions() { deleteDatabase(); DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); putSessionsIn(cache); closeDatabase(); System.err.println("Size of ssl_sessions.db w/ 10 sessions: " + new File(dbDir, "ssl_sessions.db").length()); Stopwatch stopwatch = new Stopwatch(); cache = new DatabaseSessionCache(getContext()); cache.getSessionData("crazybob.org", 443); stopwatch.stop(); } public void testOpenDirectoryWith10Sessions() throws IOException { deleteDirectory(); SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( getCacheDirectory()); putSessionsIn(cache); closeDirectoryCache(); Stopwatch stopwatch = new Stopwatch(); cache = FileClientSessionCache.usingDirectory( getCacheDirectory()); cache.getSessionData("crazybob.org", 443); stopwatch.stop(); } public void testGetSessionFromDatabase() { deleteDatabase(); DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); cache.putSessionData(new FakeSession("foo"), SESSION_DATA); closeDatabase(); cache = new DatabaseSessionCache(getContext()); cache.getSessionData("crazybob.org", 443); Stopwatch stopwatch = new Stopwatch(); byte[] sessionData = cache.getSessionData("foo", 443); stopwatch.stop(); assertTrue(Arrays.equals(SESSION_DATA, sessionData)); } public void testGetSessionFromDirectory() throws IOException { deleteDirectory(); SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( getCacheDirectory()); cache.putSessionData(new FakeSession("foo"), SESSION_DATA); closeDirectoryCache(); cache = FileClientSessionCache.usingDirectory( getCacheDirectory()); cache.getSessionData("crazybob.org", 443); Stopwatch stopwatch = new Stopwatch(); byte[] sessionData = cache.getSessionData("foo", 443); stopwatch.stop(); assertTrue(Arrays.equals(SESSION_DATA, sessionData)); } public void testPutSessionIntoDatabase() { deleteDatabase(); DatabaseSessionCache cache = new DatabaseSessionCache(getContext()); cache.getSessionData("crazybob.org", 443); Stopwatch stopwatch = new Stopwatch(); cache.putSessionData(new FakeSession("foo"), SESSION_DATA); stopwatch.stop(); } public void testPutSessionIntoDirectory() throws IOException { deleteDirectory(); SSLClientSessionCache cache = FileClientSessionCache.usingDirectory( getCacheDirectory()); cache.getSessionData("crazybob.org", 443); Stopwatch stopwatch = new Stopwatch(); cache.putSessionData(new FakeSession("foo"), SESSION_DATA); stopwatch.stop(); } public void testEngineInit() throws IOException, KeyManagementException { Stopwatch stopwatch = new Stopwatch(); new SSLContextImpl().engineInit(null, null, null); stopwatch.stop(); } public void testWebRequestWithoutCache() throws IOException, KeyManagementException { SSLContextImpl sslContext = new SSLContextImpl(); sslContext.engineInit(null, null, null); Stopwatch stopwatch = new Stopwatch(); getVerisignDotCom(sslContext); stopwatch.stop(); } public void testWebRequestWithFileCache() throws IOException, KeyManagementException { deleteDirectory(); SSLContextImpl sslContext = new SSLContextImpl(); sslContext.engineInit(null, null, null, FileClientSessionCache.usingDirectory(getCacheDirectory()), null); // Make sure www.google.com is in the cache. getVerisignDotCom(sslContext); // Re-initialize so we hit the file cache. sslContext.engineInit(null, null, null, FileClientSessionCache.usingDirectory(getCacheDirectory()), null); Stopwatch stopwatch = new Stopwatch(); getVerisignDotCom(sslContext); stopwatch.stop(); } public void testWebRequestWithInMemoryCache() throws IOException, KeyManagementException { deleteDirectory(); SSLContextImpl sslContext = new SSLContextImpl(); sslContext.engineInit(null, null, null); // Make sure www.google.com is in the cache. getVerisignDotCom(sslContext); Stopwatch stopwatch = new Stopwatch(); getVerisignDotCom(sslContext); stopwatch.stop(); } private void getVerisignDotCom(SSLContextImpl sslContext) throws IOException { SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("https", new SSLSocketFactory(sslContext.engineGetSocketFactory()), 443)); ClientConnectionManager manager = new SingleClientConnManager(null, schemeRegistry); new DefaultHttpClient(manager, null).execute( new HttpGet("https://www.verisign.com"), new ResponseHandler<Object>() { public Object handleResponse(HttpResponse response) throws ClientProtocolException, IOException { return null; } }); } private void putSessionsIn(SSLClientSessionCache cache) { for (int i = 0; i < 10; i++) { cache.putSessionData(new FakeSession("host" + i), SESSION_DATA); } } private void deleteDatabase() { closeDatabase(); if (!new File(dbDir, "ssl_sessions.db").delete()) { System.err.println("Failed to delete database."); } } private void closeDatabase() { if (DatabaseSessionCache.sDefaultDatabaseHelper != null) { DatabaseSessionCache.sDefaultDatabaseHelper.close(); } DatabaseSessionCache.sDefaultDatabaseHelper = null; DatabaseSessionCache.sHookInitializationDone = false; DatabaseSessionCache.mNeedsCacheLoad = true; } private void deleteDirectory() { closeDirectoryCache(); File dir = getCacheDirectory(); if (!dir.exists()) { return; } for (File file : dir.listFiles()) { file.delete(); } if (!dir.delete()) { System.err.println("Failed to delete directory."); } } private void closeDirectoryCache() { try { Method reset = FileClientSessionCache.class .getDeclaredMethod("reset"); reset.setAccessible(true); reset.invoke(null); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } private File getCacheDirectory() { return new File(getContext().getFilesDir(), CACHE_DIR); } class Stopwatch { { Debug.startAllocCounting(); } long start = System.nanoTime(); void stop() { long elapsed = (System.nanoTime() - start) / 1000; Debug.stopAllocCounting(); System.err.println(getName() + ": " + elapsed + "us, " + Debug.getThreadAllocCount() + " allocations, " + Debug.getThreadAllocSize() + " bytes"); } } } class FakeSession implements SSLSession { final String host; FakeSession(String host) { this.host = host; } public int getApplicationBufferSize() { throw new UnsupportedOperationException(); } public String getCipherSuite() { throw new UnsupportedOperationException(); } public long getCreationTime() { throw new UnsupportedOperationException(); } public byte[] getId() { return host.getBytes(); } public long getLastAccessedTime() { throw new UnsupportedOperationException(); } public Certificate[] getLocalCertificates() { throw new UnsupportedOperationException(); } public Principal getLocalPrincipal() { throw new UnsupportedOperationException(); } public int getPacketBufferSize() { throw new UnsupportedOperationException(); } public javax.security.cert.X509Certificate[] getPeerCertificateChain() { throw new UnsupportedOperationException(); } public Certificate[] getPeerCertificates() { throw new UnsupportedOperationException(); } public String getPeerHost() { return host; } public int getPeerPort() { return 443; } public Principal getPeerPrincipal() { throw new UnsupportedOperationException(); } public String getProtocol() { throw new UnsupportedOperationException(); } public SSLSessionContext getSessionContext() { throw new UnsupportedOperationException(); } public Object getValue(String name) { throw new UnsupportedOperationException(); } public String[] getValueNames() { throw new UnsupportedOperationException(); } public void invalidate() { throw new UnsupportedOperationException(); } public boolean isValid() { throw new UnsupportedOperationException(); } public void putValue(String name, Object value) { throw new UnsupportedOperationException(); } public void removeValue(String name) { throw new UnsupportedOperationException(); } }