/* * CCNx Android Services * * Copyright (C) 2010, 2011 Palo Alto Research Center, Inc. * * This work is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * This work is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ package org.ccnx.android.services.ccnd; import java.io.File; import java.io.FileOutputStream; import java.util.Map.Entry; import java.util.HashMap; import org.ccnx.android.ccnlib.CCNxServiceStatus.SERVICE_STATUS; import org.ccnx.android.ccnlib.CcndWrapper.CCND_OPTIONS; import org.ccnx.android.services.CCNxService; import org.ccnx.ccn.impl.security.keys.BasicKeyManager; import org.ccnx.android.ccnlib.CCNxLibraryCheck; import android.content.Context; import android.content.SharedPreferences; import android.content.Intent; import android.util.Log; /** * CCNxService specialization for ccnd. * * The CCND keystore directory is created MODE_PRIVATE. This only * works if it is on the internal storage, not an sdcard. * * The ccnd unix domain socket is created in the keystore directory, so * obviously that cannot be used with MODE_PRIVATE. At the present, * nothing using the unix domain socket on Android. */ public final class CcndService extends CCNxService { public static final String CLASS_TAG = "CCNxCCNdService"; private String KEYSTORE_NAME = ".ccnd_keystore_"; private final static char [] KEYSTORE_PASS = "\010\043\103\375\327\237\152\351\155".toCharArray(); private final static String OPTION_CCND_CAP_DEFAULT = "500"; private final static String OPTION_CCN_PORT_DEFAULT = "9695"; protected static final String [] libs = { "controller" }; public CcndService(){ TAG=CLASS_TAG; // make sure libraries are loaded try { for( int i = 0; i < libs.length; i++ ) { System.loadLibrary(libs[i]); } } catch(Throwable e) { // Need to clean this up to catch each exception, may be that we can recover or handle effectively // Why in the world would we not handle this? Nothing will work if we have a runtime // exception loading libraries e.printStackTrace(); } } protected void onStartService(Intent intent) { Log.d(TAG, "Starting"); boolean isPrefSet = false; // Get all the CCND options from the intent // If no option is found on intent, look in System properties // If no system property is set, fallback to preferences // And while settings OPTIONS, set preferences SharedPreferences.Editor prefsEditor = mCCNxServicePrefs.edit(); if (intent != null) { for( CCND_OPTIONS opt : CCND_OPTIONS.values() ) { if(! intent.hasExtra(opt.name())){ continue; } String s = intent.getStringExtra( opt.name() ); if( null == s ) s = System.getProperty(opt.name()); Log.d(TAG,"setting option " + opt.name() + " = " + s); if( s != null ) { options.put(opt.name(), s); isPrefSet = true; prefsEditor.putString(opt.name(), s); } } if (isPrefSet) { prefsEditor.commit(); } } else { // We must load options from prefs options = new HashMap<String, String>((HashMap<String, String>)mCCNxServicePrefs.getAll()); } Load(); } public void runService(){ setStatus(SERVICE_STATUS.SERVICE_INITIALIZING); String ccnd_port = options.get(CCND_OPTIONS.CCN_LOCAL_PORT.name()); if( ccnd_port == null ) { ccnd_port = OPTION_CCN_PORT_DEFAULT; options.put(CCND_OPTIONS.CCN_LOCAL_PORT.name(), ccnd_port); } Log.d(TAG,CCND_OPTIONS.CCN_LOCAL_PORT.name() + " = " + options.get(CCND_OPTIONS.CCN_LOCAL_PORT.name())); String ccnd_keydir = options.get(CCND_OPTIONS.CCND_KEYSTORE_DIRECTORY.name()); if( ccnd_keydir == null ) { File f = getDir("ccnd", Context.MODE_PRIVATE ); ccnd_keydir = f.getAbsolutePath(); options.put(CCND_OPTIONS.CCND_KEYSTORE_DIRECTORY.name(), ccnd_keydir); } if(options.get(CCND_OPTIONS.CCND_CAP.name()) == null) { options.put(CCND_OPTIONS.CCND_CAP.name(), OPTION_CCND_CAP_DEFAULT); } if(options.get(CCND_OPTIONS.CCN_LOCAL_SOCKNAME.name()) == null) { options.put(CCND_OPTIONS.CCN_LOCAL_SOCKNAME.name(), ccnd_keydir + "/ccnd.sock"); } dumpOptions(); try { createKeystore(ccnd_keydir, KEYSTORE_NAME + ccnd_port); for( Entry<String,String> entry : options.entrySet() ) { setenv(entry.getKey(), entry.getValue(), 1); } // Shouldn't we check to see that we aren't already running before we run? ccndCreate(); try { setStatus(SERVICE_STATUS.SERVICE_RUNNING); ccndRun(); } catch(RuntimeException rte) { Log.e(TAG, "RuntimeException while starting up CcndService"); } finally { ccndDestroy(); } } catch(Exception e) { e.printStackTrace(); Log.d(TAG, "Exception caught while starting up/shutting down. Reason: " + e.getMessage()); setStatus(SERVICE_STATUS.SERVICE_ERROR); // returning will end the thread } serviceStopped(); } protected void createKeystore(String dir_name, String keystore_name) { File dir = new File(dir_name); // This is to get a keystore file // Does dir/.ccnd_keystore_xxx exist? File try_keystore = new File(dir, keystore_name); if( try_keystore.exists() ) { Log.d(TAG, "Keystore Exists! " + try_keystore.getAbsolutePath()); return; } Log.d(TAG,"Creating Keystore @ " + try_keystore.getAbsolutePath()); try { // // In order to give us a chance to properly report service state avoid a mess on // subsequent attempts to start up this service (manual or via intent), we should CCNxLibraryCheck.checkBCP(); FileOutputStream stream = new FileOutputStream(try_keystore); BasicKeyManager.createKeyStore(stream, null, "ccnd", KEYSTORE_PASS, "CCND"); stream.close(); } catch(RuntimeException rte) { // There are a few which can fail and this makes the service unstable since // subsequent service invocations will find the keystore, partially baked, return, and later crash, // can cause dependent applications. Handle this by: // 0. Log it // 1. Delete any keystore cruft // 2. set error status (do not pass go) // 3. Rethrow so this can bubble up properly // We may find there are outher errors that warrant handling Log.e(TAG, "Error loading class. Reason: " + rte.getMessage()); if (try_keystore.exists()) { Log.d(TAG, "Deleting existing keystore"); try_keystore.delete(); } setStatus(SERVICE_STATUS.SERVICE_ERROR); // We want to throw this so that runService() gets a chance to handle and close down throw rte; } catch(Exception e) { // Need to clean this up to catch each exception, may be that we can recover or handle effectively // What other exceptions do we see? Should just deal with each case // 1. File Access/Permission Denied e.printStackTrace(); Log.d(TAG, "Exception while creating keystore. Reason: " + e.getMessage()); setStatus(SERVICE_STATUS.SERVICE_ERROR); } } protected void stopService(){ setStatus(SERVICE_STATUS.SERVICE_TEARING_DOWN); kill(); setStatus(SERVICE_STATUS.SERVICE_FINISHED); } /* ************************************************************* */ protected native void ccndCreate(); protected native void ccndRun(); protected native void ccndDestroy(); protected native void kill(); protected native void setenv(String key, String value, int overwrite); }