FileDocCategorySizeDatePackage
DatabaseSessionCache.javaAPI DocAndroid 1.5 API11227Wed May 06 22:42:02 BST 2009android.core

DatabaseSessionCache

public class DatabaseSessionCache extends Object implements org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache
Hook into harmony SSL cache to persist the SSL sessions. Current implementation is suitable for saving a small number of hosts - like google services. It can be extended with expiration and more features to support more hosts. {@hide}

Fields Summary
private static final String
TAG
static DatabaseHelper
sDefaultDatabaseHelper
private DatabaseHelper
mDatabaseHelper
public static final String
SSL_CACHE_TABLE
Table where sessions are stored.
private static final String
SSL_CACHE_ID
private static final String
SSL_CACHE_HOSTPORT
Key is host:port - port is not optional.
private static final String
SSL_CACHE_SESSION
Base64-encoded DER value of the session.
private static final String
SSL_CACHE_TIME_SEC
Time when the record was added - should be close to the time of the initial session negotiation.
public static final String
DATABASE_NAME
public static final int
DATABASE_VERSION
public static final int
SSL_CACHE_ID_COL
public for testing
public static final int
SSL_CACHE_HOSTPORT_COL
public static final int
SSL_CACHE_SESSION_COL
public static final int
SSL_CACHE_TIME_SEC_COL
private static final String
SAVE_ON_ADD
static boolean
sHookInitializationDone
public static final int
MAX_CACHE_SIZE
private static final Map
mExternalCache
static boolean
mNeedsCacheLoad
public static final String[]
PROJECTION
Constructors Summary
public DatabaseSessionCache()
This class needs to be installed as a hook, if the security property is set. Getting the right classloader may be fun since we don't use Provider to get its classloader, but in android this is in same loader with AndroidHttpClient. This constructor will use the default database. You must call init() before to specify the context used for the database and check settings.


                                                                         
      
        Log.v(TAG, "Instance created.");
        // May be null if caching is disabled - no sessions will be persisted.
        this.mDatabaseHelper = sDefaultDatabaseHelper;
    
public DatabaseSessionCache(android.content.Context activityContext)
Create a SslSessionCache instance, using the specified context to initialize the database. This constructor will use the default database - created the first time.

param
activityContext

        // Static init - only one initialization will happen.
        // Each SslSessionCache is using the same DB.
        init(activityContext);
        // May be null if caching is disabled - no sessions will be persisted.
        this.mDatabaseHelper = sDefaultDatabaseHelper;
    
public DatabaseSessionCache(DatabaseHelper database)
Create a SslSessionCache that uses a specific database.

param
database

        this.mDatabaseHelper = database;
    
Methods Summary
public byte[]getSessionData(java.lang.String host, int port)

        // Current (simple) implementation does a single lookup to DB, then saves
        // all entries to the cache.

        // This works for google services - i.e. small number of certs.
        // If we extend this to all processes - we should hold a separate cache
        // or do lookups to DB each time.
        if (mDatabaseHelper == null) {
            return null;
        }
        synchronized(this.getClass()) {
            if (mNeedsCacheLoad) {
                // Don't try to load again, if something is wrong on the first
                // request it'll likely be wrong each time.
                mNeedsCacheLoad = false;
                long t0 = System.currentTimeMillis();

                Cursor cur = null;
                try {
                    cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, PROJECTION, null,
                            null, null, null, null);
                    if (cur.moveToFirst()) {
                        do {
                            String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
                            String value = cur.getString(SSL_CACHE_SESSION_COL);

                            if (hostPort == null || value == null) {
                                continue;
                            }
                            // TODO: blob support ?
                            byte[] der = Base64.decodeBase64(value.getBytes());
                            mExternalCache.put(hostPort, der);
                        } while (cur.moveToNext());

                    }
                } catch (SQLException ex) {
                    Log.d(TAG, "Error loading SSL cached entries ", ex);
                } finally {
                    if (cur != null) {
                        cur.close();
                    }
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        long t1 = System.currentTimeMillis();
                        Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
                    }
                }
            }

            String key = host + ":" + port;

            return mExternalCache.get(key);
        }
    
public byte[]getSessionData(byte[] id)

        // We support client side only - the cache will do nothing on client.
        return null;
    
public static synchronized voidinit(android.content.Context activityContext)
You must call this method to enable SSL session caching for an app.

        // It is possible that multiple provider will try to install this hook.
        // We want a single db per VM.
        if (sHookInitializationDone) {
            return;
        }


//        // More values can be added in future to provide different
//        // behaviours, like 'batch save'.
//        if (enabled(activityContext)) {
            Context appContext = activityContext.getApplicationContext();
            sDefaultDatabaseHelper = new DatabaseHelper(appContext);

            // Set default SSLSocketFactory
            // The property is defined in the javadocs for javax.net.SSLSocketFactory
            // (no constant defined there)
            // This should cover all code using SSLSocketFactory.getDefault(),
            // including native http client and apache httpclient.
            // MCS is using its own custom factory - will need special code.
//            Security.setProperty("ssl.SocketFactory.provider",
//                    SslSocketFactoryWithCache.class.getName());
//        }

        // Won't try again.
        sHookInitializationDone = true;
    
public voidputSessionData(javax.net.ssl.SSLSession session, byte[] der)

        if (mDatabaseHelper == null) {
            return;
        }
        if (mExternalCache.size() > MAX_CACHE_SIZE) {
            // remove oldest.
            Cursor byTime = mDatabaseHelper.getWritableDatabase().query(SSL_CACHE_TABLE,
                    PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
            byTime.moveToFirst();
            // TODO: can I do byTime.deleteRow() ?
            String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);

            mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE,
                    SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
        }
        // Serialize native session to standard DER encoding
        long t0 = System.currentTimeMillis();

        String b64 = new String(Base64.encodeBase64(der));
        String key = session.getPeerHost() + ":" + session.getPeerPort();

        ContentValues values = new ContentValues();
        values.put(SSL_CACHE_HOSTPORT, key);
        values.put(SSL_CACHE_SESSION, b64);
        values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);

        synchronized (this.getClass()) {
            mExternalCache.put(key, der);

            try {
                mDatabaseHelper.getWritableDatabase().insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
            } catch(SQLException ex) {
                // Ignore - nothing we can do to recover, and caller shouldn't
                // be affected.
                Log.w(TAG, "Ignoring SQL exception when caching session", ex);
            }
        }
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            long t1 = System.currentTimeMillis();
            Log.d(TAG, "New SSL session " + session.getPeerHost() +
                    " DER len: " + der.length + " " + (t1 - t0));
        }