1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.core;
18
19import android.test.AndroidTestCase;
20import android.os.Debug;
21import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
22import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
23import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
24import org.apache.http.conn.scheme.SchemeRegistry;
25import org.apache.http.conn.scheme.Scheme;
26import org.apache.http.conn.ClientConnectionManager;
27import org.apache.http.conn.ssl.SSLSocketFactory;
28import org.apache.http.impl.conn.SingleClientConnManager;
29import org.apache.http.impl.client.DefaultHttpClient;
30import org.apache.http.client.methods.HttpGet;
31import org.apache.http.client.ResponseHandler;
32import org.apache.http.client.ClientProtocolException;
33import org.apache.http.HttpResponse;
34
35import javax.net.ssl.SSLSession;
36import javax.net.ssl.SSLSessionContext;
37import java.io.File;
38import java.io.IOException;
39import java.lang.reflect.Method;
40import java.lang.reflect.InvocationTargetException;
41import java.security.cert.Certificate;
42import java.security.Principal;
43import java.security.KeyManagementException;
44import java.util.Arrays;
45
46public class SSLPerformanceTest extends AndroidTestCase {
47
48    static final byte[] SESSION_DATA = new byte[6000];
49    static {
50        for (int i = 0; i < SESSION_DATA.length; i++) {
51            SESSION_DATA[i] = (byte) i;
52        }
53    }
54
55    static final File dataDir = new File("/data/data/android.core/");
56    static final File filesDir = new File(dataDir, "files");
57    static final File dbDir = new File(dataDir, "databases");
58
59    static final String CACHE_DIR
60            = SSLPerformanceTest.class.getName() + "/cache";
61
62    static final int ITERATIONS = 10;
63
64    public void testCreateNewEmptyDatabase() {
65        deleteDatabase();
66
67        Stopwatch stopwatch = new Stopwatch();
68
69        DatabaseSessionCache cache = new DatabaseSessionCache(getContext());
70        cache.getSessionData("crazybob.org", 443);
71
72        stopwatch.stop();
73    }
74
75    public void testCreateNewEmptyDirectory() throws IOException {
76        deleteDirectory();
77
78        Stopwatch stopwatch = new Stopwatch();
79
80        SSLClientSessionCache cache = FileClientSessionCache.usingDirectory(
81                getCacheDirectory());
82        cache.getSessionData("crazybob.org", 443);
83
84        stopwatch.stop();
85    }
86
87    public void testOpenDatabaseWith10Sessions() {
88        deleteDatabase();
89
90        DatabaseSessionCache cache = new DatabaseSessionCache(getContext());
91        putSessionsIn(cache);
92        closeDatabase();
93
94        System.err.println("Size of ssl_sessions.db w/ 10 sessions: "
95                + new File(dbDir, "ssl_sessions.db").length());
96
97        Stopwatch stopwatch = new Stopwatch();
98
99        cache = new DatabaseSessionCache(getContext());
100        cache.getSessionData("crazybob.org", 443);
101
102        stopwatch.stop();
103    }
104
105    public void testOpenDirectoryWith10Sessions() throws IOException {
106        deleteDirectory();
107
108        SSLClientSessionCache cache = FileClientSessionCache.usingDirectory(
109                getCacheDirectory());
110        putSessionsIn(cache);
111        closeDirectoryCache();
112
113        Stopwatch stopwatch = new Stopwatch();
114
115        cache = FileClientSessionCache.usingDirectory(
116                getCacheDirectory());
117        cache.getSessionData("crazybob.org", 443);
118
119        stopwatch.stop();
120    }
121
122    public void testGetSessionFromDatabase() {
123        deleteDatabase();
124
125        DatabaseSessionCache cache = new DatabaseSessionCache(getContext());
126        cache.putSessionData(new FakeSession("foo"), SESSION_DATA);
127        closeDatabase();
128
129        cache = new DatabaseSessionCache(getContext());
130        cache.getSessionData("crazybob.org", 443);
131
132        Stopwatch stopwatch = new Stopwatch();
133
134        byte[] sessionData = cache.getSessionData("foo", 443);
135
136        stopwatch.stop();
137
138        assertTrue(Arrays.equals(SESSION_DATA, sessionData));
139    }
140
141    public void testGetSessionFromDirectory() throws IOException {
142        deleteDirectory();
143
144        SSLClientSessionCache cache = FileClientSessionCache.usingDirectory(
145                getCacheDirectory());
146        cache.putSessionData(new FakeSession("foo"), SESSION_DATA);
147        closeDirectoryCache();
148
149        cache = FileClientSessionCache.usingDirectory(
150                getCacheDirectory());
151        cache.getSessionData("crazybob.org", 443);
152
153        Stopwatch stopwatch = new Stopwatch();
154
155        byte[] sessionData = cache.getSessionData("foo", 443);
156
157        stopwatch.stop();
158
159        assertTrue(Arrays.equals(SESSION_DATA, sessionData));
160    }
161
162    public void testPutSessionIntoDatabase() {
163        deleteDatabase();
164
165        DatabaseSessionCache cache = new DatabaseSessionCache(getContext());
166        cache.getSessionData("crazybob.org", 443);
167
168        Stopwatch stopwatch = new Stopwatch();
169
170        cache.putSessionData(new FakeSession("foo"), SESSION_DATA);
171
172        stopwatch.stop();
173    }
174
175    public void testPutSessionIntoDirectory() throws IOException {
176        deleteDirectory();
177
178        SSLClientSessionCache cache = FileClientSessionCache.usingDirectory(
179                getCacheDirectory());
180        cache.getSessionData("crazybob.org", 443);
181
182        Stopwatch stopwatch = new Stopwatch();
183
184        cache.putSessionData(new FakeSession("foo"), SESSION_DATA);
185
186        stopwatch.stop();
187    }
188
189    public void testEngineInit() throws IOException, KeyManagementException {
190        Stopwatch stopwatch = new Stopwatch();
191
192        new OpenSSLContextImpl().engineInit(null, null, null);
193
194        stopwatch.stop();
195    }
196
197    public void testWebRequestWithoutCache() throws IOException,
198            KeyManagementException {
199        OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
200        sslContext.engineInit(null, null, null);
201
202        Stopwatch stopwatch = new Stopwatch();
203
204        getVerisignDotCom(sslContext);
205
206        stopwatch.stop();
207    }
208
209    public void testWebRequestWithFileCache() throws IOException,
210            KeyManagementException {
211        deleteDirectory();
212
213        OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
214        sslContext.engineInit(null, null, null);
215        sslContext.engineGetClientSessionContext().setPersistentCache(
216                FileClientSessionCache.usingDirectory(getCacheDirectory()));
217
218        // Make sure www.google.com is in the cache.
219        getVerisignDotCom(sslContext);
220
221        // Re-initialize so we hit the file cache.
222        sslContext.engineInit(null, null, null);
223        sslContext.engineGetClientSessionContext().setPersistentCache(
224                FileClientSessionCache.usingDirectory(getCacheDirectory()));
225
226        Stopwatch stopwatch = new Stopwatch();
227
228        getVerisignDotCom(sslContext);
229
230        stopwatch.stop();
231    }
232
233    public void testWebRequestWithInMemoryCache() throws IOException,
234            KeyManagementException {
235        deleteDirectory();
236
237        OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
238        sslContext.engineInit(null, null, null);
239
240        // Make sure www.google.com is in the cache.
241        getVerisignDotCom(sslContext);
242
243        Stopwatch stopwatch = new Stopwatch();
244
245        getVerisignDotCom(sslContext);
246
247        stopwatch.stop();
248    }
249
250    private void getVerisignDotCom(OpenSSLContextImpl sslContext)
251            throws IOException {
252        SchemeRegistry schemeRegistry = new SchemeRegistry();
253        schemeRegistry.register(new Scheme("https",
254                new SSLSocketFactory(sslContext.engineGetSocketFactory()),
255                443));
256
257        ClientConnectionManager manager =
258                new SingleClientConnManager(null, schemeRegistry);
259
260        new DefaultHttpClient(manager, null).execute(
261                new HttpGet("https://www.verisign.com"),
262                new ResponseHandler<Object>() {
263                    public Object handleResponse(HttpResponse response)
264                            throws ClientProtocolException, IOException {
265                        return null;
266                    }
267                });
268    }
269
270    private void putSessionsIn(SSLClientSessionCache cache) {
271        for (int i = 0; i < 10; i++) {
272            cache.putSessionData(new FakeSession("host" + i), SESSION_DATA);
273        }
274    }
275
276    private void deleteDatabase() {
277        closeDatabase();
278        if (!new File(dbDir, "ssl_sessions.db").delete()) {
279            System.err.println("Failed to delete database.");
280        }
281    }
282
283    private void closeDatabase() {
284        if (DatabaseSessionCache.sDefaultDatabaseHelper != null) {
285            DatabaseSessionCache.sDefaultDatabaseHelper.close();
286        }
287        DatabaseSessionCache.sDefaultDatabaseHelper = null;
288        DatabaseSessionCache.sHookInitializationDone = false;
289        DatabaseSessionCache.mNeedsCacheLoad = true;
290    }
291
292    private void deleteDirectory() {
293        closeDirectoryCache();
294
295        File dir = getCacheDirectory();
296        if (!dir.exists()) {
297            return;
298        }
299        for (File file : dir.listFiles()) {
300            file.delete();
301        }
302        if (!dir.delete()) {
303            System.err.println("Failed to delete directory.");
304        }
305    }
306
307    private void closeDirectoryCache() {
308        try {
309            Method reset = FileClientSessionCache.class
310                    .getDeclaredMethod("reset");
311            reset.setAccessible(true);
312            reset.invoke(null);
313        } catch (NoSuchMethodException e) {
314            throw new RuntimeException(e);
315        } catch (IllegalAccessException e) {
316            throw new RuntimeException(e);
317        } catch (InvocationTargetException e) {
318            throw new RuntimeException(e);
319        }
320    }
321
322    private File getCacheDirectory() {
323        return new File(getContext().getFilesDir(), CACHE_DIR);
324    }
325
326    class Stopwatch {
327        {
328            Debug.startAllocCounting();
329        }
330        long start = System.nanoTime();
331
332        void stop() {
333            long elapsed = (System.nanoTime() - start) / 1000;
334            Debug.stopAllocCounting();
335            System.err.println(getName() + ": " + elapsed + "us, "
336                + Debug.getThreadAllocCount() + " allocations, "
337                + Debug.getThreadAllocSize() + " bytes");
338        }
339    }
340}
341
342class FakeSession implements SSLSession {
343    final String host;
344
345    FakeSession(String host) {
346        this.host = host;
347    }
348
349    public int getApplicationBufferSize() {
350        throw new UnsupportedOperationException();
351    }
352
353    public String getCipherSuite() {
354        throw new UnsupportedOperationException();
355    }
356
357    public long getCreationTime() {
358        throw new UnsupportedOperationException();
359    }
360
361    public byte[] getId() {
362        return host.getBytes();
363    }
364
365    public long getLastAccessedTime() {
366        throw new UnsupportedOperationException();
367    }
368
369    public Certificate[] getLocalCertificates() {
370        throw new UnsupportedOperationException();
371    }
372
373    public Principal getLocalPrincipal() {
374        throw new UnsupportedOperationException();
375    }
376
377    public int getPacketBufferSize() {
378        throw new UnsupportedOperationException();
379    }
380
381    public javax.security.cert.X509Certificate[] getPeerCertificateChain() {
382        throw new UnsupportedOperationException();
383    }
384
385    public Certificate[] getPeerCertificates() {
386        throw new UnsupportedOperationException();
387    }
388
389    public String getPeerHost() {
390        return host;
391    }
392
393    public int getPeerPort() {
394        return 443;
395    }
396
397    public Principal getPeerPrincipal() {
398        throw new UnsupportedOperationException();
399    }
400
401    public String getProtocol() {
402        throw new UnsupportedOperationException();
403    }
404
405    public SSLSessionContext getSessionContext() {
406        throw new UnsupportedOperationException();
407    }
408
409    public Object getValue(String name) {
410        throw new UnsupportedOperationException();
411    }
412
413    public String[] getValueNames() {
414        throw new UnsupportedOperationException();
415    }
416
417    public void invalidate() {
418        throw new UnsupportedOperationException();
419    }
420
421    public boolean isValid() {
422        throw new UnsupportedOperationException();
423    }
424
425    public void putValue(String name, Object value) {
426        throw new UnsupportedOperationException();
427    }
428
429    public void removeValue(String name) {
430        throw new UnsupportedOperationException();
431    }
432}
433