ソースを参照

added openvpn and strongswan files, permissions, service etc .. And project is compiling and build..

Khubaib 11 ヶ月 前
コミット
35aeda481e
100 ファイル変更14860 行追加1 行削除
  1. 53 0
      app/build.gradle.kts
  2. 39 0
      app/src/main/AndroidManifest.xml
  3. 248 0
      app/src/main/aidl/com/vpn/fastestvpnservice/Billing.aidl
  4. 3 0
      app/src/main/aidl/de/blinkt/openvpn/api/APIVpnProfile.aidl
  5. 66 0
      app/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl
  6. 13 0
      app/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNStatusCallback.aidl
  7. 3 0
      app/src/main/aidl/de/blinkt/openvpn/core/ConnectionStatus.aidl
  8. 23 0
      app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl
  9. 36 0
      app/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl
  10. 24 0
      app/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl
  11. 3 0
      app/src/main/aidl/de/blinkt/openvpn/core/LogItem.aidl
  12. 4 0
      app/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl
  13. 15 0
      app/src/main/assets/appDetails/appDetails.json
  14. 8 0
      app/src/main/assets/fileDetails/fileDetails.json
  15. 67 0
      app/src/main/assets/fileDetails/tcp.ovpn
  16. 67 0
      app/src/main/assets/fileDetails/udp.ovpn
  17. BIN
      app/src/main/assets/nopie_openvpn.arm64-v8a
  18. BIN
      app/src/main/assets/nopie_openvpn.armeabi-v7a
  19. BIN
      app/src/main/assets/pie_openvpn.arm64-v8a
  20. BIN
      app/src/main/assets/pie_openvpn.armeabi-v7a
  21. 1 1
      app/src/main/java/com/vpn/fastestvpnservice/beans/Server.kt
  22. 45 0
      app/src/main/java/com/vpn/fastestvpnservice/openVpnUtils/Crypto.java
  23. 14 0
      app/src/main/java/com/vpn/fastestvpnservice/openVpnUtils/Data.java
  24. 15 0
      app/src/main/java/com/vpn/fastestvpnservice/openVpnUtils/EncryptData.java
  25. 291 0
      app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
  26. 874 0
      app/src/main/java/de/blinkt/openvpn/VpnProfile.java
  27. 49 0
      app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java
  28. 101 0
      app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java
  29. 52 0
      app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java
  30. 343 0
      app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java
  31. 27 0
      app/src/main/java/de/blinkt/openvpn/api/GrantPermissionsActivity.java
  32. 13 0
      app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java
  33. 286 0
      app/src/main/java/de/blinkt/openvpn/core/App.java
  34. 59 0
      app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java
  35. 698 0
      app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
  36. 54 0
      app/src/main/java/de/blinkt/openvpn/core/Connection.java
  37. 41 0
      app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java
  38. 219 0
      app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java
  39. 200 0
      app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java
  40. 316 0
      app/src/main/java/de/blinkt/openvpn/core/LogItem.java
  41. 45 0
      app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java
  42. 21 0
      app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
  43. 294 0
      app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
  44. 29 0
      app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
  45. 1141 0
      app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
  46. 195 0
      app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java
  47. 189 0
      app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
  48. 549 0
      app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
  49. 298 0
      app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java
  50. 50 0
      app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java
  51. 20 0
      app/src/main/java/de/blinkt/openvpn/core/Preferences.java
  52. 214 0
      app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
  53. 52 0
      app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java
  54. 87 0
      app/src/main/java/de/blinkt/openvpn/core/StatusListener.java
  55. 191 0
      app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java
  56. 123 0
      app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
  57. 801 0
      app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
  58. 179 0
      app/src/main/java/de/blinkt/openvpn/core/X509Utils.java
  59. 89 0
      app/src/main/java/org/spongycastle/util/encoders/Base64.java
  60. 195 0
      app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java
  61. 20 0
      app/src/main/java/org/spongycastle/util/encoders/Encoder.java
  62. 25 0
      app/src/main/java/org/spongycastle/util/pem/PemGenerationException.java
  63. 52 0
      app/src/main/java/org/spongycastle/util/pem/PemHeader.java
  64. 56 0
      app/src/main/java/org/spongycastle/util/pem/PemObject.java
  65. 9 0
      app/src/main/java/org/spongycastle/util/pem/PemObjectGenerator.java
  66. 62 0
      app/src/main/java/org/spongycastle/util/pem/PemReader.java
  67. 103 0
      app/src/main/java/org/spongycastle/util/pem/PemWriter.java
  68. 225 0
      app/src/main/java/org/strongswan/android/data/Server.java
  69. 364 0
      app/src/main/java/org/strongswan/android/data/VpnProfile.java
  70. 508 0
      app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
  71. 94 0
      app/src/main/java/org/strongswan/android/data/VpnType.java
  72. 1400 0
      app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
  73. 125 0
      app/src/main/java/org/strongswan/android/logic/NetworkManager.java
  74. 165 0
      app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java
  75. 74 0
      app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
  76. 262 0
      app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java
  77. 630 0
      app/src/main/java/org/strongswan/android/logic/VpnStateService.java
  78. 99 0
      app/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java
  79. 58 0
      app/src/main/java/org/strongswan/android/logic/imc/ImcState.java
  80. 273 0
      app/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java
  81. 28 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java
  82. 100 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java
  83. 45 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java
  84. 67 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java
  85. 65 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java
  86. 65 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java
  87. 47 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java
  88. 78 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java
  89. 68 0
      app/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java
  90. 30 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java
  91. 45 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java
  92. 55 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java
  93. 79 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java
  94. 30 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java
  95. 60 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java
  96. 61 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java
  97. 33 0
      app/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java
  98. 29 0
      app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java
  99. 139 0
      app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java
  100. 0 0
      app/src/main/java/org/strongswan/android/security/LocalCertificateStore.java

+ 53 - 0
app/build.gradle.kts

@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.cli.jvm.main
+
 plugins {
     id("com.android.application")
     id("org.jetbrains.kotlin.android")
@@ -17,6 +19,12 @@ android {
         versionName = "1.0"
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        ndk {
+            abiFilters.add("arm64-v8a")
+            abiFilters.add("armeabi-v7a")
+            abiFilters.add("x86")
+            abiFilters.add("x86_64")
+        }
         vectorDrawables {
             useSupportLibrary = true
         }
@@ -56,9 +64,19 @@ android {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
         }
     }
+    sourceSets {
+        getByName("main") {
+            aidl {
+                srcDirs("src/main/aidl")
+            }
+        }
+    }
+    buildToolsVersion = "30.0.2"
+    ndkVersion = "19.2.5345600"
 }
 
 dependencies {
+    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
 
     implementation("androidx.core:core-ktx:1.12.0")
     implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
@@ -88,6 +106,11 @@ dependencies {
     implementation("androidx.compose.runtime:runtime-livedata:1.6.1")
 
     implementation("com.wireguard.android:tunnel:1.0.20210211")
+    implementation("androidx.navigation:navigation-fragment:2.7.7")
+    implementation("androidx.appcompat:appcompat:1.6.1")
+    implementation("com.google.android.material:material:1.11.0")
+
+
 
 
     // Ping
@@ -101,6 +124,7 @@ dependencies {
     implementation("com.google.accompanist:accompanist-drawablepainter:0.34.0")
     implementation("com.google.firebase:firebase-crashlytics:18.6.2")
     implementation("com.google.firebase:firebase-messaging:23.4.1")
+    implementation("com.google.firebase:firebase-analytics:21.5.1")  //16.0.6
 //    implementation("com.firebase:firebase-jobdispatcher:0.8.5")
 
 
@@ -111,4 +135,33 @@ dependencies {
     androidTestImplementation("androidx.compose.ui:ui-test-junit4")
     debugImplementation("androidx.compose.ui:ui-tooling")
     debugImplementation("androidx.compose.ui:ui-test-manifest")
+
+
+    //strongswan
+    android{
+        sourceSets {
+            getByName("main") {
+                jni.srcDirs(emptyList<String>())
+                jniLibs.srcDirs("src/main/libs")
+            }
+        }
+    }
+
+    tasks.register<Exec>("buildNative") {
+        workingDir = file("src/main/jni")
+        commandLine(android.ndkDirectory.absolutePath + "/ndk-build", "-j", Runtime.getRuntime().availableProcessors().toString())
+    }
+
+    tasks.register<Exec>("cleanNative") {
+        workingDir = file("src/main/jni")
+        commandLine(android.ndkDirectory.absolutePath + "/ndk-build", "clean")
+    }
+
+//    tasks.withType<JavaCompile> {
+//        dependsOn("buildNative")
+//        options.compilerArgs.addAll(listOf("-Xlint:unchecked", "-Xlint:deprecation"))
+//    }
+
+    tasks.getByName("clean").dependsOn("cleanNative")
+
 }

+ 39 - 0
app/src/main/AndroidManifest.xml

@@ -5,6 +5,10 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
     <application
         android:name=".application.App"
@@ -50,6 +54,41 @@
             </intent-filter>
         </service>
 
+        <service
+            android:name="de.blinkt.openvpn.core.OpenVPNService"
+            android:exported="true"
+            android:foregroundServiceType="location"
+            android:permission="android.permission.BIND_VPN_SERVICE">
+            <intent-filter>
+                <action android:name="android.net.VpnService" />
+            </intent-filter>
+        </service>
+
+        <service
+            android:name="de.blinkt.openvpn.api.ExternalOpenVPNService"
+            android:exported="true"
+            android:foregroundServiceType="location"
+            tools:ignore="ExportedService">
+            <intent-filter>
+                <action android:name="de.blinkt.openvpn.api.IOpenVPNAPIService" />
+            </intent-filter>
+        </service>
+
+        <service
+            android:name="org.strongswan.android.logic.VpnStateService"
+            android:foregroundServiceType="location"
+            android:exported="true" />
+
+        <service
+            android:name="org.strongswan.android.logic.CharonVpnService"
+            android:exported="true"
+            android:foregroundServiceType="location"
+            android:permission="android.permission.BIND_VPN_SERVICE">
+            <intent-filter>
+                <action android:name="android.net.VpnService" />
+            </intent-filter>
+        </service>
+
     </application>
 
 </manifest>

+ 248 - 0
app/src/main/aidl/com/vpn/fastestvpnservice/Billing.aidl

@@ -0,0 +1,248 @@
+// Billing.aidl
+package com.vpn.fastestvpnservice;
+
+// Declare any non-default types here with import statements
+
+interface Billing {
+    /**
+     * Demonstrates some basic types that you can use as parameters
+     * and return values in AIDL.
+     */
+    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
+            double aDouble, String aString);
+
+    /**
+     * Checks support for the requested billing API version, package and in-app type.
+     * Minimum API version supported by this interface is 3.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName the package name of the calling app
+     * @param type type of the in-app item being purchased ("inapp" for one-time purchases
+     *        and "subs" for subscriptions)
+     * @return RESULT_OK(0) on success and appropriate response code on failures.
+     */
+    int isBillingSupported(int apiVersion, String packageName, String type);
+
+     /**
+     * Provides details of a list of SKUs
+     * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
+     * with a list JSON strings containing the productId, price, title and description.
+     * This API can be called with a maximum of 20 SKUs.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName the package name of the calling app
+     * @param type of the in-app items ("inapp" for one-time purchases
+     *        and "subs" for subscriptions)
+     * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
+     *                         on failures.
+     *         "DETAILS_LIST" with a StringArrayList containing purchase information
+     *                        in JSON format similar to:
+     *                        '{ "productId" : "exampleSku",
+     *                           "type" : "inapp",
+     *                           "price" : "$5.00",
+     *                           "price_currency": "USD",
+     *                           "price_amount_micros": 5000000,
+     *                           "title : "Example Title",
+     *                           "description" : "This is an example description" }'
+     */
+    Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
+
+    /**
+         * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
+         * the type, a unique purchase token and an optional developer payload.
+         * @param apiVersion billing API version that the app is using
+         * @param packageName package name of the calling app
+         * @param sku the SKU of the in-app item as published in the developer console
+         * @param type of the in-app item being purchased ("inapp" for one-time purchases
+         *        and "subs" for subscriptions)
+         * @param developerPayload optional argument to be sent back with the purchase information
+         * @return Bundle containing the following key-value pairs
+         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
+         *                         on failures.
+         *         "BUY_INTENT" - PendingIntent to start the purchase flow
+         *
+         * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
+         * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
+         * If the purchase is successful, the result data will contain the following key-value pairs
+         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
+         *                         codes on failures.
+         *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
+         *                                 '{"orderId":"12999763169054705758.1371079406387615",
+         *                                   "packageName":"com.example.app",
+         *                                   "productId":"exampleSku",
+         *                                   "purchaseTime":1345678900000,
+         *                                   "purchaseToken" : "122333444455555",
+         *                                   "developerPayload":"example developer payload" }'
+         *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
+         *                                  was signed with the private key of the developer
+         */
+        Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
+            String developerPayload);
+
+    /**
+         * Returns the current SKUs owned by the user of the type and package name specified along with
+         * purchase information and a signature of the data to be validated.
+         * This will return all SKUs that have been purchased in V3 and managed items purchased using
+         * V1 and V2 that have not been consumed.
+         * @param apiVersion billing API version that the app is using
+         * @param packageName package name of the calling app
+         * @param type of the in-app items being requested ("inapp" for one-time purchases
+         *        and "subs" for subscriptions)
+         * @param continuationToken to be set as null for the first call, if the number of owned
+         *        skus are too many, a continuationToken is returned in the response bundle.
+         *        This method can be called again with the continuation token to get the next set of
+         *        owned skus.
+         * @return Bundle containing the following key-value pairs
+         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
+                                   on failures.
+         *         "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
+         *         "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
+         *         "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
+         *                                      of the purchase information
+         *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
+         *                                      next set of in-app purchases. Only set if the
+         *                                      user has more owned skus than the current list.
+         */
+        Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
+
+        /**
+         * Consume the last purchase of the given SKU. This will result in this item being removed
+         * from all subsequent responses to getPurchases() and allow re-purchase of this item.
+         * @param apiVersion billing API version that the app is using
+         * @param packageName package name of the calling app
+         * @param purchaseToken token in the purchase information JSON that identifies the purchase
+         *        to be consumed
+         * @return RESULT_OK(0) if consumption succeeded, appropriate response codes on failures.
+         */
+        int consumePurchase(int apiVersion, String packageName, String purchaseToken);
+
+        /**
+         * This API is currently under development.
+         */
+        int stub(int apiVersion, String packageName, String type);
+
+        /**
+         * Returns a pending intent to launch the purchase flow for upgrading or downgrading a
+         * subscription. The existing owned SKU(s) should be provided along with the new SKU that
+         * the user is upgrading or downgrading to.
+         * @param apiVersion billing API version that the app is using, must be 5 or later
+         * @param packageName package name of the calling app
+         * @param oldSkus the SKU(s) that the user is upgrading or downgrading from,
+         *        if null or empty this method will behave like {@link #getBuyIntent}
+         * @param newSku the SKU that the user is upgrading or downgrading to
+         * @param type of the item being purchased, currently must be "subs"
+         * @param developerPayload optional argument to be sent back with the purchase information
+         * @return Bundle containing the following key-value pairs
+         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
+         *                         on failures.
+         *         "BUY_INTENT" - PendingIntent to start the purchase flow
+         *
+         * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
+         * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
+         * If the purchase is successful, the result data will contain the following key-value pairs
+         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
+         *                         codes on failures.
+         *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
+         *                                 '{"orderId":"12999763169054705758.1371079406387615",
+         *                                   "packageName":"com.example.app",
+         *                                   "productId":"exampleSku",
+         *                                   "purchaseTime":1345678900000,
+         *                                   "purchaseToken" : "122333444455555",
+         *                                   "developerPayload":"example developer payload" }'
+         *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
+         *                                  was signed with the private key of the developer
+         */
+        Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName,
+            in List<String> oldSkus, String newSku, String type, String developerPayload);
+
+     /**
+         * Returns a pending intent to launch the purchase flow for an in-app item. This method is
+         * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams}
+         * parameter. This parameter is a Bundle of optional keys and values that affect the
+         * operation of the method.
+         * @param apiVersion billing API version that the app is using, must be 6 or later
+         * @param packageName package name of the calling app
+         * @param sku the SKU of the in-app item as published in the developer console
+         * @param type of the in-app item being purchased ("inapp" for one-time purchases
+         *        and "subs" for subscriptions)
+         * @param developerPayload optional argument to be sent back with the purchase information
+         * @extraParams a Bundle with the following optional keys:
+         *        "skusToReplace" - List<String> - an optional list of SKUs that the user is
+         *                          upgrading or downgrading from.
+         *                          Pass this field if the purchase is upgrading or downgrading
+         *                          existing subscriptions.
+         *                          The specified SKUs are replaced with the SKUs that the user is
+         *                          purchasing. Google Play replaces the specified SKUs at the start of
+         *                          the next billing cycle.
+         * "replaceSkusProration" - Boolean - whether the user should be credited for any unused
+         *                          subscription time on the SKUs they are upgrading or downgrading.
+         *                          If you set this field to true, Google Play swaps out the old SKUs
+         *                          and credits the user with the unused value of their subscription
+         *                          time on a pro-rated basis.
+         *                          Google Play applies this credit to the new subscription, and does
+         *                          not begin billing the user for the new subscription until after
+         *                          the credit is used up.
+         *                          If you set this field to false, the user does not receive credit for
+         *                          any unused subscription time and the recurrence date does not
+         *                          change.
+         *                          Default value is true. Ignored if you do not pass skusToReplace.
+         *            "accountId" - String - an optional obfuscated string that is uniquely
+         *                          associated with the user's account in your app.
+         *                          If you pass this value, Google Play can use it to detect irregular
+         *                          activity, such as many devices making purchases on the same
+         *                          account in a short period of time.
+         *                          Do not use the developer ID or the user's Google ID for this field.
+         *                          In addition, this field should not contain the user's ID in
+         *                          cleartext.
+         *                          We recommend that you use a one-way hash to generate a string from
+         *                          the user's ID, and store the hashed string in this field.
+         *                   "vr" - Boolean - an optional flag indicating whether the returned intent
+         *                          should start a VR purchase flow. The apiVersion must also be 7 or
+         *                          later to use this flag.
+         */
+        Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku,
+            String type, String developerPayload, in Bundle extraParams);
+
+    /**
+         * Returns the most recent purchase made by the user for each SKU, even if that purchase is
+         * expired, canceled, or consumed.
+         * @param apiVersion billing API version that the app is using, must be 6 or later
+         * @param packageName package name of the calling app
+         * @param type of the in-app items being requested ("inapp" for one-time purchases
+         *        and "subs" for subscriptions)
+         * @param continuationToken to be set as null for the first call, if the number of owned
+         *        skus is too large, a continuationToken is returned in the response bundle.
+         *        This method can be called again with the continuation token to get the next set of
+         *        owned skus.
+         * @param extraParams a Bundle with extra params that would be appended into http request
+         *        query string. Not used at this moment. Reserved for future functionality.
+         * @return Bundle containing the following key-value pairs
+         *         "RESPONSE_CODE" with int value: RESULT_OK(0) if success,
+         *         {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures.
+         *
+         *         "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs
+         *         "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information
+         *         "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures
+         *                                      of the purchase information
+         *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
+         *                                      next set of in-app purchases. Only set if the
+         *                                      user has more owned skus than the current list.
+         */
+        Bundle getPurchaseHistory(int apiVersion, String packageName, String type,
+            String continuationToken, in Bundle extraParams);
+
+        /**
+        * This method is a variant of {@link #isBillingSupported}} that takes an additional
+        * {@code extraParams} parameter.
+        * @param apiVersion billing API version that the app is using, must be 7 or later
+        * @param packageName package name of the calling app
+        * @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs"
+        *        for subscriptions)
+        * @param extraParams a Bundle with the following optional keys:
+        *        "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams}
+        *               supports returning a VR purchase flow.
+        * @return RESULT_OK(0) on success and appropriate response code on failures.
+        */
+        int isBillingSupportedExtraParams(int apiVersion, String packageName, String type,
+            in Bundle extraParams);
+}

+ 3 - 0
app/src/main/aidl/de/blinkt/openvpn/api/APIVpnProfile.aidl

@@ -0,0 +1,3 @@
+package de.blinkt.openvpn.api;
+
+parcelable APIVpnProfile;

+ 66 - 0
app/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl

@@ -0,0 +1,66 @@
+// IOpenVPNAPIService.aidl
+package de.blinkt.openvpn.api;
+
+import de.blinkt.openvpn.api.APIVpnProfile;
+import de.blinkt.openvpn.api.IOpenVPNStatusCallback;
+
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+
+interface IOpenVPNAPIService {
+	List<APIVpnProfile> getProfiles();
+	
+	void startProfile (String profileUUID);
+	
+	/** Use a profile with all certificates etc. embedded,
+	 * old version which does not return the UUID of the addded profile, see
+	 * below for a version that return the UUID on add */
+	boolean addVPNProfile (String name, String config);
+	
+	/** start a profile using a config as inline string. Make sure that all needed data is inlined,
+	 * e.g., using <ca>...</ca> or <auth-user-data>...</auth-user-data>
+	 * See the OpenVPN manual page for more on inlining files */
+	void startVPN (in String inlineconfig);
+	
+	/** This permission framework is used  to avoid confused deputy style attack to the VPN
+	 * calling this will give null if the app is allowed to use the external API and an Intent
+	 * that can be launched to request permissions otherwise */
+	Intent prepare (in String packagename);
+	
+	/** Used to trigger to the Android VPN permission dialog (VPNService.prepare()) in advance,
+	 * if this return null OpenVPN for ANdroid already has the permissions otherwise you can start the returned Intent
+	 * to let OpenVPN for Android request the permission */
+	Intent prepareVPNService ();
+
+	/* Disconnect the VPN */
+    void disconnect();
+
+    /* Pause the VPN (same as using the pause feature in the notifcation bar) */
+    void pause();
+
+    /* Resume the VPN (same as using the pause feature in the notifcation bar) */
+    void resume();
+    
+    /**
+      * Registers to receive OpenVPN Status Updates
+      */
+    void registerStatusCallback(in IOpenVPNStatusCallback cb);
+    
+    /**
+     * Remove a previously registered callback interface.
+     */
+    void unregisterStatusCallback(in IOpenVPNStatusCallback cb);
+
+	/** Remove a profile by UUID */
+	void removeProfile (in String profileUUID);
+
+	/** Request a socket to be protected as a VPN socket would be. Useful for creating
+	  * a helper socket for an app controlling OpenVPN
+	  * Before calling this function you should make sure OpenVPN for Android may actually
+	  * this function by checking if prepareVPNService returns null; */
+	boolean protectSocket(in ParcelFileDescriptor fd);
+
+
+    /** Use a profile with all certificates etc. embedded */
+    APIVpnProfile addNewVPNProfile (String name, boolean userEditable, String config);
+}

+ 13 - 0
app/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNStatusCallback.aidl

@@ -0,0 +1,13 @@
+package de.blinkt.openvpn.api;
+
+/**
+ * Example of a callback interface used by IRemoteService to send
+ * synchronous notifications back to its clients.  Note that this is a
+ * one-way interface so the server does not block waiting for the client.
+ */
+interface IOpenVPNStatusCallback {
+    /**
+     * Called when the service has a new status for you.
+     */
+    oneway void newStatus(in String uuid, in String state, in String message, in String level);
+}

+ 3 - 0
app/src/main/aidl/de/blinkt/openvpn/core/ConnectionStatus.aidl

@@ -0,0 +1,3 @@
+package de.blinkt.openvpn.core;
+
+parcelable ConnectionStatus;

+ 23 - 0
app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.core;
+
+/**
+ * Created by arne on 15.11.16.
+ */
+
+interface IOpenVPNServiceInternal {
+
+    boolean protect(int fd);
+
+    void userPause(boolean b);
+
+    /**
+     * @param replaceConnection True if the VPN is connected by a new connection.
+     * @return true if there was a process that has been send a stop signal
+     */
+    boolean stopVPN(boolean replaceConnection);
+}

+ 36 - 0
app/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl

@@ -0,0 +1,36 @@
+// StatusIPC.aidl
+package de.blinkt.openvpn.core;
+
+// Declare any non-default types here with import statements
+import de.blinkt.openvpn.core.IStatusCallbacks;
+import android.os.ParcelFileDescriptor;
+import de.blinkt.openvpn.core.TrafficHistory;
+
+
+interface IServiceStatus {
+         /**
+          * Registers to receive OpenVPN Status Updates and gets a
+          * ParcelFileDescript back that contains the log up to that point
+          */
+         ParcelFileDescriptor registerStatusCallback(in IStatusCallbacks cb);
+
+         /**
+           * Remove a previously registered callback interface.
+           */
+        void unregisterStatusCallback(in IStatusCallbacks cb);
+
+        /**
+         * Returns the last connedcted VPN
+         */
+        String getLastConnectedVPN();
+
+        /**
+          * Sets a cached password
+          */
+       void setCachedPassword(in String uuid, int type, String password);
+
+       /**
+       * Gets the traffic history
+       */
+       TrafficHistory getTrafficHistory();
+}

+ 24 - 0
app/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl

@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.core;
+
+import de.blinkt.openvpn.core.LogItem;
+import de.blinkt.openvpn.core.ConnectionStatus;
+
+
+
+interface IStatusCallbacks {
+    /**
+     * Called when the service has a new status for you.
+     */
+    oneway void newLogItem(in LogItem item);
+
+    oneway void updateStateString(in String state, in String msg, in int resid, in ConnectionStatus level);
+
+    oneway void updateByteCount(long inBytes, long outBytes);
+
+    oneway void connectedVPN(String uuid);
+}

+ 3 - 0
app/src/main/aidl/de/blinkt/openvpn/core/LogItem.aidl

@@ -0,0 +1,3 @@
+package de.blinkt.openvpn.core;
+
+parcelable LogItem;

+ 4 - 0
app/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl

@@ -0,0 +1,4 @@
+package de.blinkt.openvpn.core;
+
+
+parcelable TrafficHistory;

+ 15 - 0
app/src/main/assets/appDetails/appDetails.json

@@ -0,0 +1,15 @@
+{
+  "ads":"false",
+  "update":[{
+    "version":"1.2.2",
+    "title":"FastestVPN",
+    "description":"The best VPN application",
+    "size":"https://git.io/JeY69"
+  }],
+  "blocked":[],
+  "free":[
+    {"id":0, "file":0, "city":"Essen","country":"Germany","image":"germany","ip":"51.68.191.75","active":"true","signal":"a"},
+    {"id":1, "file":0, "city":"Hamburg","country":"Germany","image":"germany","ip":"51.68.191.75","active":"true","signal":"b"},
+    {"id":2, "file":1, "city":"Los Angeles CA","country":"United States","image":"unitedstates","ip":"205.185.119.100","active":"true","signal":"c"}
+  ]
+}

ファイルの差分が大きいため隠しています
+ 8 - 0
app/src/main/assets/fileDetails/fileDetails.json


+ 67 - 0
app/src/main/assets/fileDetails/tcp.ovpn

@@ -0,0 +1,67 @@
+client
+proto tcp
+comp-lzo
+mssfix
+persist-key
+persist-tun
+dev tun
+auth SHA256
+auth-user-pass
+tls-client
+<ca>
+-----BEGIN CERTIFICATE-----
+MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UE
+BhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4g
+Um9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNV
+BAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBO
+IFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTo
+zFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6
+kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjp
+KWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2
+fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6v
+Ck2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4I
+quhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJ
+L/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM
+9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIA
+u2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8
+x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4
+LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8
+xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqR
+CMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaI
+hoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo
+1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4
+LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8
+X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oR
+XqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XU
+j/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPt
+TJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+
+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQX
+lvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQ
+clor0+aq
+-----END CERTIFICATE-----
+</ca>
+key-direction 1
+<tls-auth>
+-----BEGIN OpenVPN Static key V1-----
+697fe793b32cb5091d30f2326d5d124a
+9412e93d0a44ef7361395d76528fcbfc
+82c3859dccea70a93cfa8fae409709bf
+f75f844cf5ff0c237f426d0c20969233
+db0e706edb6bdf195ec3dc11b3f76bc8
+07a77e74662d9a800c8cd1144ebb67b7
+f0d3f1281d1baf522bfe03b7c3f963b1
+364fc0769400e413b61ca7b43ab19fac
+9e0f77e41efd4bda7fd77b1de2d7d785
+5cbbe3e620cecceac72c21a825b243e6
+51f44d90e290e09c3ad650de8fca99c8
+58bc7caad584bc69b11e5c9fd9381c69
+c505ec487a65912c672d83ed0113b5a7
+4ddfbd3ab33b3683cec593557520a72c
+4d6cce46111f56f3396cc3ce7183edce
+553c68ea0796cf6c4375fad00aaa2a42
+-----END OpenVPN Static key V1-----
+</tls-auth>
+tls-cipher  TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA
+cipher AES-256-CBC
+ping-timer-rem

+ 67 - 0
app/src/main/assets/fileDetails/udp.ovpn

@@ -0,0 +1,67 @@
+client
+proto udp
+comp-lzo
+mssfix
+persist-key
+persist-tun
+dev tun
+auth SHA256
+auth-user-pass
+tls-client
+<ca>
+-----BEGIN CERTIFICATE-----
+MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UE
+BhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4g
+Um9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNV
+BAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBO
+IFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTo
+zFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6
+kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjp
+KWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2
+fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6v
+Ck2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4I
+quhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJ
+L/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM
+9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIA
+u2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8
+x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4
+LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8
+xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqR
+CMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaI
+hoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo
+1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4
+LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8
+X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oR
+XqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XU
+j/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPt
+TJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+
+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQX
+lvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQ
+clor0+aq
+-----END CERTIFICATE-----
+</ca>
+key-direction 1
+<tls-auth>
+-----BEGIN OpenVPN Static key V1-----
+697fe793b32cb5091d30f2326d5d124a
+9412e93d0a44ef7361395d76528fcbfc
+82c3859dccea70a93cfa8fae409709bf
+f75f844cf5ff0c237f426d0c20969233
+db0e706edb6bdf195ec3dc11b3f76bc8
+07a77e74662d9a800c8cd1144ebb67b7
+f0d3f1281d1baf522bfe03b7c3f963b1
+364fc0769400e413b61ca7b43ab19fac
+9e0f77e41efd4bda7fd77b1de2d7d785
+5cbbe3e620cecceac72c21a825b243e6
+51f44d90e290e09c3ad650de8fca99c8
+58bc7caad584bc69b11e5c9fd9381c69
+c505ec487a65912c672d83ed0113b5a7
+4ddfbd3ab33b3683cec593557520a72c
+4d6cce46111f56f3396cc3ce7183edce
+553c68ea0796cf6c4375fad00aaa2a42
+-----END OpenVPN Static key V1-----
+</tls-auth>
+tls-cipher  TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA
+cipher AES-256-CBC
+ping-timer-rem

BIN
app/src/main/assets/nopie_openvpn.arm64-v8a


BIN
app/src/main/assets/nopie_openvpn.armeabi-v7a


BIN
app/src/main/assets/pie_openvpn.arm64-v8a


BIN
app/src/main/assets/pie_openvpn.armeabi-v7a


+ 1 - 1
app/src/main/java/com/vpn/fastestvpnservice/beans/Server.kt

@@ -15,7 +15,7 @@ class Server(
     @SerializedName("lt") val lt: Double? = null,
     @SerializedName("lg") var lg: Double? = null,
     @SerializedName("ip") val ip: Any? = null,
-    @SerializedName("port") val port: String? = null,
+    @SerializedName("port") val port: Int? = null,
     @SerializedName("protocol") val protocol: String? = null,
     @SerializedName("ipsec") val ipsec: Any? = null,
     @SerializedName("remote_id") val remoteId: Any? = null,

+ 45 - 0
app/src/main/java/com/vpn/fastestvpnservice/openVpnUtils/Crypto.java

@@ -0,0 +1,45 @@
+package com.vpn.fastestvpnservice.openVpnUtils;
+
+import android.util.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class Crypto {
+    public static String Encrypt(String key, String initVector, String value) {
+        try {
+            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
+            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
+
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
+            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
+
+            byte[] encrypted = cipher.doFinal(value.getBytes());
+            System.out.println("encrypted string: "+ Base64.encodeToString(encrypted, Base64.DEFAULT));
+
+            return Base64.encodeToString(encrypted, Base64.DEFAULT);
+        } catch (Exception e) {
+            return value;
+        }
+    }
+
+    public static String Decrypt(String key, String initVector, String encrypted) {
+        try {
+            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
+            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
+
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
+            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
+
+            byte[] original = cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT));
+
+            return new String(original);
+        } catch (Exception e) {
+            return encrypted;
+        }
+    }
+
+}
+
+

+ 14 - 0
app/src/main/java/com/vpn/fastestvpnservice/openVpnUtils/Data.java

@@ -0,0 +1,14 @@
+package com.vpn.fastestvpnservice.openVpnUtils;
+
+public class Data {
+    static String FileUsername;
+    static String FilePassword;
+
+
+    public static boolean isAppDetails = false, isConnectionDetails = false;
+    public static String PREF_USAGE = "daily_usage";
+    public static String StringCountDown;
+    public static long LongDataUsage;
+
+
+}

+ 15 - 0
app/src/main/java/com/vpn/fastestvpnservice/openVpnUtils/EncryptData.java

@@ -0,0 +1,15 @@
+package com.vpn.fastestvpnservice.openVpnUtils;
+
+import android.util.Base64;
+
+public class EncryptData {
+    public String encrypt(String input) {
+        // This is base64 encoding, which is not an encryption
+        return Base64.encodeToString(input.getBytes(), Base64.DEFAULT);
+    }
+
+    public String decrypt(String input) {
+        return new String(Base64.decode(input, Base64.DEFAULT));
+    }
+
+}

+ 291 - 0
app/src/main/java/de/blinkt/openvpn/LaunchVPN.java

@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.method.PasswordTransformationMethod;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.IOException;
+
+import de.blinkt.openvpn.core.App;
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.IServiceStatus;
+import de.blinkt.openvpn.core.OpenVPNStatusService;
+import de.blinkt.openvpn.core.PasswordCache;
+import de.blinkt.openvpn.core.Preferences;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.core.VPNLaunchHelper;
+import de.blinkt.openvpn.core.VpnStatus;
+
+/**
+ * This Activity actually handles two stages of a launcher shortcut's life cycle.
+ * <p/>
+ * 1. Your application offers to provide shortcuts to the launcher.  When
+ * the user installs a shortcut, an activity within your application
+ * generates the actual shortcut and returns it to the launcher, where it
+ * is shown to the user as an icon.
+ * <p/>
+ * 2. Any time the user clicks on an installed shortcut, an intent is sent.
+ * Typically this would then be handled as necessary by an activity within
+ * your application.
+ * <p/>
+ * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form
+ * of an {@link Intent} that the launcher will use to create the shortcut.
+ * <p/>
+ * You can also implement this in an interactive way, by having your activity actually present
+ * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL,
+ * media item, or action.
+ * <p/>
+ * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents
+ * of the incoming {@link Intent}.
+ * <p/>
+ * In a real application, you would probably use the shortcut intent to display specific content
+ * or start a particular operation.
+ */
+public class LaunchVPN extends Activity {
+    public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID";
+    public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName";
+    public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";
+    public static final String CLEARLOG = "clearlogconnect";
+    private static final int START_VPN_PROFILE = 70;
+    private VpnProfile mSelectedProfile;
+    private boolean mhideLog = false;
+    private boolean mCmfixed = false;
+    private String mTransientAuthPW;
+    private String mTransientCertOrPCKS12PW;
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder binder) {
+            IServiceStatus service = IServiceStatus.Stub.asInterface(binder);
+            try {
+                if (mTransientAuthPW != null) service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.AUTHPASSWORD, mTransientAuthPW);
+                if (mTransientCertOrPCKS12PW != null) service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.PCKS12ORCERTPASSWORD, mTransientCertOrPCKS12PW);
+                onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            unbindService(this);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        startVpnFromIntent();
+    }
+
+    protected void startVpnFromIntent() {
+        // Resolve the intent
+        //DataObj.str_network = "Establish Connection";
+        //DataObj.bl_continue = true;
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+        // If the intent is a request to create a shortcut, we'll do that and exit
+        if (Intent.ACTION_MAIN.equals(action)) {
+            // Check if we need to clear the log
+            if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) VpnStatus.clearLog();
+            // we got called to be the starting point, most likely a shortcut
+            String shortcutUUID = intent.getStringExtra(EXTRA_KEY);
+            String shortcutName = intent.getStringExtra(EXTRA_NAME);
+            mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
+            VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID);
+            if (shortcutName != null && profileToConnect == null) profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
+            if (profileToConnect == null) {
+                VpnStatus.logError(R.string.shortcut_profile_notfound);
+                // show Log window to display error
+                showLogWindow();
+                finish();
+            } else {
+                mSelectedProfile = profileToConnect;
+                launchVPN();
+            }
+        }
+    }
+
+    private void askForPW(final int type) {
+        final EditText entry = new EditText(this);
+        entry.setSingleLine();
+        entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        entry.setTransformationMethod(new PasswordTransformationMethod());
+        AlertDialog.Builder dialog = new AlertDialog.Builder(this);
+        dialog.setTitle(getString(R.string.pw_request_dialog_title, getString(type)));
+        dialog.setMessage(getString(R.string.pw_request_dialog_prompt, mSelectedProfile.mName));
+        @SuppressLint("InflateParams") final View userpwlayout = getLayoutInflater().inflate(R.layout.userpass, null, false);
+        if (type == R.string.password) {
+            ((EditText) userpwlayout.findViewById(R.id.username)).setText(mSelectedProfile.mUsername);
+            ((EditText) userpwlayout.findViewById(R.id.password)).setText(mSelectedProfile.mPassword);
+            ((CheckBox) userpwlayout.findViewById(R.id.save_password)).setChecked(!TextUtils.isEmpty(mSelectedProfile.mPassword));
+            ((CheckBox) userpwlayout.findViewById(R.id.show_password)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                @Override
+                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                    if (isChecked) ((EditText) userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+                    else ((EditText) userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+                }
+            });
+            dialog.setView(userpwlayout);
+        } else {
+            dialog.setView(entry);
+        }
+        AlertDialog.Builder builder = dialog.setPositiveButton(android.R.string.ok, new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                if (type == R.string.password) {
+                    mSelectedProfile.mUsername = ((EditText) userpwlayout.findViewById(R.id.username)).getText().toString();
+                    String pw = ((EditText) userpwlayout.findViewById(R.id.password)).getText().toString();
+                    if (((CheckBox) userpwlayout.findViewById(R.id.save_password)).isChecked()) {
+                        mSelectedProfile.mPassword = pw;
+                    } else {
+                        mSelectedProfile.mPassword = null;
+                        mTransientAuthPW = pw;
+                    }
+                } else {
+                    mTransientCertOrPCKS12PW = entry.getText().toString();
+                }
+                Intent intent = new Intent(LaunchVPN.this, OpenVPNStatusService.class);
+                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+            }
+        });
+        dialog.setNegativeButton(android.R.string.cancel, new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                VpnStatus.updateStateString("USER_VPN_PASSWORD_CANCELLED", "", R.string.state_user_vpn_password_cancelled, ConnectionStatus.LEVEL_NOTCONNECTED);
+
+                finish();
+            }
+        });
+        dialog.create().show();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == START_VPN_PROFILE) {
+            if (resultCode == Activity.RESULT_OK) {
+                int needpw = mSelectedProfile.needUserPWInput(mTransientCertOrPCKS12PW, mTransientAuthPW);
+                if (needpw != 0) {
+                    VpnStatus.updateStateString("USER_VPN_PASSWORD", "", R.string.state_user_vpn_password, ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
+                    askForPW(needpw);
+                } else {
+                    SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
+                    boolean showLogWindow = prefs.getBoolean("showlogwindow", true);
+                    if (!mhideLog && showLogWindow) showLogWindow();
+                    ProfileManager.updateLRU(this, mSelectedProfile);
+                    VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
+                    finish();
+                }
+            } else if (resultCode == Activity.RESULT_CANCELED) {
+                // User does not want us to start, so we just vanish
+                VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, ConnectionStatus.LEVEL_NOTCONNECTED);
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) VpnStatus.logError(R.string.nought_alwayson_warning);
+                App.abortConnection = true;
+                finish();
+            }
+        }
+    }
+
+    void showLogWindow() {
+        //TODO : Implement your own logwindow, apparently
+    }
+
+    void showConfigErrorDialog(int vpnok) {
+        AlertDialog.Builder d = new AlertDialog.Builder(this);
+        d.setTitle(R.string.config_error_found);
+        d.setMessage(vpnok);
+        d.setPositiveButton(android.R.string.ok, new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                finish();
+            }
+        });
+        d.setOnCancelListener(new DialogInterface.OnCancelListener() {
+            @Override
+            public void onCancel(DialogInterface dialog) {
+                finish();
+            }
+        });
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) setOnDismissListener(d);
+        d.show();
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    private void setOnDismissListener(AlertDialog.Builder d) {
+        d.setOnDismissListener(new DialogInterface.OnDismissListener() {
+            @Override
+            public void onDismiss(DialogInterface dialog) {
+                finish();
+            }
+        });
+    }
+
+    void launchVPN() {
+        int vpnok = mSelectedProfile.checkProfile(this);
+        if (vpnok != R.string.no_error_found) {
+            showConfigErrorDialog(vpnok);
+            return;
+        }
+        Intent intent = VpnService.prepare(this);
+        // Check if we want to fix /dev/tun
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
+        boolean usecm9fix = prefs.getBoolean("useCM9Fix", false);
+        boolean loadTunModule = prefs.getBoolean("loadTunModule", false);
+        if (loadTunModule) execeuteSUcmd("insmod /system/lib/modules/tun.ko");
+        if (usecm9fix && !mCmfixed) {
+            execeuteSUcmd("chown system /dev/tun");
+        }
+        if (intent != null) {
+            VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
+            // Start the query
+            try {
+                startActivityForResult(intent, START_VPN_PROFILE);
+            } catch (ActivityNotFoundException ane) {
+                // Shame on you Sony! At least one user reported that
+                // an official Sony Xperia Arc S image triggers this exception
+                VpnStatus.logError(R.string.no_vpn_support_image);
+                showLogWindow();
+            }
+        } else {
+            onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
+        }
+    }
+
+    private void execeuteSUcmd(String command) {
+        try {
+            ProcessBuilder pb = new ProcessBuilder("su", "-c", command);
+            Process p = pb.start();
+            int ret = p.waitFor();
+            if (ret == 0) mCmfixed = true;
+        } catch (InterruptedException | IOException e) {
+            VpnStatus.logException("SU command", e);
+        }
+    }
+}

+ 874 - 0
app/src/main/java/de/blinkt/openvpn/VpnProfile.java

@@ -0,0 +1,874 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.text.TextUtils;
+import android.util.Base64;
+
+import androidx.annotation.NonNull;
+
+import com.vpn.fastestvpnservice.R;
+
+import org.spongycastle.util.pem.PemObject;
+import org.spongycastle.util.pem.PemWriter;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.Vector;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import de.blinkt.openvpn.core.Connection;
+import de.blinkt.openvpn.core.NativeUtils;
+import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.PasswordCache;
+import de.blinkt.openvpn.core.VPNLaunchHelper;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.X509Utils;
+
+public class VpnProfile implements Serializable, Cloneable {
+    // Note that this class cannot be moved to core where it belongs since
+    // the profile loading depends on it being here
+    // The Serializable documentation mentions that class name change are possible
+    // but the how is unclear
+    //
+    transient public static final long MAX_EMBED_FILE_SIZE = 2048 * 1024; // 2048kB
+    // Don't change this, not all parts of the program use this constant
+    public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
+    public static final String INLINE_TAG = "[[INLINE]]";
+    public static final String DISPLAYNAME_TAG = "[[NAME]]";
+    public static final int MAXLOGLEVEL = 4;
+    public static final int CURRENT_PROFILE_VERSION = 6;
+    public static final int DEFAULT_MSSFIX_SIZE = 1280;
+    public static final int TYPE_CERTIFICATES = 0;
+    public static final int TYPE_PKCS12 = 1;
+    public static final int TYPE_KEYSTORE = 2;
+    public static final int TYPE_USERPASS = 3;
+    public static final int TYPE_STATICKEYS = 4;
+    public static final int TYPE_USERPASS_CERTIFICATES = 5;
+    public static final int TYPE_USERPASS_PKCS12 = 6;
+    public static final int TYPE_USERPASS_KEYSTORE = 7;
+    public static final int X509_VERIFY_TLSREMOTE = 0;
+    public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
+    public static final int X509_VERIFY_TLSREMOTE_DN = 2;
+    public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
+    public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
+    public static final int AUTH_RETRY_NONE_FORGET = 0;
+    public static final int AUTH_RETRY_NOINTERACT = 2;
+    public static final boolean mIsOpenVPN22 = false;
+    private static final long serialVersionUID = 7085688938959334563L;
+    private static final int AUTH_RETRY_NONE_KEEP = 1;
+    private static final int AUTH_RETRY_INTERACT = 3;
+    public static String DEFAULT_DNS1 = "8.8.8.8";
+    public static String DEFAULT_DNS2 = "8.8.4.4";
+    // variable named wrong and should haven beeen transient
+    // but needs to keep wrong name to guarante loading of old
+    // profiles
+    public transient boolean profileDeleted = false;
+    public int mAuthenticationType = TYPE_KEYSTORE;
+    public String mName;
+    public String mAlias;
+    public String mClientCertFilename;
+    public String mTLSAuthDirection = "";
+    public String mTLSAuthFilename;
+    public String mClientKeyFilename;
+    public String mCaFilename;
+    public boolean mUseLzo = true;
+    public String mPKCS12Filename;
+    public String mPKCS12Password;
+    public boolean mUseTLSAuth = false;
+    public String mDNS1 = DEFAULT_DNS1;
+    public String mDNS2 = DEFAULT_DNS2;
+    public String mIPv4Address;
+    public String mIPv6Address;
+    public boolean mOverrideDNS = false;
+    public String mSearchDomain = "blinkt.de";
+    public boolean mUseDefaultRoute = true;
+    public boolean mUsePull = true;
+    public String mCustomRoutes;
+    public boolean mCheckRemoteCN = true;
+    public boolean mExpectTLSCert = false;
+    public String mRemoteCN = "";
+    public String mPassword = "";
+    public String mUsername = "";
+    public boolean mRoutenopull = false;
+    public boolean mUseRandomHostname = false;
+    public boolean mUseFloat = false;
+    public boolean mUseCustomConfig = false;
+    public String mCustomConfigOptions = "";
+    public String mVerb = "1";  //ignored
+    public String mCipher = "";
+    public boolean mNobind = false;
+    public boolean mUseDefaultRoutev6 = true;
+    public String mCustomRoutesv6 = "";
+    public String mKeyPassword = "";
+    public boolean mPersistTun = false;
+    public String mConnectRetryMax = "-1";
+    public String mConnectRetry = "2";
+    public String mConnectRetryMaxTime = "300";
+    public boolean mUserEditable = true;
+    public String mAuth = "";
+    public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
+    public String mx509UsernameField = null;
+    public boolean mAllowLocalLAN;
+    public String mExcludedRoutes;
+    public String mExcludedRoutesv6;
+    public int mMssFix = 0; // -1 is default,
+    public Connection[] mConnections = new Connection[0];
+    public boolean mRemoteRandom = false;
+    public HashSet<String> mAllowedAppsVpn = new HashSet<>();
+    public boolean mAllowedAppsVpnAreDisallowed = true;
+    public String mCrlFilename;
+    public String mProfileCreator;
+    public int mAuthRetry = AUTH_RETRY_NONE_FORGET;
+    public int mTunMtu;
+    public boolean mPushPeerInfo = false;
+    public int mVersion = 0;
+    // timestamp when the profile was last used
+    public long mLastUsed;
+    /* Options no longer used in new profiles */
+    public String mServerName = "openvpn.example.com";
+    public String mServerPort = "11940";
+    public boolean mUseUdp = true;
+    private transient PrivateKey mPrivateKey;
+    // Public attributes, since I got mad with getter/setter
+    // set members to default values
+    private UUID mUuid;
+    private int mProfileVersion;
+
+
+    public VpnProfile(String name) {
+        mUuid = UUID.randomUUID();
+        mName = name;
+        mProfileVersion = CURRENT_PROFILE_VERSION;
+        mConnections = new Connection[1];
+        mConnections[0] = new Connection();
+        mLastUsed = System.currentTimeMillis();
+    }
+
+    public static String openVpnEscape(String unescaped) {
+        if (unescaped == null) return null;
+        String escapedString = unescaped.replace("\\", "\\\\");
+        escapedString = escapedString.replace("\"", "\\\"");
+        escapedString = escapedString.replace("\n", "\\n");
+        if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#") && !escapedString.contains(";") && !escapedString.equals("")) return unescaped;
+        else return '"' + escapedString + '"';
+    }
+
+    //! Put inline data inline and other data as normal escaped filename
+    public static String insertFileData(String cfgentry, String filedata) {
+        if (filedata == null) {
+            return String.format("%s %s\n", cfgentry, "file missing in config profile");
+        } else if (isEmbedded(filedata)) {
+            String dataWithOutHeader = getEmbeddedContent(filedata);
+            return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
+        } else {
+            return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
+        }
+    }
+
+    public static String getDisplayName(String embeddedFile) {
+        int start = DISPLAYNAME_TAG.length();
+        int end = embeddedFile.indexOf(INLINE_TAG);
+        return embeddedFile.substring(start, end);
+    }
+
+    public static String getEmbeddedContent(String data) {
+        if (!data.contains(INLINE_TAG)) return data;
+        int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
+        return data.substring(start);
+    }
+
+    public static boolean isEmbedded(String data) {
+        if (data == null) return false;
+        return data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof VpnProfile) {
+            VpnProfile vpnProfile = (VpnProfile) obj;
+            return mUuid.equals(vpnProfile.mUuid);
+        } else {
+            return false;
+        }
+    }
+
+    public void clearDefaults() {
+        mServerName = "unknown";
+        mUsePull = false;
+        mUseLzo = false;
+        mUseDefaultRoute = false;
+        mUseDefaultRoutev6 = false;
+        mExpectTLSCert = false;
+        mCheckRemoteCN = false;
+        mPersistTun = false;
+        mAllowLocalLAN = true;
+        mPushPeerInfo = false;
+        mMssFix = 0;
+    }
+
+    public UUID getUUID() {
+        return mUuid;
+    }
+
+    public String getName() {
+        if (TextUtils.isEmpty(mName)) return "No profile name";
+        return mName;
+    }
+
+    public void upgradeProfile() {
+        if (mProfileVersion < 2) {
+            /* default to the behaviour the OS used */
+            mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT;
+        }
+        if (mProfileVersion < 4) {
+            moveOptionsToConnection();
+            mAllowedAppsVpnAreDisallowed = true;
+        }
+        if (mAllowedAppsVpn == null) mAllowedAppsVpn = new HashSet<>();
+        if (mConnections == null) mConnections = new Connection[0];
+        if (mProfileVersion < 6) {
+            if (TextUtils.isEmpty(mProfileCreator)) mUserEditable = true;
+        }
+        mProfileVersion = CURRENT_PROFILE_VERSION;
+    }
+
+    private void moveOptionsToConnection() {
+        mConnections = new Connection[1];
+        Connection conn = new Connection();
+        conn.mServerName = mServerName;
+        conn.mServerPort = mServerPort;
+        conn.mUseUdp = mUseUdp;
+        conn.mCustomConfiguration = "";
+        mConnections[0] = conn;
+    }
+
+    public String getConfigFile(Context context, boolean configForOvpn3) {
+        File cacheDir = context.getCacheDir();
+        String cfg = "";
+        // Enable management interface
+        cfg += "# Enables connection to GUI\n";
+        cfg += "management ";
+        cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
+        cfg += " unix\n";
+        cfg += "management-client\n";
+        // Not needed, see updated man page in 2.3
+        //cfg += "management-signal\n";
+        cfg += "management-query-passwords\n";
+        cfg += "management-hold\n\n";
+        try {
+            if (!configForOvpn3) {
+                cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)));
+                String versionString = String.format(Locale.US, "%d %s %s %s %s %s", Build.VERSION.SDK_INT, Build.VERSION.RELEASE,
+                        NativeUtils.getNativeAPI(), Build.BRAND, Build.BOARD, Build.MODEL);
+                cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString));
+            }
+        } catch (Exception e){
+            //SendLog Send;
+            //Send = new SendLog(context);
+            //Send.Exception("200 VpnProfile getConfigFile()", e.toString());
+        }
+        cfg += "machine-readable-output\n";
+        cfg += "allow-recursive-routing\n";
+        // Users are confused by warnings that are misleading...
+        cfg += "ifconfig-nowarn\n";
+        boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);
+        if (useTLSClient && mUsePull) cfg += "client\n";
+        else if (mUsePull) cfg += "pull\n";
+        else if (useTLSClient) cfg += "tls-client\n";
+        //cfg += "verb " + mVerb + "\n";
+        cfg += "verb " + MAXLOGLEVEL + "\n";
+        if (mConnectRetryMax == null) {
+            mConnectRetryMax = "-1";
+        }
+        if (!mConnectRetryMax.equals("-1")) cfg += "connect-retry-max " + mConnectRetryMax + "\n";
+        if (TextUtils.isEmpty(mConnectRetry)) mConnectRetry = "2";
+        if (TextUtils.isEmpty(mConnectRetryMaxTime)) mConnectRetryMaxTime = "300";
+        if (!mIsOpenVPN22) cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n";
+        else if (mIsOpenVPN22 && mUseUdp) cfg += "connect-retry " + mConnectRetry + "\n";
+        cfg += "resolv-retry 60\n";
+        // We cannot use anything else than tun
+        cfg += "dev tun\n";
+        boolean canUsePlainRemotes = true;
+        if (mConnections.length == 1) {
+            cfg += mConnections[0].getConnectionBlock();
+        } else {
+            for (Connection conn : mConnections) {
+                canUsePlainRemotes = canUsePlainRemotes && conn.isOnlyRemote();
+            }
+            if (mRemoteRandom) cfg += "remote-random\n";
+            if (canUsePlainRemotes) {
+                for (Connection conn : mConnections) {
+                    if (conn.mEnabled) {
+                        cfg += conn.getConnectionBlock();
+                    }
+                }
+            }
+        }
+        switch (mAuthenticationType) {
+            case VpnProfile.TYPE_USERPASS_CERTIFICATES:
+                cfg += "auth-user-pass\n";
+            case VpnProfile.TYPE_CERTIFICATES:
+                // Ca
+                cfg += insertFileData("ca", mCaFilename);
+                // Client Cert + Key
+                cfg += insertFileData("key", mClientKeyFilename);
+                cfg += insertFileData("cert", mClientCertFilename);
+                break;
+            case VpnProfile.TYPE_USERPASS_PKCS12:
+                cfg += "auth-user-pass\n";
+            case VpnProfile.TYPE_PKCS12:
+                cfg += insertFileData("pkcs12", mPKCS12Filename);
+                break;
+            case VpnProfile.TYPE_USERPASS_KEYSTORE:
+                cfg += "auth-user-pass\n";
+            case VpnProfile.TYPE_KEYSTORE:
+                if (!configForOvpn3) {
+                    String[] ks = getKeyStoreCertificates(context);
+                    cfg += "### From Keystore ####\n";
+                    if (ks != null) {
+                        cfg += "<ca>\n" + ks[0] + "\n</ca>\n";
+                        if (ks[1] != null) cfg += "<extra-certs>\n" + ks[1] + "\n</extra-certs>\n";
+                        cfg += "<cert>\n" + ks[2] + "\n</cert>\n";
+                        cfg += "management-external-key\n";
+                    } else {
+                        cfg += context.getString(R.string.keychain_access) + "\n";
+                        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) if (!mAlias.matches("^[a-zA-Z0-9]$")) cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n";
+                    }
+                }
+                break;
+            case VpnProfile.TYPE_USERPASS:
+                cfg += "auth-user-pass\n";
+                cfg += insertFileData("ca", mCaFilename);
+        }
+        if (isUserPWAuth()) {
+            if (mAuthenticationType == AUTH_RETRY_NOINTERACT) cfg += "auth-retry nointeract";
+        }
+        if (!TextUtils.isEmpty(mCrlFilename)) cfg += insertFileData("crl-verify", mCrlFilename);
+        if (mUseLzo) {
+            cfg += "comp-lzo\n";
+        }
+        if (mUseTLSAuth) {
+            boolean useTlsCrypt = mTLSAuthDirection.equals("tls-crypt");
+            if (mAuthenticationType == TYPE_STATICKEYS) cfg += insertFileData("secret", mTLSAuthFilename);
+            else if (useTlsCrypt) cfg += insertFileData("tls-crypt", mTLSAuthFilename);
+            else cfg += insertFileData("tls-auth", mTLSAuthFilename);
+            if (!TextUtils.isEmpty(mTLSAuthDirection) && !useTlsCrypt) {
+                cfg += "key-direction ";
+                cfg += mTLSAuthDirection;
+                cfg += "\n";
+            }
+        }
+        if (!mUsePull) {
+            if (!TextUtils.isEmpty(mIPv4Address)) cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
+            if (!TextUtils.isEmpty(mIPv6Address)) cfg += "ifconfig-ipv6 " + mIPv6Address + "\n";
+        }
+        if (mUsePull && mRoutenopull) cfg += "route-nopull\n";
+        String routes = "";
+        if (mUseDefaultRoute) routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n";
+        else {
+            for (String route : getCustomRoutes(mCustomRoutes)) {
+                routes += "route " + route + " vpn_gateway\n";
+            }
+            for (String route : getCustomRoutes(mExcludedRoutes)) {
+                routes += "route " + route + " net_gateway\n";
+            }
+        }
+        if (mUseDefaultRoutev6) cfg += "route-ipv6 ::/0\n";
+        else for (String route : getCustomRoutesv6(mCustomRoutesv6)) {
+            routes += "route-ipv6 " + route + "\n";
+        }
+        cfg += routes;
+        if (mOverrideDNS || !mUsePull) {
+            if (!TextUtils.isEmpty(mDNS1)) {
+                if (mDNS1.contains(":")) cfg += "dhcp-option DNS6 " + mDNS1 + "\n";
+                else cfg += "dhcp-option DNS " + mDNS1 + "\n";
+            }
+            if (!TextUtils.isEmpty(mDNS2)) {
+                if (mDNS2.contains(":")) cfg += "dhcp-option DNS6 " + mDNS2 + "\n";
+                else cfg += "dhcp-option DNS " + mDNS2 + "\n";
+            }
+            if (!TextUtils.isEmpty(mSearchDomain)) cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";
+        }
+        if (mMssFix != 0) {
+            if (mMssFix != 1450) {
+                cfg += String.format(Locale.US, "mssfix %d\n", mMssFix);
+            } else cfg += "mssfix\n";
+        }
+        if (mTunMtu >= 48 && mTunMtu != 1500) {
+            cfg += String.format(Locale.US, "tun-mtu %d\n", mTunMtu);
+        }
+        if (mNobind) cfg += "nobind\n";
+        // Authentication
+        if (mAuthenticationType != TYPE_STATICKEYS) {
+            if (mCheckRemoteCN) {
+                if (mRemoteCN == null || mRemoteCN.equals("")) cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n";
+                else switch (mX509AuthType) {
+                    // 2.2 style x509 checks
+                    case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING:
+                        cfg += "compat-names no-remapping\n";
+                    case X509_VERIFY_TLSREMOTE:
+                        cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
+                        break;
+                    case X509_VERIFY_TLSREMOTE_RDN:
+                        cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n";
+                        break;
+                    case X509_VERIFY_TLSREMOTE_RDN_PREFIX:
+                        cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n";
+                        break;
+                    case X509_VERIFY_TLSREMOTE_DN:
+                        cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
+                        break;
+                }
+                if (!TextUtils.isEmpty(mx509UsernameField)) cfg += "x509-username-field " + openVpnEscape(mx509UsernameField) + "\n";
+            }
+            if (mExpectTLSCert) cfg += "remote-cert-tls server\n";
+        }
+        if (!TextUtils.isEmpty(mCipher)) {
+            cfg += "cipher " + mCipher + "\n";
+        }
+        if (!TextUtils.isEmpty(mAuth)) {
+            cfg += "auth " + mAuth + "\n";
+        }
+        // Obscure Settings dialog
+        if (mUseRandomHostname) cfg += "#my favorite options :)\nremote-random-hostname\n";
+        if (mUseFloat) cfg += "float\n";
+        if (mPersistTun) {
+            cfg += "persist-tun\n";
+            cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n";
+            cfg += "preresolve\n";
+        }
+        if (mPushPeerInfo) cfg += "push-peer-info\n";
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
+        if (usesystemproxy && !mIsOpenVPN22) {
+            cfg += "# Use system proxy setting\n";
+            cfg += "management-query-proxy\n";
+        }
+        if (mUseCustomConfig) {
+            cfg += "# Custom configuration options\n";
+            cfg += "# You are on your on own here :)\n";
+            cfg += mCustomConfigOptions;
+            cfg += "\n";
+        }
+        if (!canUsePlainRemotes) {
+            cfg += "# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n";
+            for (Connection conn : mConnections) {
+                if (conn.mEnabled) {
+                    cfg += "<connection>\n";
+                    cfg += conn.getConnectionBlock();
+                    cfg += "</connection>\n";
+                }
+            }
+        }
+        return cfg;
+    }
+
+    public String getVersionEnvString(Context c) {
+        String version = "unknown";
+        try {
+            PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+            version = packageinfo.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            VpnStatus.logException(e);
+        }
+        return String.format(Locale.US, "%s %s", c.getPackageName(), version);
+    }
+
+    @NonNull
+    private Collection<String> getCustomRoutes(String routes) {
+        Vector<String> cidrRoutes = new Vector<>();
+        if (routes == null) {
+            // No routes set, return empty vector
+            return cidrRoutes;
+        }
+        for (String route : routes.split("[\n \t]")) {
+            if (!route.equals("")) {
+                String cidrroute = cidrToIPAndNetmask(route);
+                if (cidrroute == null) return cidrRoutes;
+                cidrRoutes.add(cidrroute);
+            }
+        }
+        return cidrRoutes;
+    }
+
+    private Collection<String> getCustomRoutesv6(String routes) {
+        Vector<String> cidrRoutes = new Vector<>();
+        if (routes == null) {
+            // No routes set, return empty vector
+            return cidrRoutes;
+        }
+        for (String route : routes.split("[\n \t]")) {
+            if (!route.equals("")) {
+                cidrRoutes.add(route);
+            }
+        }
+        return cidrRoutes;
+    }
+
+    private String cidrToIPAndNetmask(String route) {
+        String[] parts = route.split("/");
+        // No /xx, assume /32 as netmask
+        if (parts.length == 1) parts = (route + "/32").split("/");
+        if (parts.length != 2) return null;
+        int len;
+        try {
+            len = Integer.parseInt(parts[1]);
+        } catch (NumberFormatException ne) {
+            return null;
+        }
+        if (len < 0 || len > 32) return null;
+        long nm = 0xffffffffL;
+        nm = (nm << (32 - len)) & 0xffffffffL;
+        String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff);
+        return parts[0] + "  " + netmask;
+    }
+
+    public Intent prepareStartService(Context context) {
+        Intent intent = getStartServiceIntent(context);
+        // TODO: Handle this?!
+//        if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
+//            if (getKeyStoreCertificates(context) == null)
+//                return null;
+//        }
+        return intent;
+    }
+
+    public void writeConfigFile(Context context) throws IOException {
+        FileWriter cfg = new FileWriter(VPNLaunchHelper.getConfigFilePath(context));
+        cfg.write(getConfigFile(context, false));
+        cfg.flush();
+        cfg.close();
+    }
+
+    public Intent getStartServiceIntent(Context context) {
+        String prefix = context.getPackageName();
+        Intent intent = new Intent(context, OpenVPNService.class);
+        intent.putExtra(prefix + ".profileUUID", mUuid.toString());
+        intent.putExtra(prefix + ".profileVersion", mVersion);
+        return intent;
+    }
+
+    public String[] getKeyStoreCertificates(Context context) {
+        return getKeyStoreCertificates(context, 5);
+    }
+
+    public void checkForRestart(final Context context) {
+        /* This method is called when OpenVPNService is restarted */
+        if ((mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) && mPrivateKey == null) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    getKeyStoreCertificates(context);
+                }
+            }).start();
+        }
+    }
+
+    @Override
+    protected VpnProfile clone() throws CloneNotSupportedException {
+        VpnProfile copy = (VpnProfile) super.clone();
+        copy.mUuid = UUID.randomUUID();
+        copy.mConnections = new Connection[mConnections.length];
+        int i = 0;
+        for (Connection conn : mConnections) {
+            copy.mConnections[i++] = conn.clone();
+        }
+        copy.mAllowedAppsVpn = (HashSet<String>) mAllowedAppsVpn.clone();
+        return copy;
+    }
+
+    public VpnProfile copy(String name) {
+        try {
+            VpnProfile copy = clone();
+            copy.mName = name;
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public void pwDidFail(Context c) {
+    }
+
+    synchronized String[] getKeyStoreCertificates(Context context, int tries) {
+        // Force application context- KeyChain methods will block long enough that by the time they
+        // are finished and try to unbind, the original activity context might have been destroyed.
+        context = context.getApplicationContext();
+        try {
+            PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
+            mPrivateKey = privateKey;
+            String keystoreChain = null;
+            X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias);
+            if (caChain == null) throw new NoCertReturnedException("No certificate returned from Keystore");
+            if (caChain.length <= 1 && TextUtils.isEmpty(mCaFilename)) {
+                VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert));
+            } else {
+                StringWriter ksStringWriter = new StringWriter();
+                PemWriter pw = new PemWriter(ksStringWriter);
+                for (int i = 1; i < caChain.length; i++) {
+                    X509Certificate cert = caChain[i];
+                    pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
+                }
+                pw.close();
+                keystoreChain = ksStringWriter.toString();
+            }
+            String caout = null;
+            if (!TextUtils.isEmpty(mCaFilename)) {
+                try {
+                    Certificate[] cacerts = X509Utils.getCertificatesFromFile(mCaFilename);
+                    StringWriter caoutWriter = new StringWriter();
+                    PemWriter pw = new PemWriter(caoutWriter);
+                    for (Certificate cert : cacerts)
+                        pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
+                    pw.close();
+                    caout = caoutWriter.toString();
+                } catch (Exception e) {
+                    VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage());
+                }
+            }
+            StringWriter certout = new StringWriter();
+            if (caChain.length >= 1) {
+                X509Certificate usercert = caChain[0];
+                PemWriter upw = new PemWriter(certout);
+                upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded()));
+                upw.close();
+            }
+            String user = certout.toString();
+            String ca, extra;
+            if (caout == null) {
+                ca = keystoreChain;
+                extra = null;
+            } else {
+                ca = caout;
+                extra = keystoreChain;
+            }
+            return new String[]{ca, extra, user};
+        } catch (InterruptedException | IOException | KeyChainException | NoCertReturnedException | IllegalArgumentException | CertificateException e) {
+            e.printStackTrace();
+            VpnStatus.logError(R.string.keyChainAccessError, e.getLocalizedMessage());
+            VpnStatus.logError(R.string.keychain_access);
+            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
+                if (!mAlias.matches("^[a-zA-Z0-9]$")) {
+                    VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug);
+                }
+            }
+            return null;
+        } catch (AssertionError e) {
+            if (tries == 0) return null;
+            VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying", e.getLocalizedMessage()));
+            try {
+                Thread.sleep(3000);
+            } catch (InterruptedException e1) {
+                VpnStatus.logException(e1);
+            }
+            return getKeyStoreCertificates(context, tries - 1);
+        }
+    }
+
+    //! Return an error if something is wrong
+    public int checkProfile(Context context) {
+        if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
+            if (mAlias == null) return R.string.no_keystore_cert_selected;
+        } else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
+            if (TextUtils.isEmpty(mCaFilename)) return R.string.no_ca_cert_selected;
+        }
+        if (mCheckRemoteCN && mX509AuthType == X509_VERIFY_TLSREMOTE) return R.string.deprecated_tls_remote;
+        if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) {
+            if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) return R.string.ipv4_format_error;
+        }
+        if (!mUseDefaultRoute) {
+            if (!TextUtils.isEmpty(mCustomRoutes) && getCustomRoutes(mCustomRoutes).size() == 0) return R.string.custom_route_format_error;
+            if (!TextUtils.isEmpty(mExcludedRoutes) && getCustomRoutes(mExcludedRoutes).size() == 0) return R.string.custom_route_format_error;
+        }
+        if (mUseTLSAuth && TextUtils.isEmpty(mTLSAuthFilename)) return R.string.missing_tlsauth;
+        if ((mAuthenticationType == TYPE_USERPASS_CERTIFICATES || mAuthenticationType == TYPE_CERTIFICATES) && (TextUtils.isEmpty(mClientCertFilename) || TextUtils.isEmpty(mClientKeyFilename)))
+            return R.string.missing_certificates;
+        if ((mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) && TextUtils.isEmpty(mCaFilename)) return R.string.missing_ca_certificate;
+        boolean noRemoteEnabled = true;
+        for (Connection c : mConnections)
+            if (c.mEnabled) noRemoteEnabled = false;
+        if (noRemoteEnabled) return R.string.remote_no_server_selected;
+        // Everything okay
+        return R.string.no_error_found;
+    }
+
+    //! Openvpn asks for a "Private Key", this should be pkcs12 key
+    //
+    public String getPasswordPrivateKey() {
+        String cachedPw = PasswordCache.getPKCS12orCertificatePassword(mUuid, true);
+        if (cachedPw != null) {
+            return cachedPw;
+        }
+        switch (mAuthenticationType) {
+            case TYPE_PKCS12:
+            case TYPE_USERPASS_PKCS12:
+                return mPKCS12Password;
+            case TYPE_CERTIFICATES:
+            case TYPE_USERPASS_CERTIFICATES:
+                return mKeyPassword;
+            case TYPE_USERPASS:
+            case TYPE_STATICKEYS:
+            default:
+                return null;
+        }
+    }
+
+    public boolean isUserPWAuth() {
+        switch (mAuthenticationType) {
+            case TYPE_USERPASS:
+            case TYPE_USERPASS_CERTIFICATES:
+            case TYPE_USERPASS_KEYSTORE:
+            case TYPE_USERPASS_PKCS12:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public boolean requireTLSKeyPassword() {
+        if (TextUtils.isEmpty(mClientKeyFilename)) return false;
+        String data = "";
+        if (isEmbedded(mClientKeyFilename)) data = mClientKeyFilename;
+        else {
+            char[] buf = new char[2048];
+            FileReader fr;
+            try {
+                fr = new FileReader(mClientKeyFilename);
+                int len = fr.read(buf);
+                while (len > 0) {
+                    data += new String(buf, 0, len);
+                    len = fr.read(buf);
+                }
+                fr.close();
+            } catch (FileNotFoundException e) {
+                return false;
+            } catch (IOException e) {
+                return false;
+            }
+        }
+        if (data.contains("Proc-Type: 4,ENCRYPTED")) return true;
+        else return data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----");
+    }
+
+    public int needUserPWInput(String transientCertOrPkcs12PW, String mTransientAuthPW) {
+        if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) && (mPKCS12Password == null || mPKCS12Password.equals(""))) {
+            if (transientCertOrPkcs12PW == null) return R.string.pkcs12_file_encryption_key;
+        }
+        if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
+            if (requireTLSKeyPassword() && TextUtils.isEmpty(mKeyPassword)) if (transientCertOrPkcs12PW == null) {
+                return R.string.private_key_password;
+            }
+        }
+        if (isUserPWAuth() && (TextUtils.isEmpty(mUsername) || (TextUtils.isEmpty(mPassword) && mTransientAuthPW == null))) {
+            return R.string.password;
+        }
+        return 0;
+    }
+
+    public String getPasswordAuth() {
+        String cachedPw = PasswordCache.getAuthPassword(mUuid, true);
+        if (cachedPw != null) {
+            return cachedPw;
+        } else {
+            return mPassword;
+        }
+    }
+
+    // Used by the Array Adapter
+    @Override
+    public String toString() {
+        return mName;
+    }
+
+    public String getUUIDString() {
+        return mUuid.toString();
+    }
+
+    public PrivateKey getKeystoreKey() {
+        return mPrivateKey;
+    }
+
+    public String getSignedData(String b64data) {
+        PrivateKey privkey = getKeystoreKey();
+        byte[] data = Base64.decode(b64data, Base64.DEFAULT);
+        // The Jelly Bean *evil* Hack
+        // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
+            return processSignJellyBeans(privkey, data);
+        }
+        try {
+            /* ECB is perfectly fine in this special case, since we are using it for
+               the public/private part in the TLS exchange
+             */
+            @SuppressLint("GetInstance") Cipher rsaSigner = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+            rsaSigner.init(Cipher.ENCRYPT_MODE, privkey);
+            byte[] signed_bytes = rsaSigner.doFinal(data);
+            return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+        } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) {
+            VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
+            return null;
+        }
+    }
+
+    private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
+        try {
+            Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
+            getKey.setAccessible(true);
+            // Real object type is OpenSSLKey
+            Object opensslkey = getKey.invoke(privkey);
+            getKey.setAccessible(false);
+            Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext");
+            // integer pointer to EVP_pkey
+            getPkeyContext.setAccessible(true);
+            int pkey = (Integer) getPkeyContext.invoke(opensslkey);
+            getPkeyContext.setAccessible(false);
+            // 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
+            byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
+            return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+        } catch (NoSuchMethodException | InvalidKeyException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
+            VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
+            return null;
+        }
+    }
+
+    class NoCertReturnedException extends Exception {
+        public NoCertReturnedException(String msg) {
+            super(msg);
+        }
+    }
+}

+ 49 - 0
app/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.api;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class APIVpnProfile implements Parcelable {
+    public static final Creator<APIVpnProfile> CREATOR
+            = new Creator<APIVpnProfile>() {
+        public APIVpnProfile createFromParcel(Parcel in) {
+            return new APIVpnProfile(in);
+        }
+        public APIVpnProfile[] newArray(int size) {
+            return new APIVpnProfile[size];
+        }
+    };
+    public final String mUUID;
+    public final String mName;
+    //public final String mProfileCreator;
+    public final boolean mUserEditable;
+    public APIVpnProfile(Parcel in) {
+        mUUID = in.readString();
+        mName = in.readString();
+        mUserEditable = in.readInt() != 0;
+        //mProfileCreator = in.readString();
+    }
+    public APIVpnProfile(String uuidString, String name, boolean userEditable, String profileCreator) {
+        mUUID = uuidString;
+        mName = name;
+        mUserEditable = userEditable;
+        //mProfileCreator = profileCreator;
+    }
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mUUID);
+        dest.writeString(mName);
+        if (mUserEditable)
+            dest.writeInt(0);
+        else
+            dest.writeInt(1);
+        //dest.writeString(mProfileCreator);
+    }
+}

+ 101 - 0
app/src/main/java/de/blinkt/openvpn/api/ConfirmDialog.java

@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 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 de.blinkt.openvpn.api;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnShowListener;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.vpn.fastestvpnservice.R;
+
+public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener {
+    private static final String TAG = "OpenVPNVpnConfirm";
+    private String mPackage;
+    private Button mButton;
+    private AlertDialog mAlert;
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        try {
+            mPackage = getCallingPackage();
+            if (mPackage == null) {
+                finish();
+                return;
+            }
+            PackageManager pm = getPackageManager();
+            ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
+            View view = View.inflate(this, R.layout.api_confirm, null);
+            ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm));
+            ((TextView) view.findViewById(R.id.prompt)).setText(getString(R.string.prompt, app.loadLabel(pm), getString(R.string.app_name)));
+            ((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this);
+            Builder builder = new Builder(this);
+            builder.setView(view);
+            builder.setIconAttribute(android.R.attr.alertDialogIcon);
+            builder.setTitle(android.R.string.dialog_alert_title);
+            builder.setPositiveButton(android.R.string.ok, this);
+            builder.setNegativeButton(android.R.string.cancel, this);
+            mAlert = builder.create();
+            mAlert.setCanceledOnTouchOutside(false);
+            mAlert.setOnShowListener(new OnShowListener() {
+                @Override
+                public void onShow(DialogInterface dialog) {
+                    mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+                    mButton.setEnabled(false);
+                }
+            });
+            //setCloseOnTouchOutside(false);
+            mAlert.show();
+        } catch (Exception e) {
+            //Log.e(TAG, "onResume", e);
+            finish();
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        setResult(RESULT_CANCELED);
+        finish();
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton button, boolean checked) {
+        mButton.setEnabled(checked);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            ExternalAppDatabase extapps = new ExternalAppDatabase(this);
+            extapps.addApp(mPackage);
+            setResult(RESULT_OK);
+            finish();
+        }
+        if (which == DialogInterface.BUTTON_NEGATIVE) {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+    }
+}

+ 52 - 0
app/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.api;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import de.blinkt.openvpn.core.Preferences;
+
+public class ExternalAppDatabase {
+    private final String PREFERENCES_KEY = "allowed_apps";
+    Context mContext;
+    public ExternalAppDatabase(Context c) {
+        mContext = c;
+    }
+    boolean isAllowed(String packagename) {
+        Set<String> allowedapps = getExtAppList();
+        return allowedapps.contains(packagename);
+    }
+    public Set<String> getExtAppList() {
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
+        return prefs.getStringSet(PREFERENCES_KEY, new HashSet<String>());
+    }
+    public void addApp(String packagename) {
+        Set<String> allowedapps = getExtAppList();
+        allowedapps.add(packagename);
+        saveExtAppList(allowedapps);
+    }
+    private void saveExtAppList(Set<String> allowedapps) {
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
+        Editor prefedit = prefs.edit();
+        // Workaround for bug
+        prefedit.putStringSet(PREFERENCES_KEY, allowedapps);
+        int counter = prefs.getInt("counter", 0);
+        prefedit.putInt("counter", counter + 1);
+        prefedit.apply();
+    }
+    public void clearAllApiApps() {
+        saveExtAppList(new HashSet<String>());
+    }
+    public void removeApp(String packagename) {
+        Set<String> allowedapps = getExtAppList();
+        allowedapps.remove(packagename);
+        saveExtAppList(allowedapps);
+    }
+}

+ 343 - 0
app/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java

@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.api;
+
+import android.annotation.TargetApi;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.VpnService;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+import java.util.List;
+
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.ConfigParser;
+import de.blinkt.openvpn.core.ConfigParser.ConfigParseError;
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
+import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.core.VPNLaunchHelper;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.VpnStatus.StateListener;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+public class ExternalOpenVPNService extends Service implements StateListener {
+    private static final int SEND_TOALL = 0;
+    private static final OpenVPNServiceHandler mHandler = new OpenVPNServiceHandler();
+    final RemoteCallbackList<IOpenVPNStatusCallback> mCallbacks = new RemoteCallbackList<>();
+    private IOpenVPNServiceInternal mService;
+    private ExternalAppDatabase mExtAppDb;
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            //Log.e("ExternalOpenVPN", "ExternalOpenVPN");
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            mService = (IOpenVPNServiceInternal) (service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mService = null;
+        }
+    };
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent != null && Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+                // Check if the running config is temporary and installed by the app being uninstalled
+                VpnProfile vp = ProfileManager.getLastConnectedVpn();
+                if (ProfileManager.isTempProfile()) {
+                    if (intent.getPackage().equals(vp.mProfileCreator)) {
+                        if (mService != null) try {
+                            mService.stopVPN(false);
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+            }
+        }
+    };
+    private UpdateMessage mMostRecentState;
+    private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() {
+        private String checkOpenVPNPermission() throws SecurityRemoteException {
+            PackageManager pm = getPackageManager();
+            for (String appPackage : mExtAppDb.getExtAppList()) {
+                ApplicationInfo app;
+                try {
+                    app = pm.getApplicationInfo(appPackage, 0);
+                    if (Binder.getCallingUid() == app.uid) {
+                        return appPackage;
+                    }
+                } catch (NameNotFoundException e) {
+                    // App not found. Remove it from the list
+                    mExtAppDb.removeApp(appPackage);
+                }
+            }
+            throw new SecurityException("Unauthorized OpenVPN API Caller");
+        }
+
+        @Override
+        public List<APIVpnProfile> getProfiles() throws RemoteException {
+            checkOpenVPNPermission();
+            ProfileManager pm = ProfileManager.getInstance(getBaseContext());
+            List<APIVpnProfile> profiles = new LinkedList<>();
+            for (VpnProfile vp : pm.getProfiles()) {
+                if (!vp.profileDeleted) profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator));
+            }
+            return profiles;
+        }
+
+        private void startProfile(VpnProfile vp) {
+            Intent vpnPermissionIntent = VpnService.prepare(ExternalOpenVPNService.this);
+            /* Check if we need to show the confirmation dialog,
+             * Check if we need to ask for username/password */
+            int neddPassword = vp.needUserPWInput(null, null);
+            if (vpnPermissionIntent != null || neddPassword != 0) {
+                Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN);
+                shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class);
+                shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, vp.getUUIDString());
+                shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_HIDELOG, true);
+                shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivity(shortVPNIntent);
+            } else {
+                VPNLaunchHelper.startOpenVpn(vp, getBaseContext());
+            }
+        }
+
+        @Override
+        public void startProfile(String profileUUID) throws RemoteException {
+            checkOpenVPNPermission();
+            VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
+            if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) throw new RemoteException(getString(vp.checkProfile(getApplicationContext())));
+            startProfile(vp);
+        }
+
+        public void startVPN(String inlineConfig) throws RemoteException {
+            String callingApp = checkOpenVPNPermission();
+            ConfigParser cp = new ConfigParser(getBaseContext());
+            try {
+                cp.parseConfig(new StringReader(inlineConfig));
+                VpnProfile vp = cp.convertProfile();
+                vp.mName = "Remote APP VPN";
+                if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) throw new RemoteException(getString(vp.checkProfile(getApplicationContext())));
+                vp.mProfileCreator = callingApp;
+                /*int needpw = vp.needUserPWInput(false);
+                if(needpw !=0)
+                    throw new RemoteException("The inline file would require user input: " + getString(needpw));
+                    */
+                ProfileManager.setTemporaryProfile(ExternalOpenVPNService.this, vp);
+                startProfile(vp);
+            } catch (IOException | ConfigParseError e) {
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        @Override
+        public boolean addVPNProfile(String name, String config) throws RemoteException {
+            return addNewVPNProfile(name, true, config) != null;
+        }
+
+        @Override
+        public APIVpnProfile addNewVPNProfile(String name, boolean userEditable, String config) throws RemoteException {
+            String callingPackage = checkOpenVPNPermission();
+            ConfigParser cp = new ConfigParser(getBaseContext());
+            try {
+                cp.parseConfig(new StringReader(config));
+                VpnProfile vp = cp.convertProfile();
+                vp.mName = name;
+                vp.mProfileCreator = callingPackage;
+                vp.mUserEditable = userEditable;
+                ProfileManager pm = ProfileManager.getInstance(getBaseContext());
+                pm.addProfile(vp);
+                pm.saveProfile(ExternalOpenVPNService.this, vp);
+                pm.saveProfileList(ExternalOpenVPNService.this);
+                return new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator);
+            } catch (IOException e) {
+                VpnStatus.logException(e);
+                return null;
+            } catch (ConfigParseError e) {
+                VpnStatus.logException(e);
+                return null;
+            }
+        }
+
+        @Override
+        public void removeProfile(String profileUUID) throws RemoteException {
+            checkOpenVPNPermission();
+            ProfileManager pm = ProfileManager.getInstance(getBaseContext());
+            VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
+            pm.removeProfile(ExternalOpenVPNService.this, vp);
+        }
+
+        @Override
+        public boolean protectSocket(ParcelFileDescriptor pfd) throws RemoteException {
+            checkOpenVPNPermission();
+            try {
+                boolean success = mService.protect(pfd.getFd());
+                pfd.close();
+                return success;
+            } catch (IOException e) {
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        @Override
+        public Intent prepare(String packageName) {
+            if (new ExternalAppDatabase(ExternalOpenVPNService.this).isAllowed(packageName)) return null;
+            Intent intent = new Intent();
+            intent.setClass(ExternalOpenVPNService.this, ConfirmDialog.class);
+            return intent;
+        }
+
+        @Override
+        public Intent prepareVPNService() throws RemoteException {
+            checkOpenVPNPermission();
+            if (VpnService.prepare(ExternalOpenVPNService.this) == null) return null;
+            else return new Intent(getBaseContext(), GrantPermissionsActivity.class);
+        }
+
+        @Override
+        public void registerStatusCallback(IOpenVPNStatusCallback cb) throws RemoteException {
+            checkOpenVPNPermission();
+            if (cb != null) {
+                cb.newStatus(mMostRecentState.vpnUUID, mMostRecentState.state, mMostRecentState.logmessage, mMostRecentState.level.name());
+                mCallbacks.register(cb);
+            }
+        }
+
+        @Override
+        public void unregisterStatusCallback(IOpenVPNStatusCallback cb) throws RemoteException {
+            checkOpenVPNPermission();
+            if (cb != null) mCallbacks.unregister(cb);
+        }
+
+        @Override
+        public void disconnect() throws RemoteException {
+            checkOpenVPNPermission();
+            if (mService != null) mService.stopVPN(false);
+        }
+
+        @Override
+        public void pause() throws RemoteException {
+            checkOpenVPNPermission();
+            if (mService != null) mService.userPause(true);
+        }
+
+        @Override
+        public void resume() throws RemoteException {
+            checkOpenVPNPermission();
+            if (mService != null) mService.userPause(false);
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        VpnStatus.addStateListener(this);
+        mExtAppDb = new ExternalAppDatabase(this);
+        Intent intent = new Intent(getBaseContext(), OpenVPNService.class);
+        intent.setAction(OpenVPNService.START_SERVICE);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        mHandler.setService(this);
+        IntentFilter uninstallBroadcast = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+        registerReceiver(mBroadcastReceiver, uninstallBroadcast);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mCallbacks.kill();
+        unbindService(mConnection);
+        VpnStatus.removeStateListener(this);
+        unregisterReceiver(mBroadcastReceiver);
+    }
+
+    @Override
+    public void updateState(String state, String logmessage, int resid, ConnectionStatus level) {
+        mMostRecentState = new UpdateMessage(state, logmessage, level);
+        if (ProfileManager.getLastConnectedVpn() != null) mMostRecentState.vpnUUID = ProfileManager.getLastConnectedVpn().getUUIDString();
+        Message msg = mHandler.obtainMessage(SEND_TOALL, mMostRecentState);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public void setConnectedVPN(String uuid) {
+    }
+
+    static class OpenVPNServiceHandler extends Handler {
+        WeakReference<ExternalOpenVPNService> service = null;
+
+        private void setService(ExternalOpenVPNService eos) {
+            service = new WeakReference<>(eos);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            RemoteCallbackList<IOpenVPNStatusCallback> callbacks;
+            switch (msg.what) {
+                case SEND_TOALL:
+                    if (service == null || service.get() == null) return;
+                    callbacks = service.get().mCallbacks;
+                    // Broadcast to all clients the new value.
+                    final int N = callbacks.beginBroadcast();
+                    for (int i = 0; i < N; i++) {
+                        try {
+                            sendUpdate(callbacks.getBroadcastItem(i), (UpdateMessage) msg.obj);
+                        } catch (RemoteException e) {
+                            // The RemoteCallbackList will take care of removing
+                            // the dead object for us.
+                        }
+                    }
+                    callbacks.finishBroadcast();
+                    break;
+            }
+        }
+
+        private void sendUpdate(IOpenVPNStatusCallback broadcastItem, UpdateMessage um) throws RemoteException {
+            broadcastItem.newStatus(um.vpnUUID, um.state, um.logmessage, um.level.name());
+        }
+    }
+
+    class UpdateMessage {
+        public String state;
+        public String logmessage;
+        public ConnectionStatus level;
+        String vpnUUID;
+
+        UpdateMessage(String state, String logmessage, ConnectionStatus level) {
+            this.state = state;
+            this.logmessage = logmessage;
+            this.level = level;
+        }
+    }
+}

+ 27 - 0
app/src/main/java/de/blinkt/openvpn/api/GrantPermissionsActivity.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.api;
+import android.app.Activity;
+import android.content.Intent;
+import android.net.VpnService;
+
+public class GrantPermissionsActivity extends Activity {
+    private static final int VPN_PREPARE = 0;
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Intent i = VpnService.prepare(this);
+        if (i == null)
+            onActivityResult(VPN_PREPARE, RESULT_OK, null);
+        else
+            startActivityForResult(i, VPN_PREPARE);
+    }
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        setResult(resultCode);
+        finish();
+    }
+}

+ 13 - 0
app/src/main/java/de/blinkt/openvpn/api/SecurityRemoteException.java

@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.api;
+import android.os.RemoteException;
+
+public class SecurityRemoteException extends RemoteException {
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+}

+ 286 - 0
app/src/main/java/de/blinkt/openvpn/core/App.java

@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.util.Log;
+
+import com.vpn.fastestvpnservice.R;
+import com.vpn.fastestvpnservice.constants.AppConstant;
+import com.wireguard.android.backend.Backend;
+import com.wireguard.android.backend.Tunnel;
+import com.wireguard.config.Config;
+import com.wireguard.config.Peer;
+
+import org.strongswan.android.logic.StrongSwanApplication;
+import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
+
+import java.security.Security;
+import java.util.Calendar;
+import java.util.Random;
+
+import wireguard.WgTunnel;
+
+public class App extends /*com.orm.SugarApp*/ Application {
+    public static final int NOTIFICATION_ID = new Random().nextInt(601) + 200;
+    public static final int NOTIFICATION_ID_CONST = 1;
+
+    public static final long connectionWaitTime = 10;
+    public final static int CONNECTION_STATE_DISCONNECTED = 0;
+    public final static int CONNECTION_STATE_CONNECTING = 1;
+    public final static int CONNECTION_STATE_CONNECTED = 2;
+    public final static int CONNECTION_STATE_CONNECTED_2 = 3;
+    public final static int CONNECTION_STATE_SERVER_NOT_RESPONDING = 4;
+    public final static int CONNECTION_STATE_UP_WG = 5;
+    public final static int CONNECTION_STATE_DOWN_WG = 6;
+    public static boolean isStart;
+    public static int connection_status = 0;
+    public static boolean hasFile = false;
+    public static boolean abortConnection = false;
+    public static long CountDown;
+    public static boolean ShowDailyUsage = true;
+
+    public static boolean isShowNotify = false;
+    public static final boolean isAndroidTvBox = true;
+
+    public static String device_id;
+    public static long device_created;
+    private static Context mContext;
+
+    private static App instance;
+
+    public static Peer.Builder peerBuilder = new Peer.Builder();
+    public static Backend backend;
+    public static WgTunnel tunnel;
+    public static Tunnel.State tunnelStatus;
+    public static Config.Builder config;
+    public static App mInstance = null;
+    public static IOpenVPNServiceInternal mService = null;
+
+    public static WgTunnel getTunnel() {
+        try {
+            tunnel.getName();
+        } catch (Exception e) {
+            tunnel = new WgTunnel();
+        }
+        return tunnel;
+    }
+    public static void setBackend(Backend backend1) {
+        backend = backend1;
+    }
+
+    public static Backend getBackend() {
+        return backend;
+    }
+    public static void setConfig(Config.Builder config1) {
+        config = config1;
+    }
+
+    public static Config.Builder getConfig() {
+        return config;
+    }
+
+    public static synchronized App getInstance() {
+        if (null == App.mInstance)
+        {
+            mInstance = new App();
+        }
+        return mInstance;
+    }
+
+    public static App getApplication() {
+        return instance;
+    }
+
+    static {
+        Security.addProvider(new LocalCertificateKeyStoreProvider());
+    }
+
+    /*
+     * The libraries are extracted to /data/data/org.strongswan.android/...
+     * during installation.  On newer releases most are loaded in JNI_OnLoad.
+     */
+    static {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            System.loadLibrary("strongswan");
+
+            //if (MainActivity.USE_BYOD)
+            //{
+            System.loadLibrary("tpmtss");
+            System.loadLibrary("tncif");
+            System.loadLibrary("tnccs");
+            System.loadLibrary("imcv");
+            //}
+
+            System.loadLibrary("charon");
+            System.loadLibrary("ipsec");
+        }
+        System.loadLibrary("androidbridge");
+    }
+
+    NotificationManager manager;
+
+    //strongswan
+
+    /**
+     * Returns the current application context
+     *
+     * @return context
+     */
+    public static Context getContext() {
+        return StrongSwanApplication.mContext;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        instance = this;
+
+        createNotificationChannel();
+
+        /*SharedPreferences sp_settings = getSharedPreferences("settings_data", 0);
+        device_id = sp_settings.getString("device_id", "NULL");
+
+        if (device_id.equals("NULL")) {
+            device_id = getUniqueKey();
+            SharedPreferences.Editor Editor = sp_settings.edit();
+            Editor.putString("device_id", device_id);
+            Editor.putString("device_created", String.valueOf(System.currentTimeMillis()));
+            Editor.apply();
+
+        }*/
+
+        PRNGFixes.apply();
+        StatusListener mStatus = new StatusListener();
+        mStatus.init(getApplicationContext());
+
+        //strongswan
+        StrongSwanApplication.mContext = getApplicationContext();
+    }
+
+    private void createNotificationChannel() {
+        try {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                NotificationChannel serviceChannel = new NotificationChannel(
+                        AppConstant.NOTIFICATION_CHANNEL_ID,
+                        getString(R.string.permanent_notification_name),
+                        NotificationManager.IMPORTANCE_LOW
+                );
+
+                serviceChannel.setSound(null, null);
+                manager = getSystemService(NotificationManager.class);
+                manager.createNotificationChannel(serviceChannel);
+            }
+        } catch (Exception e) {
+            //Log.e("error", e.getStackTrace()[0].getMethodName());
+        }
+    }
+
+    private String getUniqueKey() {
+        Calendar now = Calendar.getInstance();
+        int year = now.get(Calendar.YEAR);
+        int month = now.get(Calendar.MONTH);
+        int day = now.get(Calendar.DAY_OF_MONTH);
+        int hour = now.get(Calendar.HOUR_OF_DAY);
+        int minute = now.get(Calendar.MINUTE);
+        int second = now.get(Calendar.SECOND);
+        int millis = now.get(Calendar.MILLISECOND);
+        String Time = getResources().getString(R.string.get_time, year, month, day, hour, minute, second, millis);
+
+        String str_api = String.valueOf(Build.VERSION.SDK_INT); // API
+        String str_model = String.valueOf(Build.MODEL); // Model
+        String str_manufacturer = String.valueOf(Build.MANUFACTURER); // Manufacturer
+        String version;
+        try {
+            PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+            version = pInfo.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            version = "00";
+        }
+
+        Log.e("key", Time + str_manufacturer + str_api + str_model + version);
+        return Time + str_manufacturer + str_api + str_model + version;
+    }
+
+//    public static boolean isConnectedOpenWifi1() {
+//        WifiManager wifiManager1 = (WifiManager) getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+//
+//        WifiManager wifiManager2 = (WifiManager) App.getApplication().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+//        Log.d("OpenWiFi test t...", "Testing...");
+//
+//        if (wifiManager2 == null) {
+//            Log.e("OpenWiFi", "Wi-Fi is not supported on this device.");
+//            return false;
+//        }
+//
+////        if (!wifiManager.isWifiEnabled()) {
+////            Log.d("OpenWiFi test wifi en", "Not Enable");
+////            wifiManager.setWifiEnabled(true);
+////        }
+//
+//        if (wifiManager2.isWifiEnabled())
+//        {
+//            boolean status = wifiManager2.startScan();
+//
+//            Log.d("OpenWiFi test scan stat", "Status = " + status);
+//
+//            List<ScanResult> scanResults = wifiManager2.getScanResults();
+//
+//            Log.d("OpenWiFi test scan", String.valueOf(scanResults.size()) + status);
+//
+//            for (ScanResult scanResult : scanResults){
+//                String ssid = scanResult.SSID;
+//                String securityType = getSecurityType(scanResult);
+//
+//                Log.d("OpenWiFi test type", securityType);
+//
+//                if (securityType.equals("Open")){
+//                    Log.d("OpenWiFi test", "Open Wi-Fi network: " + ssid);
+//                    return true;
+//                }
+//            }
+//        }
+//
+//        return false;
+//    }
+
+//    private static String getSecurityType(ScanResult result) {
+//        String securityType = "Unknown";
+//
+//        if (result.capabilities.contains("WPA")) {
+//            securityType = "WPA";
+//        } else if (result.capabilities.contains("WEP")) {
+//            securityType = "WEP";
+//        } else if (result.capabilities.contains("EAP")) {
+//            securityType = "EAP";
+//        } else if (result.capabilities.contains("PSK")) {
+//            securityType = "WPA-PSK";
+//        } else if (result.capabilities.contains("SAE")) {
+//            securityType = "WPA3-SAE";
+//        } else if (result.capabilities.contains("OWE")) {
+//            securityType = "WPA3-OWE";
+//        } else if (result.capabilities.contains("MFP")) {
+//            securityType = "WPA2-MFP";
+//        } else if (result.capabilities.contains("WAPI-KEY")) {
+//            securityType = "WAPI";
+//        } else if (result.capabilities.contains("ESS")){
+//            securityType = "Open";
+//        }
+//        else {
+//            securityType = "Open";
+//        }
+//
+//        return securityType;
+//    }
+
+
+
+}

+ 59 - 0
app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+import java.util.Locale;
+
+class CIDRIP {
+    String mIp;
+    int len;
+    public CIDRIP(String ip, String mask) {
+        mIp = ip;
+        long netmask = getInt(mask);
+        // Add 33. bit to ensure the loop terminates
+        netmask += 1l << 32;
+        int lenZeros = 0;
+        while ((netmask & 0x1) == 0) {
+            lenZeros++;
+            netmask = netmask >> 1;
+        }
+        // Check if rest of netmask is only 1s
+        if (netmask != (0x1ffffffffl >> lenZeros)) {
+            // Asume no CIDR, set /32
+            len = 32;
+        } else {
+            len = 32 - lenZeros;
+        }
+    }
+    public CIDRIP(String address, int prefix_length) {
+        len = prefix_length;
+        mIp = address;
+    }
+    static long getInt(String ipaddr) {
+        String[] ipt = ipaddr.split("\\.");
+        long ip = 0;
+        ip += Long.parseLong(ipt[0]) << 24;
+        ip += Integer.parseInt(ipt[1]) << 16;
+        ip += Integer.parseInt(ipt[2]) << 8;
+        ip += Integer.parseInt(ipt[3]);
+        return ip;
+    }
+    @Override
+    public String toString() {
+        return String.format(Locale.ENGLISH, "%s/%d", mIp, len);
+    }
+    public boolean normalise() {
+        long ip = getInt(mIp);
+        long newip = ip & (0xffffffffL << (32 - len));
+        if (newip != ip) {
+            mIp = String.format(Locale.US, "%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff);
+            return true;
+        } else {
+            return false;
+        }
+    }
+    public long getInt() {
+        return getInt(mIp);
+    }
+}

ファイルの差分が大きいため隠しています
+ 698 - 0
app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java


+ 54 - 0
app/src/main/java/de/blinkt/openvpn/core/Connection.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.text.TextUtils;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+public class Connection implements Serializable, Cloneable {
+    public static final int CONNECTION_DEFAULT_TIMEOUT = 120;
+    private static final long serialVersionUID = 92031902903829089L;
+    public String mServerName = "openvpn.example.com";
+    public String mServerPort = "1194";
+    public boolean mUseUdp = true;
+    public String mCustomConfiguration = "";
+    public boolean mUseCustomConfig = false;
+    public boolean mEnabled = true;
+    public int mConnectTimeout = 0;
+    public String getConnectionBlock() {
+        String cfg = "";
+        // Server Address
+        cfg += "remote ";
+        cfg += mServerName;
+        cfg += " ";
+        cfg += mServerPort;
+        if (mUseUdp)
+            cfg += " udp\n";
+        else
+            cfg += " tcp-client\n";
+        if (mConnectTimeout != 0)
+            cfg += String.format(Locale.US, " connect-timeout  %d\n", mConnectTimeout);
+        if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) {
+            cfg += mCustomConfiguration;
+            cfg += "\n";
+        }
+        return cfg;
+    }
+    @Override
+    public Connection clone() throws CloneNotSupportedException {
+        return (Connection) super.clone();
+    }
+    public boolean isOnlyRemote() {
+        return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig;
+    }
+    public int getTimeout() {
+        if (mConnectTimeout <= 0)
+            return CONNECTION_DEFAULT_TIMEOUT;
+        else
+            return mConnectTimeout;
+    }
+}

+ 41 - 0
app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Created by arne on 08.11.16.
+ */
+public enum ConnectionStatus implements Parcelable {
+    LEVEL_CONNECTED,
+    LEVEL_VPNPAUSED,
+    LEVEL_CONNECTING_SERVER_REPLIED,
+    LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
+    LEVEL_NONETWORK,
+    LEVEL_NOTCONNECTED,
+    LEVEL_START,
+    LEVEL_AUTH_FAILED,
+    LEVEL_WAITING_FOR_USER_INPUT,
+    UNKNOWN_LEVEL;
+    public static final Creator<ConnectionStatus> CREATOR = new Creator<ConnectionStatus>() {
+        @Override
+        public ConnectionStatus createFromParcel(Parcel in) {
+            return ConnectionStatus.values()[in.readInt()];
+        }
+        @Override
+        public ConnectionStatus[] newArray(int size) {
+            return new ConnectionStatus[size];
+        }
+    };
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(ordinal());
+    }
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}

+ 219 - 0
app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java

@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.os.Handler;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.util.LinkedList;
+
+import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
+
+public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener, OpenVPNManagement.PausedStateCallback {
+    private final Handler mDisconnectHandler;
+    // Data traffic limit in bytes
+    private final long TRAFFIC_LIMIT = 64 * 1024;
+    connectState network = connectState.DISCONNECTED;
+    connectState screen = connectState.SHOULDBECONNECTED;
+    connectState userpause = connectState.SHOULDBECONNECTED;
+    private OpenVPNManagement mManagement;
+    private String lastStateMsg = null;
+    private Runnable mDelayDisconnectRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (!(network == connectState.PENDINGDISCONNECT)) return;
+            network = connectState.DISCONNECTED;
+            // Set screen state to be disconnected if disconnect pending
+            if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED;
+            mManagement.pause(getPauseReason());
+        }
+    };
+    private NetworkInfo lastConnectedNetwork;
+    private LinkedList<Datapoint> trafficdata = new LinkedList<>();
+
+    public DeviceStateReceiver(OpenVPNManagement magnagement) {
+        super();
+        mManagement = magnagement;
+        mManagement.setPauseCallback(this);
+        mDisconnectHandler = new Handler();
+    }
+
+    public static boolean equalsObj(Object a, Object b) {
+        return (a == null) ? (b == null) : a.equals(b);
+    }
+
+    @Override
+    public boolean shouldBeRunning() {
+        return shouldBeConnected();
+    }
+
+    @Override
+    public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+        if (screen != connectState.PENDINGDISCONNECT) return;
+        long total = diffIn + diffOut;
+        trafficdata.add(new Datapoint(System.currentTimeMillis(), total));
+        // Window time in s
+        int TRAFFIC_WINDOW = 60;
+        while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) {
+            trafficdata.removeFirst();
+        }
+        long windowtraffic = 0;
+        for (Datapoint dp : trafficdata)
+            windowtraffic += dp.data;
+        if (windowtraffic < TRAFFIC_LIMIT) {
+            screen = connectState.DISCONNECTED;
+            VpnStatus.logInfo(R.string.screenoff_pause, "64 kB", TRAFFIC_WINDOW);
+            mManagement.pause(getPauseReason());
+        }
+    }
+
+    public void userPause(boolean pause) {
+        if (pause) {
+            userpause = connectState.DISCONNECTED;
+            // Check if we should disconnect
+            mManagement.pause(getPauseReason());
+        } else {
+            boolean wereConnected = shouldBeConnected();
+            userpause = connectState.SHOULDBECONNECTED;
+            if (shouldBeConnected() && !wereConnected) mManagement.resume();
+            else
+                // Update the reason why we currently paused
+                mManagement.pause(getPauseReason());
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
+        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
+            networkStateChange(context);
+        } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+            boolean screenOffPause = prefs.getBoolean("screenoff", false);
+            if (screenOffPause) {
+                if (ProfileManager.getLastConnectedVpn() != null && !ProfileManager.getLastConnectedVpn().mPersistTun) VpnStatus.logError(R.string.screen_nopersistenttun);
+                screen = connectState.PENDINGDISCONNECT;
+                fillTrafficData();
+                if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) screen = connectState.DISCONNECTED;
+            }
+        } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
+            // Network was disabled because screen off
+            boolean connected = shouldBeConnected();
+            screen = connectState.SHOULDBECONNECTED;
+            /* We should connect now, cancel any outstanding disconnect timer */
+            mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable);
+            /* should be connected has changed because the screen is on now, connect the VPN */
+            if (shouldBeConnected() != connected) mManagement.resume();
+            else if (!shouldBeConnected())
+                /*Update the reason why we are still paused */ mManagement.pause(getPauseReason());
+        }
+    }
+
+    private void fillTrafficData() {
+        trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT));
+    }
+
+    public void networkStateChange(Context context) {
+        NetworkInfo networkInfo = getCurrentNetworkInfo(context);
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
+        boolean sendusr1 = prefs.getBoolean("netchangereconnect", true);
+        String netstatestring;
+        if (networkInfo == null) {
+            netstatestring = "not connected";
+        } else {
+            String subtype = networkInfo.getSubtypeName();
+            if (subtype == null) subtype = "";
+            String extrainfo = networkInfo.getExtraInfo();
+            if (extrainfo == null) extrainfo = "";
+            /*
+            if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) {
+				WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+				WifiInfo wifiinfo = wifiMgr.getConnectionInfo();
+				extrainfo+=wifiinfo.getBSSID();
+				subtype += wifiinfo.getNetworkId();
+			}*/
+            netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), networkInfo.getDetailedState(), extrainfo, subtype);
+        }
+        int lastNetwork = -1;
+        if (networkInfo != null && networkInfo.getState() == State.CONNECTED) {
+            int newnet = networkInfo.getType();
+            boolean pendingDisconnect = (network == connectState.PENDINGDISCONNECT);
+            network = connectState.SHOULDBECONNECTED;
+            boolean sameNetwork;
+            sameNetwork = !(lastConnectedNetwork == null || lastConnectedNetwork.getType() != networkInfo.getType() || !equalsObj(lastConnectedNetwork.getExtraInfo(), networkInfo.getExtraInfo()));
+            /* Same network, connection still 'established' */
+            if (pendingDisconnect && sameNetwork) {
+                mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable);
+                // Reprotect the sockets just be sure
+                mManagement.networkChange(true);
+            } else {
+                /* Different network or connection not established anymore */
+                if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED;
+                if (shouldBeConnected()) {
+                    mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable);
+                    if (pendingDisconnect || !sameNetwork) mManagement.networkChange(sameNetwork);
+                    else mManagement.resume();
+                }
+                lastNetwork = newnet;
+                lastConnectedNetwork = networkInfo;
+            }
+        } else if (networkInfo == null) {
+            // Not connected, stop openvpn, set last connected network to no network
+            lastNetwork = -1;
+            if (sendusr1) {
+                network = connectState.PENDINGDISCONNECT;
+                // Time to wait after network disconnect to pause the VPN
+                int DISCONNECT_WAIT = 20;
+                mDisconnectHandler.postDelayed(mDelayDisconnectRunnable, DISCONNECT_WAIT * 1000);
+            }
+        }
+        if (!netstatestring.equals(lastStateMsg)) VpnStatus.logInfo(R.string.netstatus, netstatestring);
+        VpnStatus.logDebug(String.format("Debug state info: %s, pause: %s, shouldbeconnected: %s, network: %s ", netstatestring, getPauseReason(), shouldBeConnected(), network));
+        lastStateMsg = netstatestring;
+    }
+
+    public boolean isUserPaused() {
+        return userpause == connectState.DISCONNECTED;
+    }
+
+    private boolean shouldBeConnected() {
+        return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && network == connectState.SHOULDBECONNECTED);
+    }
+
+    private pauseReason getPauseReason() {
+        if (userpause == connectState.DISCONNECTED) return pauseReason.userPause;
+        if (screen == connectState.DISCONNECTED) return pauseReason.screenOff;
+        if (network == connectState.DISCONNECTED) return pauseReason.noNetwork;
+        return pauseReason.userPause;
+    }
+
+    private NetworkInfo getCurrentNetworkInfo(Context context) {
+        ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        return conn.getActiveNetworkInfo();
+    }
+
+    private enum connectState {
+        SHOULDBECONNECTED, PENDINGDISCONNECT, DISCONNECTED
+    }
+
+    private static class Datapoint {
+        long timestamp;
+        long data;
+
+        private Datapoint(long t, long d) {
+            timestamp = t;
+            data = d;
+        }
+    }
+}

+ 200 - 0
app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java

@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2012-2015 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.Locale;
+
+/**
+ * Created by arne on 23.01.16.
+ */
+class LogFileHandler extends Handler {
+    public static final int LOG_MESSAGE = 103;
+    public static final int MAGIC_BYTE = 0x55;
+    public static final String LOGFILE_NAME = "logcache.dat";
+    static final int TRIM_LOG_FILE = 100;
+    static final int FLUSH_TO_DISK = 101;
+    static final int LOG_INIT = 102;
+    private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
+    protected OutputStream mLogFile;
+    public LogFileHandler(Looper looper) {
+        super(looper);
+    }
+    public static String bytesToHex(byte[] bytes, int len) {
+        len = Math.min(bytes.length, len);
+        char[] hexChars = new char[len * 2];
+        for (int j = 0; j < len; j++) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2] = hexArray[v >>> 4];
+            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+        }
+        return new String(hexChars);
+    }
+    @Override
+    public void handleMessage(Message msg) {
+        try {
+            if (msg.what == LOG_INIT) {
+                if (mLogFile != null)
+                    throw new RuntimeException("mLogFile not null");
+                readLogCache((File) msg.obj);
+                openLogFile((File) msg.obj);
+            } else if (msg.what == LOG_MESSAGE && msg.obj instanceof LogItem) {
+                // Ignore log messages if not yet initialized
+                if (mLogFile == null)
+                    return;
+                writeLogItemToDisk((LogItem) msg.obj);
+            } else if (msg.what == TRIM_LOG_FILE) {
+                trimLogFile();
+                for (LogItem li : VpnStatus.getlogbuffer())
+                    writeLogItemToDisk(li);
+            } else if (msg.what == FLUSH_TO_DISK) {
+                flushToDisk();
+            }
+        } catch (IOException | BufferOverflowException e) {
+            e.printStackTrace();
+            VpnStatus.logError("Error during log cache: " + msg.what);
+            VpnStatus.logException(e);
+        }
+    }
+    private void flushToDisk() throws IOException {
+        mLogFile.flush();
+    }
+    private void trimLogFile() {
+        try {
+            mLogFile.flush();
+            ((FileOutputStream) mLogFile).getChannel().truncate(0);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    private void writeLogItemToDisk(LogItem li) throws IOException {
+        // We do not really care if the log cache breaks between Android upgrades,
+        // write binary format to disc
+        byte[] liBytes = li.getMarschaledBytes();
+        writeEscapedBytes(liBytes);
+    }
+    public void writeEscapedBytes(byte[] bytes) throws IOException {
+        int magic = 0;
+        for (byte b : bytes)
+            if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1)
+                magic++;
+        byte eBytes[] = new byte[bytes.length + magic];
+        int i = 0;
+        for (byte b : bytes) {
+            if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) {
+                eBytes[i++] = MAGIC_BYTE + 1;
+                eBytes[i++] = (byte) (b - MAGIC_BYTE);
+            } else {
+                eBytes[i++] = b;
+            }
+        }
+        byte[] lenBytes = ByteBuffer.allocate(4).putInt(bytes.length).array();
+        synchronized (mLogFile) {
+            mLogFile.write(MAGIC_BYTE);
+            mLogFile.write(lenBytes);
+            mLogFile.write(eBytes);
+        }
+    }
+    private void openLogFile(File cacheDir) throws FileNotFoundException {
+        File logfile = new File(cacheDir, LOGFILE_NAME);
+        mLogFile = new FileOutputStream(logfile);
+    }
+    private void readLogCache(File cacheDir) {
+        try {
+            File logfile = new File(cacheDir, LOGFILE_NAME);
+            if (!logfile.exists() || !logfile.canRead())
+                return;
+            readCacheContents(new FileInputStream(logfile));
+        } catch (IOException | RuntimeException e) {
+            VpnStatus.logError("Reading cached logfile failed");
+            VpnStatus.logException(e);
+            e.printStackTrace();
+            // ignore reading file error
+        } finally {
+            synchronized (VpnStatus.readFileLock) {
+                VpnStatus.readFileLog = true;
+                VpnStatus.readFileLock.notifyAll();
+            }
+        }
+    }
+    protected void readCacheContents(InputStream in) throws IOException {
+        BufferedInputStream logFile = new BufferedInputStream(in);
+        byte[] buf = new byte[16384];
+        int read = logFile.read(buf, 0, 5);
+        int itemsRead = 0;
+        readloop:
+        while (read >= 5) {
+            int skipped = 0;
+            while (buf[skipped] != MAGIC_BYTE) {
+                skipped++;
+                if (!(logFile.read(buf, skipped + 4, 1) == 1) || skipped + 10 > buf.length) {
+                    VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes and no a magic byte found", skipped));
+                    break readloop;
+                }
+            }
+            if (skipped > 0)
+                VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes before finding a magic byte", skipped));
+            int len = ByteBuffer.wrap(buf, skipped + 1, 4).asIntBuffer().get();
+            // Marshalled LogItem
+            int pos = 0;
+            byte buf2[] = new byte[buf.length];
+            while (pos < len) {
+                byte b = (byte) logFile.read();
+                if (b == MAGIC_BYTE) {
+                    VpnStatus.logDebug(String.format(Locale.US, "Unexpected magic byte found at pos %d, abort current log item", pos));
+                    read = logFile.read(buf, 1, 4) + 1;
+                    continue readloop;
+                } else if (b == MAGIC_BYTE + 1) {
+                    b = (byte) logFile.read();
+                    if (b == 0)
+                        b = MAGIC_BYTE;
+                    else if (b == 1)
+                        b = MAGIC_BYTE + 1;
+                    else {
+                        VpnStatus.logDebug(String.format(Locale.US, "Escaped byte not 0 or 1: %d", b));
+                        read = logFile.read(buf, 1, 4) + 1;
+                        continue readloop;
+                    }
+                }
+                buf2[pos++] = b;
+            }
+            restoreLogItem(buf2, len);
+            //Next item
+            read = logFile.read(buf, 0, 5);
+            itemsRead++;
+            if (itemsRead > 2 * VpnStatus.MAXLOGENTRIES) {
+                VpnStatus.logError("Too many logentries read from cache, aborting.");
+                read = 0;
+            }
+        }
+        VpnStatus.logDebug(R.string.reread_log, itemsRead);
+    }
+    protected void restoreLogItem(byte[] buf, int len) throws UnsupportedEncodingException {
+        LogItem li = new LogItem(buf, len);
+        if (li.verify()) {
+            VpnStatus.newLogItem(li, true);
+        } else {
+            VpnStatus.logError(String.format(Locale.getDefault(),
+                    "Could not read log item from file: %d: %s",
+                    len, bytesToHex(buf, Math.max(len, 80))));
+        }
+    }
+}

+ 316 - 0
app/src/main/java/de/blinkt/openvpn/core/LogItem.java

@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.FormatFlagsConversionMismatchException;
+import java.util.Locale;
+import java.util.UnknownFormatConversionException;
+
+/**
+ * Created by arne on 24.04.16.
+ */
+public class LogItem implements Parcelable {
+    public static final Creator<LogItem> CREATOR = new Creator<LogItem>() {
+        public LogItem createFromParcel(Parcel in) {
+            return new LogItem(in);
+        }
+
+        public LogItem[] newArray(int size) {
+            return new LogItem[size];
+        }
+    };
+    // Default log priority
+    VpnStatus.LogLevel mLevel = VpnStatus.LogLevel.INFO;
+    private Object[] mArgs = null;
+    private String mMessage = null;
+    private int mRessourceId;
+    private long logtime = System.currentTimeMillis();
+    private int mVerbosityLevel = -1;
+
+    private LogItem(int ressourceId, Object[] args) {
+        mRessourceId = ressourceId;
+        mArgs = args;
+    }
+
+    public LogItem(VpnStatus.LogLevel level, int verblevel, String message) {
+        mMessage = message;
+        mLevel = level;
+        mVerbosityLevel = verblevel;
+    }
+
+    public LogItem(byte[] in, int length) throws UnsupportedEncodingException {
+        ByteBuffer bb = ByteBuffer.wrap(in, 0, length);
+        bb.get(); // ignore version
+        logtime = bb.getLong();
+        mVerbosityLevel = bb.getInt();
+        mLevel = VpnStatus.LogLevel.getEnumByValue(bb.getInt());
+        mRessourceId = bb.getInt();
+        int len = bb.getInt();
+        if (len == 0) {
+            mMessage = null;
+        } else {
+            if (len > bb.remaining()) throw new IndexOutOfBoundsException("String length " + len + " is bigger than remaining bytes " + bb.remaining());
+            byte[] utf8bytes = new byte[len];
+            bb.get(utf8bytes);
+            mMessage = new String(utf8bytes, "UTF-8");
+        }
+        int numArgs = bb.getInt();
+        if (numArgs > 30) {
+            throw new IndexOutOfBoundsException("Too many arguments for Logitem to unmarschal");
+        }
+        if (numArgs == 0) {
+            mArgs = null;
+        } else {
+            mArgs = new Object[numArgs];
+            for (int i = 0; i < numArgs; i++) {
+                char type = bb.getChar();
+                switch (type) {
+                    case 's':
+                        mArgs[i] = unmarschalString(bb);
+                        break;
+                    case 'i':
+                        mArgs[i] = bb.getInt();
+                        break;
+                    case 'd':
+                        mArgs[i] = bb.getDouble();
+                        break;
+                    case 'f':
+                        mArgs[i] = bb.getFloat();
+                        break;
+                    case 'l':
+                        mArgs[i] = bb.getLong();
+                        break;
+                    case '0':
+                        mArgs[i] = null;
+                        break;
+                    default:
+                        throw new UnsupportedEncodingException("Unknown format type: " + type);
+                }
+            }
+        }
+        if (bb.hasRemaining()) throw new UnsupportedEncodingException(bb.remaining() + " bytes left after unmarshaling everything");
+    }
+
+    public LogItem(Parcel in) {
+        mArgs = in.readArray(Object.class.getClassLoader());
+        mMessage = in.readString();
+        mRessourceId = in.readInt();
+        mLevel = VpnStatus.LogLevel.getEnumByValue(in.readInt());
+        mVerbosityLevel = in.readInt();
+        logtime = in.readLong();
+    }
+
+    public LogItem(VpnStatus.LogLevel loglevel, int ressourceId, Object... args) {
+        mRessourceId = ressourceId;
+        mArgs = args;
+        mLevel = loglevel;
+    }
+
+    public LogItem(VpnStatus.LogLevel loglevel, String msg) {
+        mLevel = loglevel;
+        mMessage = msg;
+    }
+
+    public LogItem(VpnStatus.LogLevel loglevel, int ressourceId) {
+        mRessourceId = ressourceId;
+        mLevel = loglevel;
+    }
+
+    // TextUtils.join will cause not macked exeception in tests ....
+    public static String join(CharSequence delimiter, Object[] tokens) {
+        StringBuilder sb = new StringBuilder();
+        boolean firstTime = true;
+        for (Object token : tokens) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(delimiter);
+            }
+            sb.append(token);
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeArray(mArgs);
+        dest.writeString(mMessage);
+        dest.writeInt(mRessourceId);
+        dest.writeInt(mLevel.getInt());
+        dest.writeInt(mVerbosityLevel);
+        dest.writeLong(logtime);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LogItem)) return obj.equals(this);
+        LogItem other = (LogItem) obj;
+        return Arrays.equals(mArgs, other.mArgs) && ((other.mMessage == null && mMessage == other.mMessage) || mMessage.equals(other.mMessage)) && mRessourceId == other.mRessourceId && ((mLevel == null && other.mLevel == mLevel) || other.mLevel.equals(mLevel)) && mVerbosityLevel == other.mVerbosityLevel && logtime == other.logtime;
+    }
+
+    public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException {
+        ByteBuffer bb = ByteBuffer.allocate(16384);
+        bb.put((byte) 0x0);               //version
+        bb.putLong(logtime);              //8
+        bb.putInt(mVerbosityLevel);      //4
+        bb.putInt(mLevel.getInt());
+        bb.putInt(mRessourceId);
+        if (mMessage == null || mMessage.length() == 0) {
+            bb.putInt(0);
+        } else {
+            marschalString(mMessage, bb);
+        }
+        if (mArgs == null || mArgs.length == 0) {
+            bb.putInt(0);
+        } else {
+            bb.putInt(mArgs.length);
+            for (Object o : mArgs) {
+                if (o instanceof String) {
+                    bb.putChar('s');
+                    marschalString((String) o, bb);
+                } else if (o instanceof Integer) {
+                    bb.putChar('i');
+                    bb.putInt((Integer) o);
+                } else if (o instanceof Float) {
+                    bb.putChar('f');
+                    bb.putFloat((Float) o);
+                } else if (o instanceof Double) {
+                    bb.putChar('d');
+                    bb.putDouble((Double) o);
+                } else if (o instanceof Long) {
+                    bb.putChar('l');
+                    bb.putLong((Long) o);
+                } else if (o == null) {
+                    bb.putChar('0');
+                } else {
+                    VpnStatus.logDebug("Unknown object for LogItem marschaling " + o);
+                    bb.putChar('s');
+                    marschalString(o.toString(), bb);
+                }
+            }
+        }
+        int pos = bb.position();
+        bb.rewind();
+        return Arrays.copyOf(bb.array(), pos);
+    }
+
+    private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException {
+        byte[] utf8bytes = str.getBytes("UTF-8");
+        bb.putInt(utf8bytes.length);
+        bb.put(utf8bytes);
+    }
+
+    private String unmarschalString(ByteBuffer bb) throws UnsupportedEncodingException {
+        int len = bb.getInt();
+        byte[] utf8bytes = new byte[len];
+        bb.get(utf8bytes);
+        return new String(utf8bytes, "UTF-8");
+    }
+
+    public String getString(Context c) {
+        try {
+            if (mMessage != null) {
+                return mMessage;
+            } else {
+                if (c != null) {
+                    if (mRessourceId == R.string.mobile_info) return getMobileInfoString(c);
+                    if (mArgs == null) return c.getString(mRessourceId);
+                    else return c.getString(mRessourceId, mArgs);
+                } else {
+                    String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId);
+                    if (mArgs != null) str += join("|", mArgs);
+                    return str;
+                }
+            }
+        } catch (UnknownFormatConversionException e) {
+            if (c != null) throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
+            else throw e;
+        } catch (FormatFlagsConversionMismatchException e) {
+            if (c != null) throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion());
+            else throw e;
+        }
+    }
+
+    public VpnStatus.LogLevel getLogLevel() {
+        return mLevel;
+    }
+
+    @Override
+    public String toString() {
+        return getString(null);
+    }
+
+    // The lint is wrong here
+    @SuppressLint("StringFormatMatches")
+    private String getMobileInfoString(Context c) {
+        c.getPackageManager();
+        String apksign = "error getting package signature";
+        String version = "error getting version";
+        try {
+            @SuppressLint("PackageManagerGetSignatures") Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            byte[] der = cert.getEncoded();
+            md.update(der);
+            byte[] digest = md.digest();
+            if (Arrays.equals(digest, VpnStatus.officalkey)) apksign = c.getString(R.string.official_build);
+            else if (Arrays.equals(digest, VpnStatus.officaldebugkey)) apksign = c.getString(R.string.debug_build);
+            else if (Arrays.equals(digest, VpnStatus.amazonkey)) apksign = "amazon version";
+            else if (Arrays.equals(digest, VpnStatus.fdroidkey)) apksign = "F-Droid built and signed version";
+            else apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName());
+            PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
+            version = packageinfo.versionName;
+        } catch (PackageManager.NameNotFoundException | CertificateException | NoSuchAlgorithmException ignored) {
+        }
+        Object[] argsext = Arrays.copyOf(mArgs, mArgs.length);
+        argsext[argsext.length - 1] = apksign;
+        argsext[argsext.length - 2] = version;
+        return c.getString(R.string.mobile_info, argsext);
+    }
+
+    public long getLogtime() {
+        return logtime;
+    }
+
+    public int getVerbosityLevel() {
+        if (mVerbosityLevel == -1) {
+            // Hack:
+            // For message not from OpenVPN, report the status level as log level
+            return mLevel.getInt();
+        }
+        return mVerbosityLevel;
+    }
+
+    public boolean verify() {
+        if (mLevel == null) return false;
+        return !(mMessage == null && mRessourceId == 0);
+    }
+}

+ 45 - 0
app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+import android.annotation.TargetApi;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Build;
+
+/**
+ * Created by arne on 26.11.14.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class LollipopDeviceStateListener extends ConnectivityManager.NetworkCallback {
+    private String mLastConnectedStatus;
+    private String mLastLinkProperties;
+    private String mLastNetworkCapabilities;
+    @Override
+    public void onAvailable(Network network) {
+        super.onAvailable(network);
+        if (!network.toString().equals(mLastConnectedStatus)) {
+            mLastConnectedStatus = network.toString();
+            VpnStatus.logDebug("Connected to " + mLastConnectedStatus);
+        }
+    }
+    @Override
+    public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+        super.onLinkPropertiesChanged(network, linkProperties);
+        if (!linkProperties.toString().equals(mLastLinkProperties)) {
+            mLastLinkProperties = linkProperties.toString();
+            VpnStatus.logDebug(String.format("Linkproperties of %s: %s", network, linkProperties));
+        }
+    }
+    @Override
+    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+        super.onCapabilitiesChanged(network, networkCapabilities);
+        if (!networkCapabilities.toString().equals(mLastNetworkCapabilities)) {
+            mLastNetworkCapabilities = networkCapabilities.toString();
+            VpnStatus.logDebug(String.format("Network capabilities of %s: %s", network, networkCapabilities));
+        }
+    }
+}

+ 21 - 0
app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java

@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.os.Build;
+
+import java.security.InvalidKeyException;
+
+public class NativeUtils {
+    static {
+        System.loadLibrary("opvpnutil");
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
+            System.loadLibrary("jbcrypto");
+    }
+    public static native byte[] rsasign(byte[] input, int pkey) throws InvalidKeyException;
+    public static native String[] getIfconfig() throws IllegalArgumentException;
+    static native void jniclose(int fdint);
+    public static native String getNativeAPI();
+}

+ 294 - 0
app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java

@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+// TODO uncomment_this_JC
+//import com.vpn.fastestvpnservice.BuildConfig;
+
+import java.math.BigInteger;
+import java.net.Inet6Address;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.PriorityQueue;
+import java.util.TreeSet;
+import java.util.Vector;
+
+public class NetworkSpace {
+    TreeSet<ipAddress> mIpAddresses = new TreeSet<ipAddress>();
+    public Collection<ipAddress> getNetworks(boolean included) {
+        Vector<ipAddress> ips = new Vector<ipAddress>();
+        for (ipAddress ip : mIpAddresses) {
+            if (ip.included == included)
+                ips.add(ip);
+        }
+        return ips;
+    }
+    public void clear() {
+        mIpAddresses.clear();
+    }
+    void addIP(CIDRIP cidrIp, boolean include) {
+        mIpAddresses.add(new ipAddress(cidrIp, include));
+    }
+    public void addIPSplit(CIDRIP cidrIp, boolean include) {
+        ipAddress newIP = new ipAddress(cidrIp, include);
+        ipAddress[] splitIps = newIP.split();
+        for (ipAddress split : splitIps)
+            mIpAddresses.add(split);
+    }
+    void addIPv6(Inet6Address address, int mask, boolean included) {
+        mIpAddresses.add(new ipAddress(address, mask, included));
+    }
+    TreeSet<ipAddress> generateIPList() {
+        PriorityQueue<ipAddress> networks = new PriorityQueue<ipAddress>(mIpAddresses);
+        TreeSet<ipAddress> ipsDone = new TreeSet<ipAddress>();
+        ipAddress currentNet = networks.poll();
+        if (currentNet == null)
+            return ipsDone;
+        while (currentNet != null) {
+            // Check if it and the next of it are compatible
+            ipAddress nextNet = networks.poll();
+            //if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet);
+            if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) {
+                // Everything good, no overlapping nothing to do
+                ipsDone.add(currentNet);
+                currentNet = nextNet;
+            } else {
+                // This network is smaller or equal to the next but has the same base address
+                if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) {
+                    if (currentNet.included == nextNet.included) {
+                        // Included in the next next and same type
+                        // Simply forget our current network
+                        currentNet = nextNet;
+                    } else {
+                        // our currentNet is included in next and types differ. Need to split the next network
+                        ipAddress[] newNets = nextNet.split();
+                        // TODO: The contains method of the Priority is stupid linear search
+                        // First add the second half to keep the order in networks
+                        if (!networks.contains(newNets[1]))
+                            networks.add(newNets[1]);
+                        if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) {
+                            //if (BuildConfig.DEBUG)
+                                //Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask);
+                            // Don't add the lower half that would conflict with currentNet
+                        } else {
+                            if (!networks.contains(newNets[0]))
+                                networks.add(newNets[0]);
+                        }
+                        // Keep currentNet as is
+                    }
+                } else {
+                    // TODO uncomment_this_JC
+//                    if (BuildConfig.DEBUG) {
+//                        //Assert.assertTrue(currentNet.networkMask < nextNet.networkMask);
+//                        //Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1);
+//                        //Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1);
+//                    }
+                    // This network is bigger than the next and last ip of current >= next
+                    //noinspection StatementWithEmptyBody
+                    if (currentNet.included == nextNet.included) {
+                        // Next network is in included in our network with the same type,
+                        // simply ignore the next and move on
+                    } else {
+                        // We need to split our network
+                        ipAddress[] newNets = currentNet.split();
+                        if (newNets[1].networkMask == nextNet.networkMask) {
+                            // TODO uncomment_this_JC
+//                            if (BuildConfig.DEBUG) {
+//                                //Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress()));
+//                                //Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress()));
+//                                // split second equal the next network, do not add it
+//                            }
+                            networks.add(nextNet);
+                        } else {
+                            // Add the smaller network first
+                            networks.add(newNets[1]);
+                            networks.add(nextNet);
+                        }
+                        currentNet = newNets[0];
+                    }
+                }
+            }
+        }
+        return ipsDone;
+    }
+    Collection<ipAddress> getPositiveIPList() {
+        TreeSet<ipAddress> ipsSorted = generateIPList();
+        Vector<ipAddress> ips = new Vector<ipAddress>();
+        for (ipAddress ia : ipsSorted) {
+            if (ia.included)
+                ips.add(ia);
+        }
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+            // Include postive routes from the original set under < 4.4 since these might overrule the local
+            // network but only if no smaller negative route exists
+            for (ipAddress origIp : mIpAddresses) {
+                if (!origIp.included)
+                    continue;
+                // The netspace exists
+                if (ipsSorted.contains(origIp))
+                    continue;
+                boolean skipIp = false;
+                // If there is any smaller net that is excluded we may not add the positive route back
+                for (ipAddress calculatedIp : ipsSorted) {
+                    if (!calculatedIp.included && origIp.containsNet(calculatedIp)) {
+                        skipIp = true;
+                        break;
+                    }
+                }
+                if (skipIp)
+                    continue;
+                // It is safe to include the IP
+                ips.add(origIp);
+            }
+        }
+        return ips;
+    }
+    static class ipAddress implements Comparable<ipAddress> {
+        public int networkMask;
+        private BigInteger netAddress;
+        private boolean included;
+        private boolean isV4;
+        private BigInteger firstAddress;
+        private BigInteger lastAddress;
+        public ipAddress(CIDRIP ip, boolean include) {
+            included = include;
+            netAddress = BigInteger.valueOf(ip.getInt());
+            networkMask = ip.len;
+            isV4 = true;
+        }
+        public ipAddress(Inet6Address address, int mask, boolean include) {
+            networkMask = mask;
+            included = include;
+            int s = 128;
+            netAddress = BigInteger.ZERO;
+            for (byte b : address.getAddress()) {
+                s -= 8;
+                netAddress = netAddress.add(BigInteger.valueOf((b & 0xFF)).shiftLeft(s));
+            }
+        }
+        ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) {
+            this.netAddress = baseAddress;
+            this.networkMask = mask;
+            this.included = included;
+            this.isV4 = isV4;
+        }
+        /**
+         * sorts the networks with following criteria:
+         * 1. compares first 1 of the network
+         * 2. smaller networks are returned as smaller
+         */
+        @Override
+        public int compareTo(@NonNull ipAddress another) {
+            int comp = getFirstAddress().compareTo(another.getFirstAddress());
+            if (comp != 0)
+                return comp;
+            if (networkMask > another.networkMask)
+                return -1;
+            else if (another.networkMask == networkMask)
+                return 0;
+            else
+                return 1;
+        }
+        /**
+         * Warning ignores the included integer
+         *
+         * @param o the object to compare this instance with.
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof ipAddress))
+                return super.equals(o);
+            ipAddress on = (ipAddress) o;
+            return (networkMask == on.networkMask) && on.getFirstAddress().equals(getFirstAddress());
+        }
+        public BigInteger getLastAddress() {
+            if (lastAddress == null)
+                lastAddress = getMaskedAddress(true);
+            return lastAddress;
+        }
+        public BigInteger getFirstAddress() {
+            if (firstAddress == null)
+                firstAddress = getMaskedAddress(false);
+            return firstAddress;
+        }
+        private BigInteger getMaskedAddress(boolean one) {
+            BigInteger numAddress = netAddress;
+            int numBits;
+            if (isV4) {
+                numBits = 32 - networkMask;
+            } else {
+                numBits = 128 - networkMask;
+            }
+            for (int i = 0; i < numBits; i++) {
+                if (one)
+                    numAddress = numAddress.setBit(i);
+                else
+                    numAddress = numAddress.clearBit(i);
+            }
+            return numAddress;
+        }
+        @Override
+        public String toString() {
+            //String in = included ? "+" : "-";
+            if (isV4)
+                return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask);
+            else
+                return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask);
+        }
+        public ipAddress[] split() {
+            ipAddress firstHalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4);
+            ipAddress secondHalf = new ipAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4);
+            //if (BuildConfig.DEBUG)
+                //Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress()));
+            return new ipAddress[]{firstHalf, secondHalf};
+        }
+        String getIPv4Address() {
+            // TODO uncomment_this_JC
+//            if (BuildConfig.DEBUG) {
+//                //Assert.assertTrue(isV4);
+//                //Assert.assertTrue(netAddress.longValue() <= 0xffffffffl);
+//                //Assert.assertTrue(netAddress.longValue() >= 0);
+//            }
+            long ip = netAddress.longValue();
+            return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256);
+        }
+        String getIPv6Address() {
+            //if (BuildConfig.DEBUG) Assert.assertTrue(!isV4);
+            BigInteger r = netAddress;
+            String ipv6str = null;
+            boolean lastPart = true;
+            while (r.compareTo(BigInteger.ZERO) == 1) {
+                long part = r.mod(BigInteger.valueOf(0x10000)).longValue();
+                if (ipv6str != null || part != 0) {
+                    if (ipv6str == null && !lastPart)
+                        ipv6str = ":";
+                    if (lastPart)
+                        ipv6str = String.format(Locale.US, "%x", part, ipv6str);
+                    else
+                        ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str);
+                }
+                r = r.shiftRight(16);
+                lastPart = false;
+            }
+            if (ipv6str == null)
+                return "::";
+            return ipv6str;
+        }
+        public boolean containsNet(ipAddress network) {
+            // this.first >= net.first &&  this.last <= net.last
+            BigInteger ourFirst = getFirstAddress();
+            BigInteger ourLast = getLastAddress();
+            BigInteger netFirst = network.getFirstAddress();
+            BigInteger netLast = network.getLastAddress();
+            boolean a = ourFirst.compareTo(netFirst) != 1;
+            boolean b = ourLast.compareTo(netLast) != -1;
+            return a && b;
+        }
+    }
+}

+ 29 - 0
app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+public interface OpenVPNManagement {
+    int mBytecountInterval = 2;
+    void reconnect();
+    void pause(pauseReason reason);
+    void resume();
+    /**
+     * @param replaceConnection True if the VPN is connected by a new connection.
+     * @return true if there was a process that has been send a stop signal
+     */
+    boolean stopVPN(boolean replaceConnection);
+    /*
+     * Rebind the interface
+     */
+    void networkChange(boolean sameNetwork);
+    void setPauseCallback(PausedStateCallback callback);
+    enum pauseReason {
+        noNetwork,
+        userPause,
+        screenOff,
+    }
+    interface PausedStateCallback {
+        boolean shouldBeRunning();
+    }
+}

ファイルの差分が大きいため隠しています
+ 1141 - 0
app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java


+ 195 - 0
app/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java

@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by arne on 08.11.16.
+ */
+public class OpenVPNStatusService extends Service implements VpnStatus.LogListener, VpnStatus.ByteCountListener, VpnStatus.StateListener {
+    static final RemoteCallbackList<IStatusCallbacks> mCallbacks =
+            new RemoteCallbackList<>();
+    private static final OpenVPNStatusHandler mHandler = new OpenVPNStatusHandler();
+    private static final int SEND_NEW_LOGITEM = 100;
+    private static final int SEND_NEW_STATE = 101;
+    private static final int SEND_NEW_BYTECOUNT = 102;
+    private static final int SEND_NEW_CONNECTED_VPN = 103;
+    static UpdateMessage mLastUpdateMessage;
+    private static final IServiceStatus.Stub mBinder = new IServiceStatus.Stub() {
+        @Override
+        public ParcelFileDescriptor registerStatusCallback(IStatusCallbacks cb) throws RemoteException {
+            final LogItem[] logbuffer = VpnStatus.getlogbuffer();
+            if (mLastUpdateMessage != null)
+                sendUpdate(cb, mLastUpdateMessage);
+            mCallbacks.register(cb);
+            try {
+                final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                new Thread("pushLogs") {
+                    @Override
+                    public void run() {
+                        DataOutputStream fd = new DataOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]));
+                        try {
+                            synchronized (VpnStatus.readFileLock) {
+                                if (!VpnStatus.readFileLog) {
+                                    VpnStatus.readFileLock.wait();
+                                }
+                            }
+                        } catch (InterruptedException e) {
+                            VpnStatus.logException(e);
+                        }
+                        try {
+                            for (LogItem logItem : logbuffer) {
+                                byte[] bytes = logItem.getMarschaledBytes();
+                                fd.writeShort(bytes.length);
+                                fd.write(bytes);
+                            }
+                            // Mark end
+                            fd.writeShort(0x7fff);
+                            fd.close();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }.start();
+                return pipe[0];
+            } catch (IOException e) {
+                e.printStackTrace();
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+                    throw new RemoteException(e.getMessage());
+                }
+                return null;
+            }
+        }
+        @Override
+        public void unregisterStatusCallback(IStatusCallbacks cb) throws RemoteException {
+            mCallbacks.unregister(cb);
+        }
+        @Override
+        public String getLastConnectedVPN() throws RemoteException {
+            return VpnStatus.getLastConnectedVPNProfile();
+        }
+        @Override
+        public void setCachedPassword(String uuid, int type, String password) {
+            PasswordCache.setCachedPassword(uuid, type, password);
+        }
+        @Override
+        public TrafficHistory getTrafficHistory() throws RemoteException {
+            return VpnStatus.trafficHistory;
+        }
+    };
+    private static void sendUpdate(IStatusCallbacks broadcastItem,
+                                   UpdateMessage um) throws RemoteException {
+        broadcastItem.updateStateString(um.state, um.logmessage, um.resId, um.level);
+    }
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        VpnStatus.addLogListener(this);
+        VpnStatus.addByteCountListener(this);
+        VpnStatus.addStateListener(this);
+        mHandler.setService(this);
+    }
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        VpnStatus.removeLogListener(this);
+        VpnStatus.removeByteCountListener(this);
+        VpnStatus.removeStateListener(this);
+        mCallbacks.kill();
+    }
+    @Override
+    public void newLog(LogItem logItem) {
+        Message msg = mHandler.obtainMessage(SEND_NEW_LOGITEM, logItem);
+        msg.sendToTarget();
+    }
+    @Override
+    public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+        Message msg = mHandler.obtainMessage(SEND_NEW_BYTECOUNT, Pair.create(in, out));
+        msg.sendToTarget();
+    }
+    @Override
+    public void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level) {
+        mLastUpdateMessage = new UpdateMessage(state, logmessage, localizedResId, level);
+        Message msg = mHandler.obtainMessage(SEND_NEW_STATE, mLastUpdateMessage);
+        msg.sendToTarget();
+    }
+    @Override
+    public void setConnectedVPN(String uuid) {
+        Message msg = mHandler.obtainMessage(SEND_NEW_CONNECTED_VPN, uuid);
+        msg.sendToTarget();
+    }
+    static class UpdateMessage {
+        public String state;
+        public String logmessage;
+        public ConnectionStatus level;
+        int resId;
+        UpdateMessage(String state, String logmessage, int resId, ConnectionStatus level) {
+            this.state = state;
+            this.resId = resId;
+            this.logmessage = logmessage;
+            this.level = level;
+        }
+    }
+    private static class OpenVPNStatusHandler extends Handler {
+        WeakReference<OpenVPNStatusService> service = null;
+        private void setService(OpenVPNStatusService statusService) {
+            service = new WeakReference<>(statusService);
+        }
+        @Override
+        public void handleMessage(Message msg) {
+            RemoteCallbackList<IStatusCallbacks> callbacks;
+            if (service == null || service.get() == null)
+                return;
+            callbacks = service.get().mCallbacks;
+            // Broadcast to all clients the new value.
+            final int N = callbacks.beginBroadcast();
+            for (int i = 0; i < N; i++) {
+                try {
+                    IStatusCallbacks broadcastItem = callbacks.getBroadcastItem(i);
+                    switch (msg.what) {
+                        case SEND_NEW_LOGITEM:
+                            broadcastItem.newLogItem((LogItem) msg.obj);
+                            break;
+                        case SEND_NEW_BYTECOUNT:
+                            Pair<Long, Long> inout = (Pair<Long, Long>) msg.obj;
+                            broadcastItem.updateByteCount(inout.first, inout.second);
+                            break;
+                        case SEND_NEW_STATE:
+                            sendUpdate(broadcastItem, (UpdateMessage) msg.obj);
+                            break;
+                        case SEND_NEW_CONNECTED_VPN:
+                            broadcastItem.connectedVPN((String) msg.obj);
+                            break;
+                    }
+                } catch (RemoteException e) {
+                    // The RemoteCallbackList will take care of removing
+                    // the dead object for us.
+                }
+            }
+            callbacks.finishBroadcast();
+        }
+    }
+}

+ 189 - 0
app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java

@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.annotation.SuppressLint;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+//import android.util.Log;
+
+public class OpenVPNThread implements Runnable {
+    public static final int M_FATAL = (1 << 4);
+    public static final int M_NONFATAL = (1 << 5);
+    public static final int M_WARN = (1 << 6);
+    public static final int M_DEBUG = (1 << 7);
+    private static final String DUMP_PATH_STRING = "Dump path: ";
+    @SuppressLint("SdCardPath")
+    private static final String BROKEN_PIE_SUPPORT = "/data/data/com.wxy.vpn2018/cache/pievpn";
+    private final static String BROKEN_PIE_SUPPORT2 = "syntax error";
+    private static final String TAG = "OpenVPN";
+    private String[] mArgv;
+    private Process mProcess;
+    private String mNativeDir;
+    private OpenVPNService mService;
+    private String mDumpPath;
+    private boolean mBrokenPie = false;
+    private boolean mNoProcessExitStatus = false;
+
+    public OpenVPNThread(OpenVPNService service, String[] argv, String nativelibdir) {
+        mArgv = argv;
+        mNativeDir = nativelibdir;
+        mService = service;
+    }
+
+    public void stopProcess() {
+        mProcess.destroy();
+    }
+
+    void setReplaceConnection() {
+        mNoProcessExitStatus = true;
+    }
+
+    @Override
+    public void run() {
+        try {
+//            Log.e(TAG, "Starting openvpn");
+            startOpenVPNThreadArgs(mArgv);
+//            Log.e(TAG, "OpenVPN process exited");
+        } catch (Exception e) {
+            VpnStatus.logException("Starting OpenVPN Thread", e);
+//            Log.e(TAG, "OpenVPNThread Got " + e.toString());
+        } finally {
+            int exitvalue = 0;
+            try {
+                if (mProcess != null) exitvalue = mProcess.waitFor();
+            } catch (IllegalThreadStateException ite) {
+                VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage());
+            } catch (InterruptedException ie) {
+                VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage());
+            }
+            if (exitvalue != 0) {
+                VpnStatus.logError("Process exited with exit value " + exitvalue);
+                if (mBrokenPie) {
+                    /* This will probably fail since the NoPIE binary is probably not written */
+                    String[] noPieArgv = VPNLaunchHelper.replacePieWithNoPie(mArgv);
+                    // We are already noPIE, nothing to gain
+                    if (!noPieArgv.equals(mArgv)) {
+                        mArgv = noPieArgv;
+                        VpnStatus.logInfo("PIE Version could not be executed. Trying no PIE version");
+                        run();
+                    }
+                }
+            }
+            if (!mNoProcessExitStatus) {
+                VpnStatus.updateStateString("NOPROCESS", "no process tap on connect button", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED);
+            }
+            if (mDumpPath != null) {
+                try {
+                    BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log"));
+                    SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.GERMAN);
+                    for (LogItem li : VpnStatus.getlogbuffer()) {
+                        String time = timeformat.format(new Date(li.getLogtime()));
+                        logout.write(time + " " + li.getString(mService) + "\n");
+                    }
+                    logout.close();
+                    VpnStatus.logError(R.string.minidump_generated);
+                } catch (IOException e) {
+                    VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage());
+                }
+            }
+            mService.processDied();
+//            Log.e(TAG, "Exiting");
+        }
+    }
+
+    private void startOpenVPNThreadArgs(String[] argv) {
+        LinkedList<String> argvlist = new LinkedList<>();
+        Collections.addAll(argvlist, argv);
+        ProcessBuilder pb = new ProcessBuilder(argvlist);
+        // Hack O rama
+        String lbpath = genLibraryPath(argv, pb);
+        pb.environment().put("LD_LIBRARY_PATH", lbpath);
+        pb.redirectErrorStream(true);
+        try {
+            mProcess = pb.start();
+            // Close the output, since we don't need it
+            mProcess.getOutputStream().close();
+            InputStream in = mProcess.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(in));
+            while (true) {
+                String logline = br.readLine();
+                if (logline == null) {
+                    return;
+                }
+                if (logline.startsWith(DUMP_PATH_STRING)) {
+                    mDumpPath = logline.substring(DUMP_PATH_STRING.length());
+                }
+                if (logline.startsWith(BROKEN_PIE_SUPPORT) || logline.contains(BROKEN_PIE_SUPPORT2)) {
+                    mBrokenPie = true;
+                }
+                // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com'
+                Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)");
+                Matcher m = p.matcher(logline);
+                int logerror = 0;
+                if (m.matches()) {
+                    int flags = Integer.parseInt(m.group(3), 16);
+                    String msg = m.group(4);
+                    int logLevel = flags & 0x0F;
+                    VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO;
+                    if ((flags & M_FATAL) != 0) {
+                        logStatus = VpnStatus.LogLevel.ERROR;
+                    } else if ((flags & M_NONFATAL) != 0) {
+                        logStatus = VpnStatus.LogLevel.WARNING;
+                    } else if ((flags & M_WARN) != 0) {
+                        logStatus = VpnStatus.LogLevel.WARNING;
+                    } else if ((flags & M_DEBUG) != 0) {
+                        logStatus = VpnStatus.LogLevel.VERBOSE;
+                    }
+                    if (msg.startsWith("MANAGEMENT: CMD")) {
+                        logLevel = Math.max(4, logLevel);
+                    }
+                    if ((msg.endsWith("md too weak") && msg.startsWith("OpenSSL: error")) || msg.contains("error:140AB18E")) {
+                        logerror = 1;
+                    }
+                    VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg);
+                    if (logerror == 1) {
+                        VpnStatus.logError("OpenSSL reproted a certificate with a weak hash, please the in app FAQ about weak hashes");
+                    }
+                } else {
+                    VpnStatus.logInfo("P:" + logline);
+                }
+                if (Thread.interrupted()) {
+                    throw new InterruptedException("OpenVpn process was killed form java code");
+                }
+            }
+        } catch (InterruptedException | IOException e) {
+            VpnStatus.logException("Error reading from output of OpenVPN process", e);
+            stopProcess();
+        }
+    }
+
+    private String genLibraryPath(String[] argv, ProcessBuilder pb) {
+        // Hack until I find a good way to get the real library path
+        String applibpath = argv[0].replaceFirst("/cache/.*$", "/lib");
+        String lbpath = pb.environment().get("LD_LIBRARY_PATH");
+        if (lbpath == null) lbpath = applibpath;
+        else lbpath = applibpath + ":" + lbpath;
+        if (!applibpath.equals(mNativeDir)) {
+            lbpath = mNativeDir + ":" + lbpath;
+        }
+        return lbpath;
+    }
+}

+ 549 - 0
app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java

@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.content.Context;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Vector;
+
+import de.blinkt.openvpn.VpnProfile;
+
+public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
+    private static final String TAG = "openvpn";
+    private static final Vector<OpenVpnManagementThread> active = new Vector<>();
+    private final Handler mResumeHandler;
+    private LocalSocket mSocket;
+    private VpnProfile mProfile;
+    private OpenVPNService mOpenVPNService;
+    private LinkedList<FileDescriptor> mFDList = new LinkedList<>();
+    private LocalServerSocket mServerSocket;
+    private boolean mWaitingForRelease = false;
+    private long mLastHoldRelease = 0;
+    private pauseReason lastPauseReason = pauseReason.noNetwork;
+    private PausedStateCallback mPauseCallback;
+    private boolean mShuttingDown;
+    private Runnable mResumeHoldRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (shouldBeRunning()) {
+                releaseHoldCmd();
+            }
+        }
+    };
+
+    public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) {
+        mProfile = profile;
+        mOpenVPNService = openVpnService;
+        mResumeHandler = new Handler(openVpnService.getMainLooper());
+    }
+
+    private static boolean stopOpenVPN() {
+        synchronized (active) {
+            boolean sendCMD = false;
+            for (OpenVpnManagementThread mt : active) {
+                sendCMD = mt.managmentCommand("signal SIGINT\n");
+                try {
+                    if (mt.mSocket != null) mt.mSocket.close();
+                } catch (IOException e) {
+                    // Ignore close error on already closed socket
+                }
+            }
+            return sendCMD;
+        }
+    }
+
+    public boolean openManagementInterface(@NonNull Context c) {
+        // Could take a while to open connection
+        int tries = 8;
+        String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
+        // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning
+        LocalSocket mServerSocketLocal = new LocalSocket();
+        while (tries > 0 && !mServerSocketLocal.isBound()) {
+            try {
+                mServerSocketLocal.bind(new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM));
+            } catch (IOException e) {
+                // wait 300 ms before retrying
+                try {
+                    Thread.sleep(300);
+                } catch (InterruptedException ignored) {
+                }
+            }
+            tries--;
+        }
+        try {
+            mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor());
+            return true;
+        } catch (IOException e) {
+            VpnStatus.logException(e);
+        }
+        return false;
+    }
+
+    /**
+     * @param cmd command to write to management socket
+     * @return true if command have been sent
+     */
+    public boolean managmentCommand(String cmd) {
+        try {
+            if (mSocket != null && mSocket.getOutputStream() != null) {
+                mSocket.getOutputStream().write(cmd.getBytes());
+                mSocket.getOutputStream().flush();
+                return true;
+            }
+        } catch (IOException e) {
+            // Ignore socket stack traces
+        }
+        return false;
+    }
+
+    @Override
+    public void run() {
+        byte[] buffer = new byte[2048];
+        //	mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad
+        String pendingInput = "";
+        synchronized (active) {
+            active.add(this);
+        }
+        try {
+            // Wait for a client to connect
+            mSocket = mServerSocket.accept();
+            InputStream instream = mSocket.getInputStream();
+            // Close the management socket after client connected
+            try {
+                mServerSocket.close();
+            } catch (IOException e) {
+                VpnStatus.logException(e);
+            }
+            // Closing one of the two sockets also closes the other
+            //mServerSocketLocal.close();
+            while (true) {
+                int numbytesread = instream.read(buffer);
+                if (numbytesread == -1) return;
+                FileDescriptor[] fds = null;
+                try {
+                    fds = mSocket.getAncillaryFileDescriptors();
+                } catch (IOException e) {
+                    VpnStatus.logException("Error reading fds from socket", e);
+                }
+                if (fds != null) {
+                    Collections.addAll(mFDList, fds);
+                }
+                String input = new String(buffer, 0, numbytesread, "UTF-8");
+                pendingInput += input;
+                pendingInput = processInput(pendingInput);
+            }
+        } catch (IOException e) {
+            if (!e.getMessage().equals("socket closed") && !e.getMessage().equals("Connection reset by peer")) VpnStatus.logException(e);
+        }
+        synchronized (active) {
+            active.remove(this);
+        }
+    }
+
+    //! Hack O Rama 2000!
+    private void protectFileDescriptor(FileDescriptor fd) {
+        try {
+            Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$");
+            int fdint = (Integer) getInt.invoke(fd);
+            // You can even get more evil by parsing toString() and extract the int from that :)
+            boolean result = mOpenVPNService.protect(fdint);
+            if (!result) VpnStatus.logWarning("Could not protect VPN socket");
+            //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint);
+            //pfd.close();
+            NativeUtils.jniclose(fdint);
+            return;
+        } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) {
+            VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", e);
+        }
+        //Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd);
+    }
+
+    private String processInput(String pendingInput) {
+        while (pendingInput.contains("\n")) {
+            String[] tokens = pendingInput.split("\\r?\\n", 2);
+            processCommand(tokens[0]);
+            if (tokens.length == 1)
+                // No second part, newline was at the end
+                pendingInput = "";
+            else pendingInput = tokens[1];
+        }
+        return pendingInput;
+    }
+
+    private void processCommand(String command) {
+        //Log.i(TAG, "Line from managment" + command);
+        if (command.startsWith(">") && command.contains(":")) {
+            String[] parts = command.split(":", 2);
+            String cmd = parts[0].substring(1);
+            String argument = parts[1];
+            switch (cmd) {
+                case "INFO":
+                /* Ignore greeting from management */
+                    return;
+                case "PASSWORD":
+                    processPWCommand(argument);
+                    break;
+                case "HOLD":
+                    handleHold(argument);
+                    break;
+                case "NEED-OK":
+                    processNeedCommand(argument);
+                    break;
+                case "BYTECOUNT":
+                    processByteCount(argument);
+                    break;
+                case "STATE":
+                    if (!mShuttingDown) processState(argument);
+                    break;
+                case "PROXY":
+                    processProxyCMD(argument);
+                    break;
+                case "LOG":
+                    processLogMessage(argument);
+                    break;
+                case "RSA_SIGN":
+                    processSignCommand(argument);
+                    break;
+                default:
+                    VpnStatus.logWarning("MGMT: Got unrecognized command" + command);
+                    //Log.i(TAG, "Got unrecognized command" + command);
+                    break;
+            }
+        } else if (command.startsWith("SUCCESS:")) {
+            /* Ignore this kind of message too */
+            return;
+        } else if (command.startsWith("PROTECTFD: ")) {
+            FileDescriptor fdtoprotect = mFDList.pollFirst();
+            if (fdtoprotect != null) protectFileDescriptor(fdtoprotect);
+        } else {
+            //Log.i(TAG, "Got unrecognized line from managment" + command);
+            VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command);
+        }
+    }
+
+    private void processLogMessage(String argument) {
+        String[] args = argument.split(",", 4);
+        // 0 unix time stamp
+        // 1 log level N,I,E etc.
+                /*
+                  (b) zero or more message flags in a single string:
+          I -- informational
+          F -- fatal error
+          N -- non-fatal error
+          W -- warning
+          D -- debug, and
+                 */
+        // 2 log message
+        //Log.d("OpenVPN", argument);
+        VpnStatus.LogLevel level;
+        switch (args[1]) {
+            case "I":
+                level = VpnStatus.LogLevel.INFO;
+                break;
+            case "W":
+                level = VpnStatus.LogLevel.WARNING;
+                break;
+            case "D":
+                level = VpnStatus.LogLevel.VERBOSE;
+                break;
+            case "F":
+                level = VpnStatus.LogLevel.ERROR;
+                break;
+            default:
+                level = VpnStatus.LogLevel.INFO;
+                break;
+        }
+        int ovpnlevel = Integer.parseInt(args[2]) & 0x0F;
+        String msg = args[3];
+        if (msg.startsWith("MANAGEMENT: CMD")) ovpnlevel = Math.max(4, ovpnlevel);
+        VpnStatus.logMessageOpenVPN(level, ovpnlevel, msg);
+    }
+
+    boolean shouldBeRunning() {
+        if (mPauseCallback == null) return false;
+        else return mPauseCallback.shouldBeRunning();
+    }
+
+    private void handleHold(String argument) {
+        mWaitingForRelease = true;
+        int waittime = Integer.parseInt(argument.split(":")[1]);
+        if (shouldBeRunning()) {
+            if (waittime > 1) VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime), R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
+            mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000);
+            if (waittime > 5) VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime));
+            else VpnStatus.logDebug(R.string.state_waitconnectretry, String.valueOf(waittime));
+        } else {
+            VpnStatus.updateStatePause(lastPauseReason);
+        }
+    }
+
+    private void releaseHoldCmd() {
+        mResumeHandler.removeCallbacks(mResumeHoldRunnable);
+        if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) {
+            try {
+                Thread.sleep(3000);
+            } catch (InterruptedException ignored) {
+            }
+        }
+        mWaitingForRelease = false;
+        mLastHoldRelease = System.currentTimeMillis();
+        managmentCommand("hold release\n");
+        managmentCommand("bytecount " + mBytecountInterval + "\n");
+        managmentCommand("state on\n");
+        //managmentCommand("log on all\n");
+    }
+
+    public void releaseHold() {
+        if (mWaitingForRelease) releaseHoldCmd();
+    }
+
+    private void processProxyCMD(String argument) {
+        String[] args = argument.split(",", 3);
+        SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
+        if (args.length >= 2) {
+            String proto = args[1];
+            if (proto.equals("UDP")) {
+                proxyaddr = null;
+            }
+        }
+        if (proxyaddr instanceof InetSocketAddress) {
+            InetSocketAddress isa = (InetSocketAddress) proxyaddr;
+            VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort());
+            String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort());
+            managmentCommand(proxycmd);
+        } else {
+            managmentCommand("proxy NONE\n");
+        }
+    }
+
+    private void processState(String argument) {
+        String[] args = argument.split(",", 3);
+        String currentstate = args[1];
+        if (args[2].equals(",,")) VpnStatus.updateStateString(currentstate, "");
+        else VpnStatus.updateStateString(currentstate, args[2]);
+    }
+
+    private void processByteCount(String argument) {
+        //   >BYTECOUNT:{BYTES_IN},{BYTES_OUT}
+        int comma = argument.indexOf(',');
+        long in = Long.parseLong(argument.substring(0, comma));
+        long out = Long.parseLong(argument.substring(comma + 1));
+        VpnStatus.updateByteCount(in, out);
+    }
+
+    private void processNeedCommand(String argument) {
+        int p1 = argument.indexOf('\'');
+        int p2 = argument.indexOf('\'', p1 + 1);
+        String needed = argument.substring(p1 + 1, p2);
+        String extra = argument.split(":", 2)[1];
+        String status = "ok";
+        switch (needed) {
+            case "PROTECTFD":
+                FileDescriptor fdtoprotect = mFDList.pollFirst();
+                protectFileDescriptor(fdtoprotect);
+                break;
+            case "DNSSERVER":
+            case "DNS6SERVER":
+                mOpenVPNService.addDNS(extra);
+                break;
+            case "DNSDOMAIN":
+                mOpenVPNService.setDomain(extra);
+                break;
+            case "ROUTE": {
+                String[] routeparts = extra.split(" ");
+            /*
+            buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface);
+            else
+            buf_printf (&out, "%s %s %s", network, netmask, gateway);
+            */
+                if (routeparts.length == 5) {
+                    //if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]);
+                    mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]);
+                } else if (routeparts.length >= 3) {
+                    mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null);
+                } else {
+                    VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument);
+                }
+                break;
+            }
+            case "ROUTE6": {
+                String[] routeparts = extra.split(" ");
+                mOpenVPNService.addRoutev6(routeparts[0], routeparts[1]);
+                break;
+            }
+            case "IFCONFIG":
+                String[] ifconfigparts = extra.split(" ");
+                int mtu = Integer.parseInt(ifconfigparts[2]);
+                mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]);
+                break;
+            case "IFCONFIG6":
+                mOpenVPNService.setLocalIPv6(extra);
+                break;
+            case "PERSIST_TUN_ACTION":
+                // check if tun cfg stayed the same
+                status = mOpenVPNService.getTunReopenStatus();
+                break;
+            case "OPENTUN":
+                if (sendTunFD(needed, extra)) return;
+                else status = "cancel";
+                // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :(
+                break;
+            default:
+                //Log.e(TAG, "Unknown needok command " + argument);
+                return;
+        }
+        String cmd = String.format("needok '%s' %s\n", needed, status);
+        managmentCommand(cmd);
+    }
+
+    private boolean sendTunFD(String needed, String extra) {
+        if (!extra.equals("tun")) {
+            // We only support tun
+            VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
+            return false;
+        }
+        ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+        if (pfd == null) return false;
+        Method setInt;
+        int fdint = pfd.getFd();
+        try {
+            setInt = FileDescriptor.class.getDeclaredMethod("setInt$", int.class);
+            FileDescriptor fdtosend = new FileDescriptor();
+            setInt.invoke(fdtosend, fdint);
+            FileDescriptor[] fds = {fdtosend};
+            mSocket.setFileDescriptorsForSend(fds);
+            // Trigger a send so we can close the fd on our side of the channel
+            // The API documentation fails to mention that it will not reset the file descriptor to
+            // be send and will happily send the file descriptor on every write ...
+            String cmd = String.format("needok '%s' %s\n", needed, "ok");
+            managmentCommand(cmd);
+            // Set the FileDescriptor to null to stop this mad behavior
+            mSocket.setFileDescriptorsForSend(null);
+            pfd.close();
+            return true;
+        } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IOException | IllegalAccessException exp) {
+            VpnStatus.logException("Could not send fd over socket", exp);
+        }
+        return false;
+    }
+
+    private void processPWCommand(String argument) {
+        //argument has the form 	Need 'Private Key' password
+        // or  ">PASSWORD:Verification Failed: '%s' ['%s']"
+        String needed;
+        try {
+            int p1 = argument.indexOf('\'');
+            int p2 = argument.indexOf('\'', p1 + 1);
+            needed = argument.substring(p1 + 1, p2);
+            if (argument.startsWith("Verification Failed")) {
+                proccessPWFailed(needed, argument.substring(p2 + 1));
+                return;
+            }
+        } catch (StringIndexOutOfBoundsException sioob) {
+            VpnStatus.logError("Could not parse management Password command: " + argument);
+            return;
+        }
+        String pw = null;
+        if (needed.equals("Private Key")) {
+            pw = mProfile.getPasswordPrivateKey();
+        } else if (needed.equals("Auth")) {
+            pw = mProfile.getPasswordAuth();
+            String usercmd = String.format("username '%s' %s\n", needed, VpnProfile.openVpnEscape(mProfile.mUsername));
+            managmentCommand(usercmd);
+        }
+        if (pw != null) {
+            String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
+            managmentCommand(cmd);
+        } else {
+            mOpenVPNService.requestInputFromUser(R.string.password, needed);
+            VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
+        }
+    }
+
+    private void proccessPWFailed(String needed, String args) {
+        VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED);
+    }
+
+    @Override
+    public void networkChange(boolean samenetwork) {
+        if (mWaitingForRelease) releaseHold();
+        else if (samenetwork) managmentCommand("network-change\n");
+        else managmentCommand("network-change\n");
+    }
+
+    @Override
+    public void setPauseCallback(PausedStateCallback callback) {
+        mPauseCallback = callback;
+    }
+
+    public void signalusr1() {
+        mResumeHandler.removeCallbacks(mResumeHoldRunnable);
+        if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n");
+        else
+            // If signalusr1 is called update the state string
+            // if there is another for stopping
+            VpnStatus.updateStatePause(lastPauseReason);
+    }
+
+    public void reconnect() {
+        signalusr1();
+        releaseHold();
+    }
+
+    private void processSignCommand(String b64data) {
+        String signed_string = mProfile.getSignedData(b64data);
+        if (signed_string == null) {
+            managmentCommand("rsa-sig\n");
+            managmentCommand("\nEND\n");
+            stopOpenVPN();
+            return;
+        }
+        managmentCommand("rsa-sig\n");
+        managmentCommand(signed_string);
+        managmentCommand("\nEND\n");
+    }
+
+    @Override
+    public void pause(pauseReason reason) {
+        lastPauseReason = reason;
+        signalusr1();
+    }
+
+    @Override
+    public void resume() {
+        releaseHold();
+        /* Reset the reason why we are disconnected */
+        lastPauseReason = pauseReason.noNetwork;
+    }
+
+    @Override
+    public boolean stopVPN(boolean replaceConnection) {
+        boolean stopSucceed = stopOpenVPN();
+        if (stopSucceed) {
+            mShuttingDown = true;
+        }
+        return stopSucceed;
+    }
+}

+ 298 - 0
app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java

@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;/*
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will Google be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ */
+
+import android.os.Build;
+import android.os.Process;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ * <p>
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's {@code onCreate}.
+ */
+public final class PRNGFixes {
+    private static final int VERSION_CODE_JELLY_BEAN = 16;
+    private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+    private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
+
+    /**
+     * Hidden constructor to prevent instantiation.
+     */
+    private PRNGFixes() {
+    }
+
+    /**
+     * Applies all fixes.
+     *
+     * @throws if a fix is needed but could not be applied.
+     */
+    public static void apply() {
+        applyOpenSSLFix();
+        installLinuxPRNGSecureRandom();
+    }
+
+    /**
+     * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+     * fix is not needed.
+     *
+     * @throws if the fix is needed but could not be applied.
+     */
+    private static void applyOpenSSLFix() throws SecurityException {
+        if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+            // No need to apply the fix
+            return;
+        }
+        try {
+            // Mix in the device- and invocation-specific seed.
+            Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
+            // Mix output of Linux PRNG into OpenSSL's PRNG
+            int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_load_file", String.class, long.class).invoke(null, "/dev/urandom", 1024);
+            if (bytesRead != 1024) {
+                throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead);
+            }
+        } catch (Exception e) {
+            throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+        }
+    }
+
+    /**
+     * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+     * default. Does nothing if the implementation is already the default or if
+     * there is not need to install the implementation.
+     *
+     * @throws if the fix is needed but could not be applied.
+     */
+    private static void installLinuxPRNGSecureRandom() throws SecurityException {
+        if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+            // No need to apply the fix
+            return;
+        }
+        // Install a Linux PRNG-based SecureRandom implementation as the
+        // default, if not yet installed.
+        Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
+        if ((secureRandomProviders == null) || (secureRandomProviders.length < 1) || (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) {
+            Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+        }
+        // Assert that new SecureRandom() and
+        // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+        // by the Linux PRNG-based SecureRandom implementation.
+        SecureRandom rng1 = new SecureRandom();
+        if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) {
+            throw new SecurityException("new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
+        }
+        SecureRandom rng2;
+        try {
+            rng2 = SecureRandom.getInstance("SHA1PRNG");
+        } catch (NoSuchAlgorithmException e) {
+            throw new SecurityException("SHA1PRNG not available", e);
+        }
+        if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider().getClass())) {
+            throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + rng2.getProvider().getClass());
+        }
+    }
+
+    /**
+     * Generates a device- and invocation-specific seed to be mixed into the
+     * Linux PRNG.
+     */
+    private static byte[] generateSeed() {
+        try {
+            ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+            DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
+            seedBufferOut.writeLong(System.currentTimeMillis());
+            seedBufferOut.writeLong(System.nanoTime());
+            seedBufferOut.writeInt(Process.myPid());
+            seedBufferOut.writeInt(Process.myUid());
+            seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+            seedBufferOut.close();
+            return seedBuffer.toByteArray();
+        } catch (IOException e) {
+            throw new SecurityException("Failed to generate seed", e);
+        }
+    }
+
+    /**
+     * Gets the hardware serial number of this device.
+     *
+     * @return serial number or {@code null} if not available.
+     */
+    private static String getDeviceSerialNumber() {
+        // We're using the Reflection API because Build.SERIAL is only available
+        // since API Level 9 (Gingerbread, Android 2.3).
+        try {
+            return (String) Build.class.getField("SERIAL").get(null);
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    private static byte[] getBuildFingerprintAndDeviceSerial() {
+        StringBuilder result = new StringBuilder();
+        String fingerprint = Build.FINGERPRINT;
+        if (fingerprint != null) {
+            result.append(fingerprint);
+        }
+        String serial = getDeviceSerialNumber();
+        if (serial != null) {
+            result.append(serial);
+        }
+        try {
+            return result.toString().getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("UTF-8 encoding not supported");
+        }
+    }
+
+    /**
+     * {@code Provider} of {@code SecureRandom} engines which pass through
+     * all requests to the Linux PRNG.
+     */
+    private static class LinuxPRNGSecureRandomProvider extends Provider {
+        public LinuxPRNGSecureRandomProvider() {
+            super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom");
+            // Although /dev/urandom is not a SHA-1 PRNG, some apps
+            // explicitly request a SHA1PRNG SecureRandom and we thus need to
+            // prevent them from getting the default implementation whose output
+            // may have low entropy.
+            put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+            put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+        }
+    }
+
+    /**
+     * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+     * ({@code /dev/urandom}).
+     */
+    public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+        /*
+         * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+         * are passed through to the Linux PRNG (/dev/urandom). Instances of
+         * this class seed themselves by mixing in the current time, PID, UID,
+         * build fingerprint, and hardware serial number (where available) into
+         * Linux PRNG.
+         *
+         * Concurrency: Read requests to the underlying Linux PRNG are
+         * serialized (on sLock) to ensure that multiple threads do not get
+         * duplicated PRNG output.
+         */
+        private static final File URANDOM_FILE = new File("/dev/urandom");
+        private static final Object sLock = new Object();
+        /**
+         * Input stream for reading from Linux PRNG or {@code null} if not yet
+         * opened.
+         *
+         * @GuardedBy("sLock")
+         */
+        private static DataInputStream sUrandomIn;
+        /**
+         * Output stream for writing to Linux PRNG or {@code null} if not yet
+         * opened.
+         *
+         * @GuardedBy("sLock")
+         */
+        private static OutputStream sUrandomOut;
+        /**
+         * Whether this engine instance has been seeded. This is needed because
+         * each instance needs to seed itself if the client does not explicitly
+         * seed it.
+         */
+        private boolean mSeeded;
+
+        @Override
+        protected void engineSetSeed(byte[] bytes) {
+            try {
+                OutputStream out;
+                synchronized (sLock) {
+                    out = getUrandomOutputStream();
+                }
+                out.write(bytes);
+                out.flush();
+            } catch (IOException e) {
+                // On a small fraction of devices /dev/urandom is not writable.
+                // Log and ignore.
+                //Log.w(PRNGFixes.class.getSimpleName(), "Failed to mix seed into " + URANDOM_FILE);
+            } finally {
+                mSeeded = true;
+            }
+        }
+
+        @Override
+        protected void engineNextBytes(byte[] bytes) {
+            if (!mSeeded) {
+                // Mix in the device- and invocation-specific seed.
+                engineSetSeed(generateSeed());
+            }
+            try {
+                DataInputStream in;
+                synchronized (sLock) {
+                    in = getUrandomInputStream();
+                }
+                synchronized (in) {
+                    in.readFully(bytes);
+                }
+            } catch (IOException e) {
+                throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
+            }
+        }
+
+        @Override
+        protected byte[] engineGenerateSeed(int size) {
+            byte[] seed = new byte[size];
+            engineNextBytes(seed);
+            return seed;
+        }
+
+        private DataInputStream getUrandomInputStream() {
+            synchronized (sLock) {
+                if (sUrandomIn == null) {
+                    // NOTE: Consider inserting a BufferedInputStream between
+                    // DataInputStream and FileInputStream if you need higher
+                    // PRNG output performance and can live with future PRNG
+                    // output being pulled into this process prematurely.
+                    try {
+                        sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
+                    } catch (IOException e) {
+                        throw new SecurityException("Failed to open " + URANDOM_FILE + " for reading", e);
+                    }
+                }
+                return sUrandomIn;
+            }
+        }
+
+        private OutputStream getUrandomOutputStream() throws IOException {
+            synchronized (sLock) {
+                if (sUrandomOut == null) {
+                    sUrandomOut = new FileOutputStream(URANDOM_FILE);
+                }
+                return sUrandomOut;
+            }
+        }
+    }
+}

+ 50 - 0
app/src/main/java/de/blinkt/openvpn/core/PasswordCache.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+import java.util.UUID;
+
+/**
+ * Created by arne on 15.12.16.
+ */
+public class PasswordCache {
+    public static final int PCKS12ORCERTPASSWORD = 2;
+    public static final int AUTHPASSWORD = 3;
+    private static PasswordCache mInstance;
+    final private UUID mUuid;
+    private String mKeyOrPkcs12Password;
+    private String mAuthPassword;
+    private PasswordCache(UUID uuid) {
+        mUuid = uuid;
+    }
+    public static PasswordCache getInstance(UUID uuid) {
+        if (mInstance == null || !mInstance.mUuid.equals(uuid)) {
+            mInstance = new PasswordCache(uuid);
+        }
+        return mInstance;
+    }
+    public static String getPKCS12orCertificatePassword(UUID uuid, boolean resetPw) {
+        String pwcopy = getInstance(uuid).mKeyOrPkcs12Password;
+        if (resetPw)
+            getInstance(uuid).mKeyOrPkcs12Password = null;
+        return pwcopy;
+    }
+    public static String getAuthPassword(UUID uuid, boolean resetPW) {
+        String pwcopy = getInstance(uuid).mAuthPassword;
+        if (resetPW)
+            getInstance(uuid).mAuthPassword = null;
+        return pwcopy;
+    }
+    public static void setCachedPassword(String uuid, int type, String password) {
+        PasswordCache instance = getInstance(UUID.fromString(uuid));
+        switch (type) {
+            case PCKS12ORCERTPASSWORD:
+                instance.mKeyOrPkcs12Password = password;
+                break;
+            case AUTHPASSWORD:
+                instance.mAuthPassword = password;
+                break;
+        }
+    }
+}

+ 20 - 0
app/src/main/java/de/blinkt/openvpn/core/Preferences.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Created by arne on 08.01.17.
+ */
+// Until I find a good solution
+public class Preferences {
+    static SharedPreferences getSharedPreferencesMulti(String name, Context c) {
+        return c.getSharedPreferences(name, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
+    }
+    public static SharedPreferences getDefaultSharedPreferences(Context c) {
+        return c.getSharedPreferences(c.getPackageName() + "_preferences", Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
+    }
+}

+ 214 - 0
app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java

@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import de.blinkt.openvpn.VpnProfile;
+
+public class ProfileManager {
+    private static final String PREFS_NAME = "VPNList";
+    private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile";
+    private static final String TEMPORARY_PROFILE_FILENAME = "temporary-vpn-profile";
+    private static ProfileManager instance;
+    private static VpnProfile mLastConnectedVpn = null;
+    private static VpnProfile tmpprofile = null;
+    private HashMap<String, VpnProfile> profiles = new HashMap<>();
+
+    private ProfileManager() {
+    }
+
+    private static VpnProfile get(String key) {
+        if (tmpprofile != null && tmpprofile.getUUIDString().equals(key)) return tmpprofile;
+        if (instance == null) return null;
+        return instance.profiles.get(key);
+    }
+
+    private static void checkInstance(Context context) {
+        if (instance == null) {
+            instance = new ProfileManager();
+            instance.loadVPNList(context);
+        }
+    }
+
+    synchronized public static ProfileManager getInstance(Context context) {
+        checkInstance(context);
+        return instance;
+    }
+
+    public static void setConntectedVpnProfileDisconnected(Context c) {
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
+        Editor prefsedit = prefs.edit();
+        prefsedit.putString(LAST_CONNECTED_PROFILE, null);
+        prefsedit.apply();
+    }
+
+    /**
+     * Sets the profile that is connected (to connect if the service restarts)
+     */
+    public static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) {
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
+        Editor prefsedit = prefs.edit();
+        prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString());
+        prefsedit.apply();
+        mLastConnectedVpn = connectedProfile;
+    }
+
+    /**
+     * Returns the profile that was last connected (to connect if the service restarts)
+     */
+    public static VpnProfile getLastConnectedProfile(Context c) {
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
+        String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null);
+        if (lastConnectedProfile != null) return get(c, lastConnectedProfile);
+        else return null;
+    }
+
+    public static void setTemporaryProfile(Context c, VpnProfile tmp) {
+        ProfileManager.tmpprofile = tmp;
+        saveProfile(c, tmp, true, true);
+    }
+
+    public static boolean isTempProfile() {
+        return mLastConnectedVpn != null && mLastConnectedVpn == tmpprofile;
+    }
+
+    private static void saveProfile(Context context, VpnProfile profile, boolean updateVersion, boolean isTemporary) {
+        if (updateVersion) profile.mVersion += 1;
+        ObjectOutputStream vpnFile;
+        String filename = profile.getUUID().toString() + ".vp";
+        if (isTemporary) filename = TEMPORARY_PROFILE_FILENAME + ".vp";
+        try {
+            vpnFile = new ObjectOutputStream(context.openFileOutput(filename, Activity.MODE_PRIVATE));
+            vpnFile.writeObject(profile);
+            vpnFile.flush();
+            vpnFile.close();
+        } catch (IOException e) {
+            VpnStatus.logException("saving VPN profile", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static VpnProfile get(Context context, String profileUUID) {
+        return get(context, profileUUID, 0, 10);
+    }
+
+    public static VpnProfile get(Context context, String profileUUID, int version, int tries) {
+        checkInstance(context);
+        VpnProfile profile = get(profileUUID);
+        int tried = 0;
+        while ((profile == null || profile.mVersion < version) && (tried++ < tries)) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ignored) {
+            }
+            instance.loadVPNList(context);
+            profile = get(profileUUID);
+            int ver = profile == null ? -1 : profile.mVersion;
+        }
+        if (tried > 5) {
+            int ver = profile == null ? -1 : profile.mVersion;
+            VpnStatus.logError(String.format(Locale.US, "Used x %d tries to get current version (%d/%d) of the profile", tried, ver, version));
+        }
+        return profile;
+    }
+
+    public static VpnProfile getLastConnectedVpn() {
+        return mLastConnectedVpn;
+    }
+
+    public static VpnProfile getAlwaysOnVPN(Context context) {
+        checkInstance(context);
+        SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
+        String uuid = prefs.getString("alwaysOnVpn", null);
+        return get(uuid);
+    }
+
+    public static void updateLRU(Context c, VpnProfile profile) {
+        profile.mLastUsed = System.currentTimeMillis();
+        // LRU does not change the profile, no need for the service to refresh
+        if (profile != tmpprofile) saveProfile(c, profile, false, false);
+    }
+
+    public Collection<VpnProfile> getProfiles() {
+        return profiles.values();
+    }
+
+    public VpnProfile getProfileByName(String name) {
+        for (VpnProfile vpnp : profiles.values()) {
+            if (vpnp.getName().equals(name)) {
+                return vpnp;
+            }
+        }
+        return null;
+    }
+
+    public void saveProfileList(Context context) {
+        SharedPreferences sharedprefs = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
+        Editor editor = sharedprefs.edit();
+        editor.putStringSet("vpnlist", profiles.keySet());
+        // For reasing I do not understand at all
+        // Android saves my prefs file only one time
+        // if I remove the debug code below :(
+        int counter = sharedprefs.getInt("counter", 0);
+        editor.putInt("counter", counter + 1);
+        editor.apply();
+    }
+
+    public void addProfile(VpnProfile profile) {
+        profiles.put(profile.getUUID().toString(), profile);
+    }
+
+    public void saveProfile(Context context, VpnProfile profile) {
+        saveProfile(context, profile, true, false);
+    }
+
+    private void loadVPNList(Context context) {
+        profiles = new HashMap<>();
+        SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
+        Set<String> vlist = listpref.getStringSet("vpnlist", null);
+        if (vlist == null) {
+            vlist = new HashSet<>();
+        }
+        // Always try to load the temporary profile
+        vlist.add(TEMPORARY_PROFILE_FILENAME);
+        for (String vpnentry : vlist) {
+            try {
+                ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp"));
+                VpnProfile vp = ((VpnProfile) vpnfile.readObject());
+                // Sanity check
+                if (vp == null || vp.mName == null || vp.getUUID() == null) continue;
+                vp.upgradeProfile();
+                if (vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) {
+                    tmpprofile = vp;
+                } else {
+                    profiles.put(vp.getUUID().toString(), vp);
+                }
+            } catch (IOException | ClassNotFoundException e) {
+                if (!vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) VpnStatus.logException("LoadingDialog VPN List", e);
+            }
+        }
+    }
+
+    public void removeProfile(Context context, VpnProfile profile) {
+        String vpnentry = profile.getUUID().toString();
+        profiles.remove(vpnentry);
+        saveProfileList(context);
+        context.deleteFile(vpnentry + ".vp");
+        if (mLastConnectedVpn == profile) mLastConnectedVpn = null;
+    }
+}

+ 52 - 0
app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import de.blinkt.openvpn.VpnProfile;
+
+public class ProxyDetection {
+    static SocketAddress detectProxy(VpnProfile vp) {
+        // Construct a new url with https as protocol
+        try {
+            URL url = new URL(String.format("https://%s:%s", vp.mServerName, vp.mServerPort));
+            Proxy proxy = getFirstProxy(url);
+            if (proxy == null)
+                return null;
+            SocketAddress addr = proxy.address();
+            if (addr instanceof InetSocketAddress) {
+                return addr;
+            }
+        } catch (MalformedURLException e) {
+            VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
+        } catch (URISyntaxException e) {
+            VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
+        }
+        return null;
+    }
+    static Proxy getFirstProxy(URL url) throws URISyntaxException {
+        System.setProperty("java.net.useSystemProxies", "true");
+        List<Proxy> proxylist = ProxySelector.getDefault().select(url.toURI());
+        if (proxylist != null) {
+            for (Proxy proxy : proxylist) {
+                SocketAddress addr = proxy.address();
+                if (addr != null) {
+                    return proxy;
+                }
+            }
+        }
+        return null;
+    }
+}

+ 87 - 0
app/src/main/java/de/blinkt/openvpn/core/StatusListener.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Created by arne on 09.11.16.
+ */
+public class StatusListener {
+    private File mCacheDir;
+    private IStatusCallbacks mCallback = new IStatusCallbacks.Stub() {
+        @Override
+        public void newLogItem(LogItem item) throws RemoteException {
+            VpnStatus.newLogItem(item);
+        }
+
+        @Override
+        public void updateStateString(String state, String msg, int resid, ConnectionStatus level) throws RemoteException {
+            VpnStatus.updateStateString(state, msg, resid, level);
+        }
+
+        @Override
+        public void updateByteCount(long inBytes, long outBytes) throws RemoteException {
+            VpnStatus.updateByteCount(inBytes, outBytes);
+        }
+
+        @Override
+        public void connectedVPN(String uuid) throws RemoteException {
+            VpnStatus.setConnectedVPNProfile(uuid);
+        }
+    };
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            IServiceStatus serviceStatus = IServiceStatus.Stub.asInterface(service);
+            try {
+                /* Check if this a local service ... */
+                if (service.queryLocalInterface("de.blinkt.openvpn.core.IServiceStatus") == null) {
+                    // Not a local service
+                    VpnStatus.setConnectedVPNProfile(serviceStatus.getLastConnectedVPN());
+                    VpnStatus.setTrafficHistory(serviceStatus.getTrafficHistory());
+                    ParcelFileDescriptor pfd = serviceStatus.registerStatusCallback(mCallback);
+                    DataInputStream fd = new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(pfd));
+                    short len = fd.readShort();
+                    byte[] buf = new byte[65336];
+                    while (len != 0x7fff) {
+                        fd.readFully(buf, 0, len);
+                        LogItem logitem = new LogItem(buf, len);
+                        VpnStatus.newLogItem(logitem, false);
+                        len = fd.readShort();
+                    }
+                    fd.close();
+                } else {
+                    VpnStatus.initLogCache(mCacheDir);
+                }
+            } catch (RemoteException | IOException e) {
+                e.printStackTrace();
+                VpnStatus.logException(e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+        }
+    };
+
+    void init(Context c) {
+        Intent intent = new Intent(c, OpenVPNStatusService.class);
+        intent.setAction(OpenVPNService.START_SERVICE);
+        mCacheDir = c.getCacheDir();
+        c.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+}

+ 191 - 0
app/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java

@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2012-2017 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import static java.lang.Math.max;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Vector;
+
+/**
+ * Created by arne on 23.05.17.
+ */
+public class TrafficHistory implements Parcelable {
+    public static final long PERIODS_TO_KEEP = 5;
+    public static final int TIME_PERIOD_MINTUES = 60 * 1000;
+    public static final int TIME_PERIOD_HOURS = 3600 * 1000;
+    public static final Creator<TrafficHistory> CREATOR = new Creator<TrafficHistory>() {
+        @Override
+        public TrafficHistory createFromParcel(Parcel in) {
+            return new TrafficHistory(in);
+        }
+        @Override
+        public TrafficHistory[] newArray(int size) {
+            return new TrafficHistory[size];
+        }
+    };
+    private LinkedList<TrafficDatapoint> trafficHistorySeconds = new LinkedList<>();
+    private LinkedList<TrafficDatapoint> trafficHistoryMinutes = new LinkedList<>();
+    private LinkedList<TrafficDatapoint> trafficHistoryHours = new LinkedList<>();
+    private TrafficDatapoint lastSecondUsedForMinute;
+    private TrafficDatapoint lastMinuteUsedForHours;
+    public TrafficHistory() {
+    }
+    protected TrafficHistory(Parcel in) {
+        in.readList(trafficHistorySeconds, getClass().getClassLoader());
+        in.readList(trafficHistoryMinutes, getClass().getClassLoader());
+        in.readList(trafficHistoryHours, getClass().getClassLoader());
+        lastSecondUsedForMinute = in.readParcelable(getClass().getClassLoader());
+        lastMinuteUsedForHours = in.readParcelable(getClass().getClassLoader());
+    }
+    public static LinkedList<TrafficDatapoint> getDummyList() {
+        LinkedList<TrafficDatapoint> list = new LinkedList<>();
+        list.add(new TrafficDatapoint(0, 0, System.currentTimeMillis()));
+        return list;
+    }
+    public LastDiff getLastDiff(TrafficDatapoint tdp) {
+        TrafficDatapoint lasttdp;
+        if (trafficHistorySeconds.size() == 0)
+            lasttdp = new TrafficDatapoint(0, 0, System.currentTimeMillis());
+        else
+            lasttdp = trafficHistorySeconds.getLast();
+        if (tdp == null) {
+            tdp = lasttdp;
+            if (trafficHistorySeconds.size() < 2)
+                lasttdp = tdp;
+            else {
+                trafficHistorySeconds.descendingIterator().next();
+                tdp = trafficHistorySeconds.descendingIterator().next();
+            }
+        }
+        return new LastDiff(lasttdp, tdp);
+    }
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeList(trafficHistorySeconds);
+        dest.writeList(trafficHistoryMinutes);
+        dest.writeList(trafficHistoryHours);
+        dest.writeParcelable(lastSecondUsedForMinute, 0);
+        dest.writeParcelable(lastMinuteUsedForHours, 0);
+    }
+    public LinkedList<TrafficDatapoint> getHours() {
+        return trafficHistoryHours;
+    }
+    public LinkedList<TrafficDatapoint> getMinutes() {
+        return trafficHistoryMinutes;
+    }
+    public LinkedList<TrafficDatapoint> getSeconds() {
+        return trafficHistorySeconds;
+    }
+    LastDiff add(long in, long out) {
+        TrafficDatapoint tdp = new TrafficDatapoint(in, out, System.currentTimeMillis());
+        LastDiff diff = getLastDiff(tdp);
+        addDataPoint(tdp);
+        return diff;
+    }
+    private void addDataPoint(TrafficDatapoint tdp) {
+        trafficHistorySeconds.add(tdp);
+        if (lastSecondUsedForMinute == null) {
+            lastSecondUsedForMinute = new TrafficDatapoint(0, 0, 0);
+            lastMinuteUsedForHours = new TrafficDatapoint(0, 0, 0);
+        }
+        removeAndAverage(tdp, true);
+    }
+    private void removeAndAverage(TrafficDatapoint newTdp, boolean seconds) {
+        HashSet<TrafficDatapoint> toRemove = new HashSet<>();
+        Vector<TrafficDatapoint> toAverage = new Vector<>();
+        long timePeriod;
+        LinkedList<TrafficDatapoint> tpList, nextList;
+        TrafficDatapoint lastTsPeriod;
+        if (seconds) {
+            timePeriod = TIME_PERIOD_MINTUES;
+            tpList = trafficHistorySeconds;
+            nextList = trafficHistoryMinutes;
+            lastTsPeriod = lastSecondUsedForMinute;
+        } else {
+            timePeriod = TIME_PERIOD_HOURS;
+            tpList = trafficHistoryMinutes;
+            nextList = trafficHistoryHours;
+            lastTsPeriod = lastMinuteUsedForHours;
+        }
+        if (newTdp.timestamp / timePeriod > (lastTsPeriod.timestamp / timePeriod)) {
+            nextList.add(newTdp);
+            if (seconds) {
+                lastSecondUsedForMinute = newTdp;
+                removeAndAverage(newTdp, false);
+            } else
+                lastMinuteUsedForHours = newTdp;
+            for (TrafficDatapoint tph : tpList) {
+                // List is iteratered from oldest to newest, remembert first one that we did not
+                if ((newTdp.timestamp - tph.timestamp) / timePeriod >= PERIODS_TO_KEEP)
+                    toRemove.add(tph);
+            }
+            tpList.removeAll(toRemove);
+        }
+    }
+    public static class TrafficDatapoint implements Parcelable {
+        public static final Creator<TrafficDatapoint> CREATOR = new Creator<TrafficDatapoint>() {
+            @Override
+            public TrafficDatapoint createFromParcel(Parcel in) {
+                return new TrafficDatapoint(in);
+            }
+            @Override
+            public TrafficDatapoint[] newArray(int size) {
+                return new TrafficDatapoint[size];
+            }
+        };
+        public final long timestamp;
+        public final long in;
+        public final long out;
+        private TrafficDatapoint(long inBytes, long outBytes, long timestamp) {
+            this.in = inBytes;
+            this.out = outBytes;
+            this.timestamp = timestamp;
+        }
+        private TrafficDatapoint(Parcel in) {
+            timestamp = in.readLong();
+            this.in = in.readLong();
+            out = in.readLong();
+        }
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(timestamp);
+            dest.writeLong(in);
+            dest.writeLong(out);
+        }
+    }
+    static class LastDiff {
+        final private TrafficDatapoint tdp;
+        final private TrafficDatapoint lasttdp;
+        private LastDiff(TrafficDatapoint lasttdp, TrafficDatapoint tdp) {
+            this.lasttdp = lasttdp;
+            this.tdp = tdp;
+        }
+        public long getDiffOut() {
+            return max(0, tdp.out - lasttdp.out);
+        }
+        public long getDiffIn() {
+            return max(0, tdp.in - lasttdp.in);
+        }
+        public long getIn() {
+            return tdp.in;
+        }
+        public long getOut() {
+            return tdp.out;
+        }
+    }
+}

+ 123 - 0
app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Vector;
+
+import de.blinkt.openvpn.VpnProfile;
+
+public class VPNLaunchHelper {
+    private static final String MININONPIEVPN = "nopie_openvpn";
+    private static final String MINIPIEVPN = "pie_openvpn";
+    private static final String OVPNCONFIGFILE = "android.conf";
+    private static String writeMiniVPN(Context context) {
+        String nativeAPI = NativeUtils.getNativeAPI();
+        /* Q does not allow executing binaries written in temp directory anymore */
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+            return new File(context.getApplicationInfo().nativeLibraryDir, "libovpnexec.so").getPath();
+
+        String[] abis;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+            abis = getSupportedABIsLollipop();
+        else
+            //noinspection deprecation
+            abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
+        //String nativeAPI = NativeUtils.getNativeAPI();
+        if (!nativeAPI.equals(abis[0])) {
+            VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI);
+            abis = new String[]{nativeAPI};
+        }
+        for (String abi : abis) {
+            File vpnExecutable = new File(context.getCacheDir(), "c_" + getMiniVPNExecutableName() + "." + abi);
+            if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) {
+                return vpnExecutable.getPath();
+            }
+        }
+        return null;
+    }
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private static String[] getSupportedABIsLollipop() {
+        return Build.SUPPORTED_ABIS;
+    }
+    private static String getMiniVPNExecutableName() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+            return MINIPIEVPN;
+        else
+            return MININONPIEVPN;
+    }
+    public static String[] replacePieWithNoPie(String[] mArgv) {
+        mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN);
+        return mArgv;
+    }
+    static String[] buildOpenvpnArgv(Context c) {
+        Vector<String> args = new Vector<>();
+        String binaryName = writeMiniVPN(c);
+        // Add fixed paramenters
+        //args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
+        if (binaryName == null) {
+            VpnStatus.logError("Error writing minivpn binary");
+            return null;
+        }
+        args.add(binaryName);
+        args.add("--config");
+        args.add(getConfigFilePath(c));
+        return args.toArray(new String[args.size()]);
+    }
+    private static boolean writeMiniVPNBinary(Context context, String abi, File mvpnout) {
+        try {
+            InputStream mvpn;
+            try {
+                mvpn = context.getAssets().open(getMiniVPNExecutableName() + "." + abi);
+            } catch (IOException errabi) {
+                VpnStatus.logInfo("Failed getting assets for archicture " + abi);
+                return false;
+            }
+            FileOutputStream fout = new FileOutputStream(mvpnout);
+            byte buf[] = new byte[4096];
+            int lenread = mvpn.read(buf);
+            while (lenread > 0) {
+                fout.write(buf, 0, lenread);
+                lenread = mvpn.read(buf);
+            }
+            fout.close();
+            if (!mvpnout.setExecutable(true)) {
+                VpnStatus.logError("Failed to make OpenVPN executable");
+                return false;
+            }
+            return true;
+        } catch (IOException e) {
+            VpnStatus.logException(e);
+            return false;
+        }
+    }
+    public static void startOpenVpn(VpnProfile startprofile, Context context) {
+        Intent startVPN = startprofile.prepareStartService(context);
+        if (startVPN != null) {
+            context.startService(startVPN);
+            /*
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+                //noinspection NewApi
+                context.startForegroundService(startVPN);
+            else
+                context.startService(startVPN);
+                */
+        }
+    }
+    public static String getConfigFilePath(Context context) {
+        return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE;
+    }
+}

+ 801 - 0
app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java

@@ -0,0 +1,801 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import com.vpn.fastestvpnservice.R;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Vector;
+
+public class VpnStatus {
+    final static Object readFileLock = new Object();
+    static final int MAXLOGENTRIES = 1000;
+    // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
+    static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
+    static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
+    static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
+    static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
+    private static final LinkedList<LogItem> logbuffer;
+    public static TrafficHistory trafficHistory;
+    static boolean readFileLog = false;
+    private static Vector<LogListener> logListener;
+    private static Vector<StateListener> stateListener;
+    private static Vector<ByteCountListener> byteCountListener;
+    private static String mLaststatemsg = "";
+    private static String mLaststate = "NOPROCESS";
+    private static int mLastStateresid = R.string.state_noprocess;
+    private static HandlerThread mHandlerThread;
+    private static String mLastConnectedVPNUUID;
+    private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
+    private static LogFileHandler mLogFileHandler;
+
+    static {
+        logbuffer = new LinkedList<>();
+        logListener = new Vector<>();
+        stateListener = new Vector<>();
+        byteCountListener = new Vector<>();
+        trafficHistory = new TrafficHistory();
+        logInformation();
+    }
+
+    public static void logException(LogLevel ll, String context, Exception e) {
+        StringWriter sw = new StringWriter();
+        e.printStackTrace(new PrintWriter(sw));
+        LogItem li;
+        if (context != null) {
+            li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
+        } else {
+            li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
+        }
+        newLogItem(li);
+    }
+
+    public static void logException(Exception e) {
+        logException(LogLevel.ERROR, null, e);
+    }
+
+    public static void logException(String context, Exception e) {
+        logException(LogLevel.ERROR, context, e);
+    }
+
+    public static boolean isVPNActive() {
+        return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
+    }
+
+    public static String getLastCleanLogMessage(Context c) {
+        String message = mLaststatemsg;
+        switch (mLastLevel) {
+            case LEVEL_CONNECTED:
+                String[] parts = mLaststatemsg.split(",");
+                /*
+                   (a) the integer unix date/time,
+                   (b) the state name,
+                   0 (c) optional descriptive string (used mostly on RECONNECTING
+                    and EXITING to show the reason for the disconnect),
+                    1 (d) optional TUN/TAP local IPv4 address
+                   2 (e) optional address of remote server,
+                   3 (f) optional port of remote server,
+                   4 (g) optional local address,
+                   5 (h) optional local port, and
+                   6 (i) optional TUN/TAP local IPv6 address.
+*/
+                // Return only the assigned IP addresses in the UI
+                if (parts.length >= 7) message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
+                break;
+        }
+        while (message.endsWith(",")) message = message.substring(0, message.length() - 1);
+        String status = mLaststate;
+        if (status.equals("NOPROCESS")) return message;
+        if (mLastStateresid == R.string.state_waitconnectretry) {
+            return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
+        }
+        String prefix = c.getString(mLastStateresid);
+        if (mLastStateresid == R.string.unknown_state) message = status + message;
+        if (message.length() > 0) prefix += ": ";
+        return prefix + message;
+    }
+
+    public static void initLogCache(File cacheDir) {
+        mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
+        mHandlerThread.start();
+        mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
+        Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
+        mLogFileHandler.sendMessage(m);
+    }
+
+    public static void flushLog() {
+        if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
+    }
+
+    public static void setConnectedVPNProfile(String uuid) {
+        mLastConnectedVPNUUID = uuid;
+        for (StateListener sl : stateListener)
+            sl.setConnectedVPN(uuid);
+    }
+
+    public static String getLastConnectedVPNProfile() {
+        return mLastConnectedVPNUUID;
+    }
+
+    public static void setTrafficHistory(TrafficHistory trafficHistory) {
+        VpnStatus.trafficHistory = trafficHistory;
+    }
+
+    public synchronized static void logMessage(LogLevel level, String prefix, String message) {
+        newLogItem(new LogItem(level, prefix + message));
+    }
+
+    public synchronized static void clearLog() {
+        logbuffer.clear();
+        logInformation();
+        if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
+    }
+
+    private static void logInformation() {
+        String nativeAPI;
+        try {
+            nativeAPI = NativeUtils.getNativeAPI();
+        } catch (UnsatisfiedLinkError ignore) {
+            nativeAPI = "error";
+        }
+        logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
+    }
+
+    public synchronized static void addLogListener(LogListener ll) {
+        logListener.add(ll);
+    }
+
+    public synchronized static void removeLogListener(LogListener ll) {
+        logListener.remove(ll);
+    }
+
+    public synchronized static void addByteCountListener(ByteCountListener bcl) {
+        TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null);
+        bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(), diff.getDiffOut());
+        byteCountListener.add(bcl);
+    }
+
+    public synchronized static void removeByteCountListener(ByteCountListener bcl) {
+        byteCountListener.remove(bcl);
+    }
+
+    public synchronized static void addStateListener(StateListener sl) {
+        if (!stateListener.contains(sl)) {
+            stateListener.add(sl);
+            if (mLaststate != null) sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
+        }
+    }
+
+    private static int getLocalizedState(String state) {
+        switch (state) {
+            case "CONNECTING":
+                return R.string.state_connecting;
+            case "WAIT":
+                return R.string.state_wait;
+            case "AUTH":
+                return R.string.state_auth;
+            case "GET_CONFIG":
+                return R.string.state_get_config;
+            case "ASSIGN_IP":
+                return R.string.state_assign_ip;
+            case "ADD_ROUTES":
+                return R.string.state_add_routes;
+            case "CONNECTED":
+                return R.string.state_connected;
+            case "DISCONNECTED":
+                return R.string.state_disconnected;
+            case "RECONNECTING":
+                return R.string.state_reconnecting;
+            case "EXITING":
+                return R.string.state_exiting;
+            case "RESOLVE":
+                return R.string.state_resolve;
+            case "TCP_CONNECT":
+                return R.string.state_tcp_connect;
+            default:
+                return R.string.unknown_state;
+        }
+    }
+
+    public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
+        switch (pauseReason) {
+            case noNetwork:
+                VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
+                break;
+            case screenOff:
+                VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
+                break;
+            case userPause:
+                VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
+                break;
+        }
+    }
+
+    private static ConnectionStatus getLevel(String state) {
+        String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
+        String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"};
+        String[] connected = {"CONNECTED"};
+        String[] notconnected = {"DISCONNECTED", "EXITING"};
+        for (String x : noreplyet)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
+        for (String x : reply)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
+        for (String x : connected)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTED;
+        for (String x : notconnected)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_NOTCONNECTED;
+        return ConnectionStatus.UNKNOWN_LEVEL;
+    }
+
+    public synchronized static void removeStateListener(StateListener sl) {
+        stateListener.remove(sl);
+    }
+
+    synchronized public static LogItem[] getlogbuffer() {
+        // The stoned way of java to return an array from a vector
+        // brought to you by eclipse auto complete
+        return logbuffer.toArray(new LogItem[logbuffer.size()]);
+    }
+
+    static void updateStateString(String state, String msg) {
+        int rid = getLocalizedState(state);
+        ConnectionStatus level = getLevel(state);
+        updateStateString(state, msg, rid, level);
+    }
+
+    public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
+        // Workound for OpenVPN doing AUTH and wait and being connected
+        // Simply ignore these state
+        if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && (state.equals("WAIT") || state.equals("AUTH"))) {
+            newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
+            return;
+        }
+        mLaststate = state;
+        mLaststatemsg = msg;
+        mLastStateresid = resid;
+        mLastLevel = level;
+        for (StateListener sl : stateListener) {
+            sl.updateState(state, msg, resid, level);
+        }
+        newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s", state, level.toString(), msg)));
+    }
+
+    public static void logInfo(String message) {
+        newLogItem(new LogItem(LogLevel.INFO, message));
+    }
+
+    public static void logDebug(String message) {
+        newLogItem(new LogItem(LogLevel.DEBUG, message));
+    }
+
+    public static void logInfo(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
+    }
+
+    public static void logDebug(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
+    }
+
+    static void newLogItem(LogItem logItem) {
+        newLogItem(logItem, false);
+    }
+
+    synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
+        if (cachedLine) {
+            logbuffer.addFirst(logItem);
+        } else {
+            logbuffer.addLast(logItem);
+            if (mLogFileHandler != null) {
+                Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
+                mLogFileHandler.sendMessage(m);
+            }
+        }
+        if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
+            while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst();
+            if (mLogFileHandler != null) mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
+        }
+        //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test"))
+        //    Log.d("OpenVPN", logItem.getString(null));
+        for (LogListener ll : logListener) {
+            ll.newLog(logItem);
+        }
+    }
+
+    public static void logError(String msg) {
+        newLogItem(new LogItem(LogLevel.ERROR, msg));
+    }
+
+    public static void logWarning(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
+    }
+
+    public static void logWarning(String msg) {
+        newLogItem(new LogItem(LogLevel.WARNING, msg));
+    }
+
+    public static void logError(int resourceId) {
+        newLogItem(new LogItem(LogLevel.ERROR, resourceId));
+    }
+
+    public static void logError(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
+    }
+
+    public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
+        newLogItem(new LogItem(level, ovpnlevel, message));
+    }
+
+    public static synchronized void updateByteCount(long in, long out) {
+        TrafficHistory.LastDiff diff = trafficHistory.add(in, out);
+        Log.e("Some", String.valueOf(in) + " " +  out);
+        for (ByteCountListener bcl : byteCountListener) {
+            bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut());
+        }
+    }
+
+    public enum LogLevel {
+        INFO(2), ERROR(-2), WARNING(1), VERBOSE(3), DEBUG(4);
+        protected int mValue;
+
+        LogLevel(int value) {
+            mValue = value;
+        }
+
+        public static LogLevel getEnumByValue(int value) {
+            switch (value) {
+                case 2:
+                    return INFO;
+                case -2:
+                    return ERROR;
+                case 1:
+                    return WARNING;
+                case 3:
+                    return VERBOSE;
+                case 4:
+                    return DEBUG;
+                default:
+                    return null;
+            }
+        }
+
+        public int getInt() {
+            return mValue;
+        }
+    }
+
+    public interface LogListener {
+        void newLog(LogItem logItem);
+    }
+
+    public interface StateListener {
+        void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
+
+        void setConnectedVPN(String uuid);
+    }
+
+    public interface ByteCountListener {
+        void updateByteCount(long in, long out, long diffIn, long diffOut);
+    }
+}
+
+
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+/*
+package de.blinkt.openvpn.core;
+
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import
+com.vpn.fastestvpnservice.R;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Vector;
+
+public class VpnStatus {
+    final static java.lang.Object readFileLock = new Object();
+    static final int MAXLOGENTRIES = 1000;
+    // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
+    static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
+    static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
+    static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
+    static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
+    private static final LinkedList<LogItem> logbuffer;
+    public static TrafficHistory trafficHistory;
+    static boolean readFileLog = false;
+    private static Vector<LogListener> logListener;
+    private static Vector<StateListener> stateListener;
+    private static Vector<ByteCountListener> byteCountListener;
+    private static String mLaststatemsg = "";
+    private static String mLaststate = "NOPROCESS";
+    private static int mLastStateresid = R.string.state_noprocess;
+    private static HandlerThread mHandlerThread;
+    private static String mLastConnectedVPNUUID;
+    private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
+    private static LogFileHandler mLogFileHandler;
+
+    static {
+        logbuffer = new LinkedList<>();
+        logListener = new Vector<>();
+        stateListener = new Vector<>();
+        byteCountListener = new Vector<>();
+        trafficHistory = new TrafficHistory();
+        logInformation();
+    }
+
+    public static void logException(LogLevel ll, String context, Exception e) {
+        StringWriter sw = new StringWriter();
+        e.printStackTrace(new PrintWriter(sw));
+        LogItem li;
+        if (context != null) {
+            li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
+        } else {
+            li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
+        }
+        newLogItem(li);
+    }
+
+    public static void logException(Exception e) {
+        logException(LogLevel.ERROR, null, e);
+    }
+
+    public static void logException(String context, Exception e) {
+        logException(LogLevel.ERROR, context, e);
+    }
+
+    public static boolean isVPNActive() {
+        return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
+    }
+
+    public static String getLastCleanLogMessage(Context c) {
+        String message = mLaststatemsg;
+        switch (mLastLevel) {
+            case LEVEL_CONNECTED:
+                String[] parts = mLaststatemsg.split(",");
+                message = parts[2]; */
+                //Log.e("Parts", parts[0] + parts[1] + parts[2]);
+                /*
+                   (a) the integer unix date/time,
+                   (b) the state name,
+                   0 (c) optional descriptive string (used mostly on RECONNECTING
+                    and EXITING to show the reason for the disconnect),
+                    1 (d) optional TUN/TAP local IPv4 address
+                   2 (e) optional address of remote server,
+                   3 (f) optional port of remote server,
+                   4 (g) optional local address,
+                   5 (h) optional local port, and
+                   6 (i) optional TUN/TAP local IPv6 address.
+*/
+                // Return only the assigned IP addresses in the UI
+
+/*                if (parts.length >= 7) message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
+                break;
+        }
+
+        while (message.endsWith(",")) message = message.substring(0, message.length() - 1);
+        String status = mLaststate;
+        if (status.equals("NOPROCESS")) return message;
+
+        if (mLastStateresid == R.string.state_waitconnectretry) {
+            return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
+        }
+
+        String prefix = c.getString(mLastStateresid);
+
+        if (mLastStateresid == R.string.unknown_state) message = status + message;
+
+        if (message.length() > 0) prefix += " ";
+
+        return prefix + message;
+    }
+
+    public static void initLogCache(File cacheDir) {
+        mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
+        mHandlerThread.start();
+        mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
+        Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
+        mLogFileHandler.sendMessage(m);
+    }
+
+    public static void flushLog() {
+        if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
+    }
+
+    public static void setConnectedVPNProfile(String uuid) {
+        mLastConnectedVPNUUID = uuid;
+        for (StateListener sl : stateListener)
+            sl.setConnectedVPN(uuid);
+    }
+
+    public static String getLastConnectedVPNProfile() {
+        return mLastConnectedVPNUUID;
+    }
+
+    public static void setTrafficHistory(TrafficHistory trafficHistory) {
+        VpnStatus.trafficHistory = trafficHistory;
+    }
+
+    public synchronized static void logMessage(LogLevel level, String prefix, String message) {
+        newLogItem(new LogItem(level, prefix + message));
+    }
+
+    public synchronized static void clearLog() {
+        logbuffer.clear();
+        logInformation();
+        if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
+    }
+
+    private static void logInformation() {
+        String nativeAPI;
+        try {
+            // API 25 java.lang.NoClassDefFoundError: de.blinkt.openvpn.core.NativeUtils
+            nativeAPI = NativeUtils.getNativeAPI();
+        } catch (UnsatisfiedLinkError ignore) {
+            nativeAPI = "error";
+        }
+        logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
+    }
+
+    public synchronized static void addLogListener(LogListener ll) {
+        logListener.add(ll);
+    }
+
+    public synchronized static void removeLogListener(LogListener ll) {
+        logListener.remove(ll);
+    }
+
+    public synchronized static void addByteCountListener(ByteCountListener bcl) {
+        TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null);
+        bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(), diff.getDiffOut());
+        byteCountListener.add(bcl);
+    }
+
+    public synchronized static void removeByteCountListener(ByteCountListener bcl) {
+        byteCountListener.remove(bcl);
+    }
+
+    public synchronized static void addStateListener(StateListener sl) {
+        if (!stateListener.contains(sl)) {
+            stateListener.add(sl);
+            if (mLaststate != null) sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
+        }
+    }
+
+    private static int getLocalizedState(String state) {
+        switch (state) {
+            case "CONNECTING":
+                return R.string.state_connecting;
+            case "WAIT":
+                return R.string.state_wait;
+            case "AUTH":
+                return R.string.state_auth;
+            case "GET_CONFIG":
+                return R.string.state_get_config;
+            case "ASSIGN_IP":
+                return R.string.state_assign_ip;
+            case "ADD_ROUTES":
+                return R.string.state_add_routes;
+            case "CONNECTED":
+                return R.string.state_connected;
+            case "DISCONNECTED":
+                return R.string.state_disconnected;
+            case "RECONNECTING":
+                return R.string.state_reconnecting;
+            case "EXITING":
+                return R.string.state_exiting;
+            case "RESOLVE":
+                return R.string.state_resolve;
+            case "TCP_CONNECT":
+                return R.string.state_tcp_connect;
+            default:
+                return R.string.unknown_state;
+        }
+    }
+
+    public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
+        switch (pauseReason) {
+            case noNetwork:
+                VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
+                //DataObj.bl_network = false;
+                break;
+            case screenOff:
+                VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
+                //DataObj.bl_network = false;
+                break;
+            case userPause:
+                VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
+                //DataObj.bl_network = false;
+                break;
+        }
+    }
+
+    private static ConnectionStatus getLevel(String state) {
+        String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
+        String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"};
+        String[] connected = {"CONNECTED"};
+        String[] notconnected = {"DISCONNECTED", "EXITING"};
+        for (String x : noreplyet)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
+        for (String x : reply)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
+        for (String x : connected)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTED;
+        for (String x : notconnected)
+            if (state.equals(x)) return ConnectionStatus.LEVEL_NOTCONNECTED;
+        return ConnectionStatus.UNKNOWN_LEVEL;
+    }
+
+    public synchronized static void removeStateListener(StateListener sl) {
+        stateListener.remove(sl);
+    }
+
+    synchronized public static LogItem[] getlogbuffer() {
+        // The stoned way of java to return an array from a vector
+        // brought to you by eclipse auto complete
+        return logbuffer.toArray(new LogItem[logbuffer.size()]);
+    }
+
+    static void updateStateString(String state, String msg) {
+        int rid = getLocalizedState(state);
+        ConnectionStatus level = getLevel(state);
+        updateStateString(state, msg, rid, level);
+    }
+
+    public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
+        // Workound for OpenVPN doing AUTH and wait and being connected
+        // Simply ignore these state
+        if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && (state.equals("WAIT") || state.equals("AUTH"))) {
+            newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
+            return;
+        }
+        mLaststate = state;
+        mLaststatemsg = msg;
+        mLastStateresid = resid;
+        mLastLevel = level;
+        for (StateListener sl : stateListener) {
+            sl.updateState(state, msg, resid, level);
+        }
+        newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s", state, level.toString(), msg)));
+    }
+
+    public static void logInfo(String message) {
+        newLogItem(new LogItem(LogLevel.INFO, message));
+    }
+
+    public static void logDebug(String message) {
+        newLogItem(new LogItem(LogLevel.DEBUG, message));
+    }
+
+    public static void logInfo(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
+    }
+
+    public static void logDebug(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
+    }
+
+    static void newLogItem(LogItem logItem) {
+        newLogItem(logItem, false);
+    }
+
+    synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
+        if (cachedLine) {
+            logbuffer.addFirst(logItem);
+        } else {
+            logbuffer.addLast(logItem);
+            if (mLogFileHandler != null) {
+                Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
+                mLogFileHandler.sendMessage(m);
+            }
+        }
+        if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
+            while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst();
+            if (mLogFileHandler != null) mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
+        }
+        //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test"))
+        //    Log.d("OpenVPN", logItem.getString(null));
+        for (LogListener ll : logListener) {
+            ll.newLog(logItem);
+        }
+    }
+
+    public static void logError(String msg) {
+        newLogItem(new LogItem(LogLevel.ERROR, msg));
+    }
+
+    public static void logWarning(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
+    }
+
+    public static void logWarning(String msg) {
+        newLogItem(new LogItem(LogLevel.WARNING, msg));
+    }
+
+    public static void logError(int resourceId) {
+        newLogItem(new LogItem(LogLevel.ERROR, resourceId));
+    }
+
+    public static void logError(int resourceId, Object... args) {
+        newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
+    }
+
+    public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
+        newLogItem(new LogItem(level, ovpnlevel, message));
+    }
+
+    public static synchronized void updateByteCount(long in, long out) {
+        TrafficHistory.LastDiff diff = trafficHistory.add(in, out);
+        for (ByteCountListener bcl : byteCountListener) {
+            bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut());
+        }
+    }
+
+    public enum LogLevel {
+        INFO(2), ERROR(-2), WARNING(1), VERBOSE(3), DEBUG(4);
+        protected int mValue;
+
+        LogLevel(int value) {
+            mValue = value;
+        }
+
+        public static LogLevel getEnumByValue(int value) {
+            switch (value) {
+                case 2:
+                    return INFO;
+                case -2:
+                    return ERROR;
+                case 1:
+                    return WARNING;
+                case 3:
+                    return VERBOSE;
+                case 4:
+                    return DEBUG;
+                default:
+                    return null;
+            }
+        }
+
+        public int getInt() {
+            return mValue;
+        }
+    }
+
+    public interface LogListener {
+        void newLog(LogItem logItem);
+    }
+
+    public interface StateListener {
+        void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
+
+        void setConnectedVPN(String uuid);
+    }
+
+    public interface ByteCountListener {
+        void updateByteCount(long in, long out, long diffIn, long diffOut);
+    }
+}
+*/

+ 179 - 0
app/src/main/java/de/blinkt/openvpn/core/X509Utils.java

@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.core;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.vpn.fastestvpnservice.R;
+
+import org.spongycastle.util.pem.PemObject;
+import org.spongycastle.util.pem.PemReader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.security.auth.x500.X500Principal;
+
+import de.blinkt.openvpn.VpnProfile;
+
+public class X509Utils {
+    public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+        Vector<Certificate> certificates = new Vector<>();
+        if (VpnProfile.isEmbedded(certfilename)) {
+            int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----");
+            do {
+                // The java certifcate reader is ... kind of stupid
+                // It does NOT ignore chars before the --BEGIN ...
+                subIndex = Math.max(0, subIndex);
+                InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes());
+                certificates.add(certFact.generateCertificate(inStream));
+                subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex + 1);
+            } while (subIndex > 0);
+            return certificates.toArray(new Certificate[certificates.size()]);
+        } else {
+            InputStream inStream = new FileInputStream(certfilename);
+            return new Certificate[]{certFact.generateCertificate(inStream)};
+        }
+    }
+    public static PemObject readPemObjectFromFile(String keyfilename) throws IOException {
+        Reader inStream;
+        if (VpnProfile.isEmbedded(keyfilename))
+            inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename));
+        else
+            inStream = new FileReader(new File(keyfilename));
+        PemReader pr = new PemReader(inStream);
+        PemObject r = pr.readPemObject();
+        pr.close();
+        return r;
+    }
+    public static String getCertificateFriendlyName(Context c, String filename) {
+        if (!TextUtils.isEmpty(filename)) {
+            try {
+                X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0];
+                String friendlycn = getCertificateFriendlyName(cert);
+                friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn;
+                return friendlycn;
+            } catch (Exception e) {
+                VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage());
+            }
+        }
+        return c.getString(R.string.cannotparsecert);
+    }
+    public static String getCertificateValidityString(X509Certificate cert, Resources res) {
+        try {
+            cert.checkValidity();
+        } catch (CertificateExpiredException ce) {
+            return "EXPIRED: ";
+        } catch (CertificateNotYetValidException cny) {
+            return "NOT YET VALID: ";
+        }
+        Date certNotAfter = cert.getNotAfter();
+        Date now = new Date();
+        long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms
+        // More than 72h left, display days
+        // More than 3 months display months
+        if (timeLeft > 90l * 24 * 3600 * 1000) {
+            long months = getMonthsDifference(now, certNotAfter);
+            return res.getQuantityString(R.plurals.months_left, (int) months, months);
+        } else if (timeLeft > 72 * 3600 * 1000) {
+            long days = timeLeft / (24 * 3600 * 1000);
+            return res.getQuantityString(R.plurals.days_left, (int) days, days);
+        } else {
+            long hours = timeLeft / (3600 * 1000);
+            return res.getQuantityString(R.plurals.hours_left, (int) hours, hours);
+        }
+    }
+    public static int getMonthsDifference(Date date1, Date date2) {
+        int m1 = date1.getYear() * 12 + date1.getMonth();
+        int m2 = date2.getYear() * 12 + date2.getMonth();
+        return m2 - m1 + 1;
+    }
+    public static String getCertificateFriendlyName(X509Certificate cert) {
+        X500Principal principal = cert.getSubjectX500Principal();
+        byte[] encodedSubject = principal.getEncoded();
+        String friendlyName = null;
+        /* Hack so we do not have to ship a whole Spongy/bouncycastle */
+        Exception exp = null;
+        try {
+            @SuppressLint("PrivateApi") Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
+            Method getInstance = X509NameClass.getMethod("getInstance", Object.class);
+            Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass);
+            if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1"))
+                defaultSymbols.put("1.2.840.113549.1.9.1", "eMail");
+            Object subjectName = getInstance.invoke(X509NameClass, encodedSubject);
+            Method toString = X509NameClass.getMethod("toString", boolean.class, Hashtable.class);
+            friendlyName = (String) toString.invoke(subjectName, true, defaultSymbols);
+        } catch (ClassNotFoundException e) {
+            exp = e;
+        } catch (NoSuchMethodException e) {
+            exp = e;
+        } catch (InvocationTargetException e) {
+            exp = e;
+        } catch (IllegalAccessException e) {
+            exp = e;
+        } catch (NoSuchFieldException e) {
+            exp = e;
+        }
+        if (exp != null)
+            VpnStatus.logException("Getting X509 Name from certificate", exp);
+        /* Fallback if the reflection method did not work */
+        if (friendlyName == null)
+            friendlyName = principal.getName();
+        // Really evil hack to decode email address
+        // See: http://code.google.com/p/android/issues/detail?id=21531
+        String[] parts = friendlyName.split(",");
+        for (int i = 0; i < parts.length; i++) {
+            String part = parts[i];
+            if (part.startsWith("1.2.840.113549.1.9.1=#16")) {
+                parts[i] = "email=" + ia5decode(part.replace("1.2.840.113549.1.9.1=#16", ""));
+            }
+        }
+        friendlyName = TextUtils.join(",", parts);
+        return friendlyName;
+    }
+    public static boolean isPrintableChar(char c) {
+        Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
+        return (!Character.isISOControl(c)) &&
+                block != null &&
+                block != Character.UnicodeBlock.SPECIALS;
+    }
+    private static String ia5decode(String ia5string) {
+        String d = "";
+        for (int i = 1; i < ia5string.length(); i = i + 2) {
+            String hexstr = ia5string.substring(i - 1, i + 1);
+            char c = (char) Integer.parseInt(hexstr, 16);
+            if (isPrintableChar(c)) {
+                d += c;
+            } else if (i == 1 && (c == 0x12 || c == 0x1b)) {
+                // ignore
+            } else {
+                d += "\\x" + hexstr;
+            }
+        }
+        return d;
+    }
+}

+ 89 - 0
app/src/main/java/org/spongycastle/util/encoders/Base64.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.encoders;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class Base64 {
+    private static final Encoder encoder = new Base64Encoder();
+
+    /**
+     * encode the input data producing a base 64 encoded byte array.
+     *
+     * @return a byte array containing the base 64 encoded data.
+     */
+    public static byte[] encode(byte[] data) {
+        int len = (data.length + 2) / 3 * 4;
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
+        try {
+            encoder.encode(data, 0, data.length, bOut);
+        } catch (IOException e) {
+            throw new RuntimeException("exception encoding base64 string: " + e);
+        }
+        return bOut.toByteArray();
+    }
+
+    /**
+     * Encode the byte data to base 64 writing it to the given output stream.
+     *
+     * @return the number of bytes produced.
+     */
+    public static int encode(byte[] data, OutputStream out) throws IOException {
+        return encoder.encode(data, 0, data.length, out);
+    }
+
+    /**
+     * Encode the byte data to base 64 writing it to the given output stream.
+     *
+     * @return the number of bytes produced.
+     */
+    public static int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
+        return encoder.encode(data, off, length, out);
+    }
+
+    /**
+     * decode the base 64 encoded input data. It is assumed the input data is valid.
+     *
+     * @return a byte array representing the decoded data.
+     */
+    public static byte[] decode(byte[] data) {
+        int len = data.length / 4 * 3;
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
+        try {
+            encoder.decode(data, 0, data.length, bOut);
+        } catch (IOException e) {
+            throw new RuntimeException("exception decoding base64 string: " + e);
+        }
+        return bOut.toByteArray();
+    }
+
+    /**
+     * decode the base 64 encoded String data - whitespace will be ignored.
+     *
+     * @return a byte array representing the decoded data.
+     */
+    public static byte[] decode(String data) {
+        int len = data.length() / 4 * 3;
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
+        try {
+            encoder.decode(data, bOut);
+        } catch (IOException e) {
+            throw new RuntimeException("exception decoding base64 string: " + e);
+        }
+        return bOut.toByteArray();
+    }
+
+    /**
+     * decode the base 64 encoded String data writing it to the given output stream,
+     * whitespace characters will be ignored.
+     *
+     * @return the number of bytes produced.
+     */
+    public static int decode(String data, OutputStream out) throws IOException {
+        return encoder.decode(data, out);
+    }
+}

ファイルの差分が大きいため隠しています
+ 195 - 0
app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java


+ 20 - 0
app/src/main/java/org/spongycastle/util/encoders/Encoder.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.encoders;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Encode and decode byte arrays (typically from binary to 7-bit ASCII
+ * encodings).
+ */
+public interface Encoder {
+    int encode(byte[] data, int off, int length, OutputStream out) throws IOException;
+
+    int decode(byte[] data, int off, int length, OutputStream out) throws IOException;
+
+    int decode(String data, OutputStream out) throws IOException;
+}

+ 25 - 0
app/src/main/java/org/spongycastle/util/pem/PemGenerationException.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.pem;
+
+import java.io.IOException;
+
+@SuppressWarnings("serial")
+public class PemGenerationException extends IOException {
+    private Throwable cause;
+
+    public PemGenerationException(String message, Throwable cause) {
+        super(message);
+        this.cause = cause;
+    }
+
+    public PemGenerationException(String message) {
+        super(message);
+    }
+
+    public Throwable getCause() {
+        return cause;
+    }
+}

+ 52 - 0
app/src/main/java/org/spongycastle/util/pem/PemHeader.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.pem;
+
+public class PemHeader {
+    private String name;
+    private String value;
+
+    public PemHeader(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public int hashCode() {
+        return getHashCode(this.name) + 31 * getHashCode(this.value);
+    }
+
+    public boolean equals(Object o) {
+        if (!(o instanceof PemHeader)) {
+            return false;
+        }
+        PemHeader other = (PemHeader) o;
+        return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value));
+    }
+
+    private int getHashCode(String s) {
+        if (s == null) {
+            return 1;
+        }
+        return s.hashCode();
+    }
+
+    private boolean isEqual(String s1, String s2) {
+        if (s1 == s2) {
+            return true;
+        }
+        if (s1 == null || s2 == null) {
+            return false;
+        }
+        return s1.equals(s2);
+    }
+}

+ 56 - 0
app/src/main/java/org/spongycastle/util/pem/PemObject.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.pem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SuppressWarnings("all")
+public class PemObject implements PemObjectGenerator {
+    private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
+    private String type;
+    private List headers;
+    private byte[] content;
+
+    /**
+     * Generic constructor for object without headers.
+     *
+     * @param type    pem object type.
+     * @param content the binary content of the object.
+     */
+    public PemObject(String type, byte[] content) {
+        this(type, EMPTY_LIST, content);
+    }
+
+    /**
+     * Generic constructor for object with headers.
+     *
+     * @param type    pem object type.
+     * @param headers a list of PemHeader objects.
+     * @param content the binary content of the object.
+     */
+    public PemObject(String type, List headers, byte[] content) {
+        this.type = type;
+        this.headers = Collections.unmodifiableList(headers);
+        this.content = content;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public List getHeaders() {
+        return headers;
+    }
+
+    public byte[] getContent() {
+        return content;
+    }
+
+    public PemObject generate() throws PemGenerationException {
+        return this;
+    }
+}

+ 9 - 0
app/src/main/java/org/spongycastle/util/pem/PemObjectGenerator.java

@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.pem;
+
+public interface PemObjectGenerator {
+    PemObject generate() throws PemGenerationException;
+}

+ 62 - 0
app/src/main/java/org/spongycastle/util/pem/PemReader.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.pem;
+
+import org.spongycastle.util.encoders.Base64;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PemReader extends BufferedReader {
+    private static final String BEGIN = "-----BEGIN ";
+    private static final String END = "-----END ";
+
+    public PemReader(Reader reader) {
+        super(reader);
+    }
+
+    public PemObject readPemObject() throws IOException {
+        String line = readLine();
+        while (line != null && !line.startsWith(BEGIN)) {
+            line = readLine();
+        }
+        if (line != null) {
+            line = line.substring(BEGIN.length());
+            int index = line.indexOf('-');
+            String type = line.substring(0, index);
+            if (index > 0) {
+                return loadObject(type);
+            }
+        }
+        return null;
+    }
+
+    private PemObject loadObject(String type) throws IOException {
+        String line;
+        String endMarker = END + type;
+        StringBuilder buf = new StringBuilder();
+        List headers = new ArrayList();
+        while ((line = readLine()) != null) {
+            if (line.indexOf(":") >= 0) {
+                int index = line.indexOf(':');
+                String hdr = line.substring(0, index);
+                String value = line.substring(index + 1).trim();
+                headers.add(new PemHeader(hdr, value));
+                continue;
+            }
+            if (line.indexOf(endMarker) != -1) {
+                break;
+            }
+            buf.append(line.trim());
+        }
+        if (line == null) {
+            throw new IOException(endMarker + " not found");
+        }
+        return new PemObject(type, headers, Base64.decode(buf.toString()));
+    }
+}

+ 103 - 0
app/src/main/java/org/spongycastle/util/pem/PemWriter.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package org.spongycastle.util.pem;
+
+import org.spongycastle.util.encoders.Base64;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+
+/**
+ * A generic PEM writer, based on RFC 1421
+ */
+@SuppressWarnings("all")
+public class PemWriter extends BufferedWriter {
+    private static final int LINE_LENGTH = 64;
+    private final int nlLength;
+    private char[] buf = new char[LINE_LENGTH];
+
+    /**
+     * Base constructor.
+     *
+     * @param out output stream to use.
+     */
+    public PemWriter(Writer out) {
+        super(out);
+        String nl = System.getProperty("line.separator");
+        if (nl != null) {
+            nlLength = nl.length();
+        } else {
+            nlLength = 2;
+        }
+    }
+
+    /**
+     * Return the number of bytes or characters required to contain the
+     * passed in object if it is PEM encoded.
+     *
+     * @param obj pem object to be output
+     * @return an estimate of the number of bytes
+     */
+    public int getOutputSize(PemObject obj) {
+        // BEGIN and END boundaries.
+        int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4;
+        if (!obj.getHeaders().isEmpty()) {
+            for (Iterator it = obj.getHeaders().iterator(); it.hasNext(); ) {
+                PemHeader hdr = (PemHeader) it.next();
+                size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength;
+            }
+            size += nlLength;
+        }
+        // base64 encoding
+        int dataLen = ((obj.getContent().length + 2) / 3) * 4;
+        size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength);
+        return size;
+    }
+
+    public void writeObject(PemObjectGenerator objGen) throws IOException {
+        PemObject obj = objGen.generate();
+        writePreEncapsulationBoundary(obj.getType());
+        if (!obj.getHeaders().isEmpty()) {
+            for (Iterator it = obj.getHeaders().iterator(); it.hasNext(); ) {
+                PemHeader hdr = (PemHeader) it.next();
+                this.write(hdr.getName());
+                this.write(": ");
+                this.write(hdr.getValue());
+                this.newLine();
+            }
+            this.newLine();
+        }
+        writeEncoded(obj.getContent());
+        writePostEncapsulationBoundary(obj.getType());
+    }
+
+    private void writeEncoded(byte[] bytes) throws IOException {
+        bytes = Base64.encode(bytes);
+        for (int i = 0; i < bytes.length; i += buf.length) {
+            int index = 0;
+            while (index != buf.length) {
+                if ((i + index) >= bytes.length) {
+                    break;
+                }
+                buf[index] = (char) bytes[i + index];
+                index++;
+            }
+            this.write(buf, 0, index);
+            this.newLine();
+        }
+    }
+
+    private void writePreEncapsulationBoundary(String type) throws IOException {
+        this.write("-----BEGIN " + type + "-----");
+        this.newLine();
+    }
+
+    private void writePostEncapsulationBoundary(String type) throws IOException {
+        this.write("-----END " + type + "-----");
+        this.newLine();
+    }
+}

+ 225 - 0
app/src/main/java/org/strongswan/android/data/Server.java

@@ -0,0 +1,225 @@
+package org.strongswan.android.data;
+
+/**
+ * Created by WASEEM AKRAM on 30-Sep-17.
+ */
+
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+public class Server implements Serializable {
+
+    @SerializedName("id")
+    @Expose
+    private Integer id;
+    @SerializedName("continent")
+    @Expose
+    private String continent;
+    @SerializedName("country")
+    @Expose
+    private String country;
+    @SerializedName("state")
+    @Expose
+    private String state;
+    @SerializedName("city")
+    @Expose
+    private Object city;
+    @SerializedName("name")
+    @Expose
+    private String name;
+    @SerializedName("dns")
+    @Expose
+    private String dns;
+    @SerializedName("iso")
+    @Expose
+    private String iso;
+    @SerializedName("lt")
+    @Expose
+    private String lt;
+    @SerializedName("lg")
+    @Expose
+    private String lg;
+    @SerializedName("ip")
+    @Expose
+    private Object ip;
+    @SerializedName("port")
+    @Expose
+    private String port;
+    @SerializedName("protocol")
+    @Expose
+    private String protocol;
+    @SerializedName("ipsec")
+    @Expose
+    private Object ipsec;
+    @SerializedName("remote_id")
+    @Expose
+    private Object remoteId;
+    @SerializedName("is_trial")
+    @Expose
+    private Boolean isTrial;
+    @SerializedName("active")
+    @Expose
+    private Boolean active;
+    @SerializedName("flag")
+    @Expose
+    private String flag;
+    @SerializedName("is_favourited")
+    @Expose
+    private Boolean isFavourited;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getContinent() {
+        return continent;
+    }
+
+    public void setContinent(String continent) {
+        this.continent = continent;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    public void setCountry(String country) {
+        this.country = country;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public Object getCity() {
+        return city;
+    }
+
+    public void setCity(Object city) {
+        this.city = city;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDns() {
+        return dns;
+    }
+
+    public void setDns(String dns) {
+        this.dns = dns;
+    }
+
+    public String getIso() {
+        return iso;
+    }
+
+    public void setIso(String iso) {
+        this.iso = iso;
+    }
+
+    public String getLt() {
+        return lt;
+    }
+
+    public void setLt(String lt) {
+        this.lt = lt;
+    }
+
+    public String getLg() {
+        return lg;
+    }
+
+    public void setLg(String lg) {
+        this.lg = lg;
+    }
+
+    public Object getIp() {
+        return ip;
+    }
+
+    public void setIp(Object ip) {
+        this.ip = ip;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(String port) {
+        this.port = port;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public Object getIpsec() {
+        return ipsec;
+    }
+
+    public void setIpsec(Object ipsec) {
+        this.ipsec = ipsec;
+    }
+
+    public Object getRemoteId() {
+        return remoteId;
+    }
+
+    public void setRemoteId(Object remoteId) {
+        this.remoteId = remoteId;
+    }
+
+    public Boolean getIsTrial() {
+        return isTrial;
+    }
+
+    public void setIsTrial(Boolean isTrial) {
+        this.isTrial = isTrial;
+    }
+
+    public Boolean getActive() {
+        return active;
+    }
+
+    public void setActive(Boolean active) {
+        this.active = active;
+    }
+
+    public String getFlag() {
+        return flag;
+    }
+
+    public void setFlag(String flag) {
+        this.flag = flag;
+    }
+
+    public Boolean getIsFavourited() {
+        return isFavourited;
+    }
+
+    public void setIsFavourited(Boolean isFavourited) {
+        this.isFavourited = isFavourited;
+    }
+
+}

+ 364 - 0
app/src/main/java/org/strongswan/android/data/VpnProfile.java

@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2012-2019 Tobias Brunner
+ * Copyright (C) 2012 Giuliano Grassi
+ * Copyright (C) 2012 Ralf Sager
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.data;
+
+
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+public class VpnProfile implements Cloneable
+{
+	/* While storing this as EnumSet would be nicer this simplifies storing it in a database */
+	public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1;
+	public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2;
+
+	public static final int FLAGS_SUPPRESS_CERT_REQS = 1 << 0;
+	public static final int FLAGS_DISABLE_CRL = 1 << 1;
+	public static final int FLAGS_DISABLE_OCSP = 1 << 2;
+	public static final int FLAGS_STRICT_REVOCATION = 1 << 3;
+	public static final int FLAGS_RSA_PSS = 1 << 4;
+
+	private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate;
+	private String mRemoteId, mLocalId, mExcludedSubnets, mIncludedSubnets, mSelectedApps;
+	private String mIkeProposal, mEspProposal, mDnsServers;
+	private Integer mMTU, mPort, mSplitTunneling, mNATKeepAlive, mFlags;
+	private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
+	private VpnType mVpnType;
+	private UUID mUUID;
+	private long mId = -1;
+
+	public enum SelectedAppsHandling
+	{
+		SELECTED_APPS_DISABLE(0),
+		SELECTED_APPS_EXCLUDE(1),
+		SELECTED_APPS_ONLY(2);
+
+		private Integer mValue;
+
+		SelectedAppsHandling(int value)
+		{
+			mValue = value;
+		}
+
+		public Integer getValue()
+		{
+			return mValue;
+		}
+	}
+
+	public VpnProfile()
+	{
+		this.mUUID = UUID.randomUUID();
+	}
+
+	public long getId()
+	{
+		return mId;
+	}
+
+	public void setId(long id)
+	{
+		this.mId = id;
+	}
+
+	public void setUUID(UUID uuid)
+	{
+		this.mUUID = uuid;
+	}
+
+	public UUID getUUID()
+	{
+		return mUUID;
+	}
+
+	public String getName()
+	{
+		return mName;
+	}
+
+	public void setName(String name)
+	{
+		this.mName = name;
+	}
+
+	public String getGateway()
+	{
+		return mGateway;
+	}
+
+	public void setGateway(String gateway)
+	{
+		this.mGateway = gateway;
+	}
+
+	public VpnType getVpnType()
+	{
+		return mVpnType;
+	}
+
+	public void setVpnType(VpnType type)
+	{
+		this.mVpnType = type;
+	}
+
+	public String getIkeProposal()
+	{
+		return mIkeProposal;
+	}
+
+	public void setIkeProposal(String proposal)
+	{
+		this.mIkeProposal = proposal;
+	}
+
+	public String getEspProposal()
+	{
+		return mEspProposal;
+	}
+
+	public void setEspProposal(String proposal)
+	{
+		this.mEspProposal = proposal;
+	}
+
+	public String getDnsServers()
+	{
+		return mDnsServers;
+	}
+
+	public void setDnsServers(String dns)
+	{
+		this.mDnsServers = dns;
+	}
+
+	public String getUsername()
+	{
+		return mUsername;
+	}
+
+	public void setUsername(String username)
+	{
+		this.mUsername = username;
+	}
+
+	public String getPassword()
+	{
+		return mPassword;
+	}
+
+	public void setPassword(String password)
+	{
+		this.mPassword = password;
+	}
+
+	public String getCertificateAlias()
+	{
+		return mCertificate;
+	}
+
+	public void setCertificateAlias(String alias)
+	{
+		this.mCertificate = alias;
+	}
+
+	public String getUserCertificateAlias()
+	{
+		return mUserCertificate;
+	}
+
+	public void setUserCertificateAlias(String alias)
+	{
+		this.mUserCertificate = alias;
+	}
+
+	public String getLocalId()
+	{
+		return mLocalId;
+	}
+
+	public void setLocalId(String localId)
+	{
+		this.mLocalId = localId;
+	}
+
+	public String getRemoteId()
+	{
+		return mRemoteId;
+	}
+
+	public void setRemoteId(String remoteId)
+	{
+		this.mRemoteId = remoteId;
+	}
+
+	public Integer getMTU()
+	{
+		return mMTU;
+	}
+
+	public void setMTU(Integer mtu)
+	{
+		this.mMTU = mtu;
+	}
+
+	public Integer getPort()
+	{
+		return mPort;
+	}
+
+	public void setPort(Integer port)
+	{
+		this.mPort = port;
+	}
+
+	public Integer getNATKeepAlive()
+	{
+		return mNATKeepAlive;
+	}
+
+	public void setNATKeepAlive(Integer keepalive)
+	{
+		this.mNATKeepAlive = keepalive;
+	}
+
+	public void setExcludedSubnets(String excludedSubnets)
+	{
+		this.mExcludedSubnets = excludedSubnets;
+	}
+
+	public String getExcludedSubnets()
+	{
+		return mExcludedSubnets;
+	}
+
+	public void setIncludedSubnets(String includedSubnets)
+	{
+		this.mIncludedSubnets = includedSubnets;
+	}
+
+	public String getIncludedSubnets()
+	{
+		return mIncludedSubnets;
+	}
+
+	public void setSelectedApps(String selectedApps)
+	{
+		this.mSelectedApps = selectedApps;
+	}
+
+	public void setSelectedApps(SortedSet<String> selectedApps)
+	{
+		this.mSelectedApps = selectedApps.size() > 0 ? TextUtils.join(" ", selectedApps) : null;
+	}
+
+	public String getSelectedApps()
+	{
+		return mSelectedApps;
+	}
+
+	public SortedSet<String> getSelectedAppsSet()
+	{
+		TreeSet<String> set = new TreeSet<>();
+		if (!TextUtils.isEmpty(mSelectedApps))
+		{
+			set.addAll(Arrays.asList(mSelectedApps.split("\\s+")));
+		}
+		return set;
+	}
+
+	public void setSelectedAppsHandling(SelectedAppsHandling selectedAppsHandling)
+	{
+		this.mSelectedAppsHandling = selectedAppsHandling;
+	}
+
+	public void setSelectedAppsHandling(Integer value)
+	{
+		mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
+		for (SelectedAppsHandling handling : SelectedAppsHandling.values())
+		{
+			if (handling.mValue.equals(value))
+			{
+				mSelectedAppsHandling = handling;
+				break;
+			}
+		}
+	}
+
+	public SelectedAppsHandling getSelectedAppsHandling()
+	{
+		return mSelectedAppsHandling;
+	}
+
+	public Integer getSplitTunneling()
+	{
+		return mSplitTunneling;
+	}
+
+	public void setSplitTunneling(Integer splitTunneling)
+	{
+		this.mSplitTunneling = splitTunneling;
+	}
+
+	public Integer getFlags()
+	{
+		return mFlags == null ? 0 : mFlags;
+	}
+
+	public void setFlags(Integer flags)
+	{
+		this.mFlags = flags;
+	}
+
+	@Override
+	public String toString()
+	{
+		return mName;
+	}
+
+	@Override
+	public boolean equals(Object o)
+	{
+		if (o != null && o instanceof VpnProfile)
+		{
+			VpnProfile other = (VpnProfile)o;
+			if (this.mUUID != null && other.getUUID() != null)
+			{
+				return this.mUUID.equals(other.getUUID());
+			}
+			return this.mId == other.getId();
+		}
+		return false;
+	}
+
+	@Override
+	public VpnProfile clone()
+	{
+		try
+		{
+			return (VpnProfile)super.clone();
+		}
+		catch (CloneNotSupportedException e)
+		{
+			throw new AssertionError();
+		}
+	}
+}

+ 508 - 0
app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java

@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2012-2019 Tobias Brunner
+ * Copyright (C) 2012 Giuliano Grassi
+ * Copyright (C) 2012 Ralf Sager
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class VpnProfileDataSource
+{
+	private static final String TAG = VpnProfileDataSource.class.getSimpleName();
+	public static final String KEY_ID = "_id";
+	public static final String KEY_UUID = "_uuid";
+	public static final String KEY_NAME = "name";
+	public static final String KEY_GATEWAY = "gateway";
+	public static final String KEY_VPN_TYPE = "vpn_type";
+	public static final String KEY_USERNAME = "username";
+	public static final String KEY_PASSWORD = "password";
+	public static final String KEY_CERTIFICATE = "certificate";
+	public static final String KEY_USER_CERTIFICATE = "user_certificate";
+	public static final String KEY_MTU = "mtu";
+	public static final String KEY_PORT = "port";
+	public static final String KEY_SPLIT_TUNNELING = "split_tunneling";
+	public static final String KEY_LOCAL_ID = "local_id";
+	public static final String KEY_REMOTE_ID = "remote_id";
+	public static final String KEY_EXCLUDED_SUBNETS = "excluded_subnets";
+	public static final String KEY_INCLUDED_SUBNETS = "included_subnets";
+	public static final String KEY_SELECTED_APPS = "selected_apps";
+	public static final String KEY_SELECTED_APPS_LIST = "selected_apps_list";
+	public static final String KEY_NAT_KEEPALIVE = "nat_keepalive";
+	public static final String KEY_FLAGS = "flags";
+	public static final String KEY_IKE_PROPOSAL = "ike_proposal";
+	public static final String KEY_ESP_PROPOSAL = "esp_proposal";
+	public static final String KEY_DNS_SERVERS = "dns_servers";
+
+	private DatabaseHelper mDbHelper;
+	private SQLiteDatabase mDatabase;
+	private final Context mContext;
+
+	private static final String DATABASE_NAME = "strongswan.db";
+	private static final String TABLE_VPNPROFILE = "vpnprofile";
+
+	private static final int DATABASE_VERSION = 17;
+
+	public static final DbColumn[] COLUMNS = new DbColumn[] {
+								new DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
+								new DbColumn(KEY_UUID, "TEXT UNIQUE", 9),
+								new DbColumn(KEY_NAME, "TEXT NOT NULL", 1),
+								new DbColumn(KEY_GATEWAY, "TEXT NOT NULL", 1),
+								new DbColumn(KEY_VPN_TYPE, "TEXT NOT NULL", 3),
+								new DbColumn(KEY_USERNAME, "TEXT", 1),
+								new DbColumn(KEY_PASSWORD, "TEXT", 1),
+								new DbColumn(KEY_CERTIFICATE, "TEXT", 1),
+								new DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2),
+								new DbColumn(KEY_MTU, "INTEGER", 5),
+								new DbColumn(KEY_PORT, "INTEGER", 5),
+								new DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7),
+								new DbColumn(KEY_LOCAL_ID, "TEXT", 8),
+								new DbColumn(KEY_REMOTE_ID, "TEXT", 8),
+								new DbColumn(KEY_EXCLUDED_SUBNETS, "TEXT", 10),
+								new DbColumn(KEY_INCLUDED_SUBNETS, "TEXT", 11),
+								new DbColumn(KEY_SELECTED_APPS, "INTEGER", 12),
+								new DbColumn(KEY_SELECTED_APPS_LIST, "TEXT", 12),
+								new DbColumn(KEY_NAT_KEEPALIVE, "INTEGER", 13),
+								new DbColumn(KEY_FLAGS, "INTEGER", 14),
+								new DbColumn(KEY_IKE_PROPOSAL, "TEXT", 15),
+								new DbColumn(KEY_ESP_PROPOSAL, "TEXT", 15),
+								new DbColumn(KEY_DNS_SERVERS, "TEXT", 17),
+							};
+
+	private static final String[] ALL_COLUMNS = getColumns(DATABASE_VERSION);
+
+	private static String getDatabaseCreate(int version)
+	{
+		boolean first = true;
+		StringBuilder create = new StringBuilder("CREATE TABLE ");
+		create.append(TABLE_VPNPROFILE);
+		create.append(" (");
+		for (DbColumn column : COLUMNS)
+		{
+			if (column.Since <= version)
+			{
+				if (!first)
+				{
+					create.append(",");
+				}
+				first = false;
+				create.append(column.Name);
+				create.append(" ");
+				create.append(column.Type);
+			}
+		}
+		create.append(");");
+		return create.toString();
+	}
+
+	private static String[] getColumns(int version)
+	{
+		ArrayList<String> columns = new ArrayList<>();
+		for (DbColumn column : COLUMNS)
+		{
+			if (column.Since <= version)
+			{
+				columns.add(column.Name);
+			}
+		}
+		return columns.toArray(new String[0]);
+	}
+
+	private static class DatabaseHelper extends SQLiteOpenHelper
+	{
+		public DatabaseHelper(Context context)
+		{
+			super(context, DATABASE_NAME, null, DATABASE_VERSION);
+		}
+
+		@Override
+		public void onCreate(SQLiteDatabase database)
+		{
+			database.execSQL(getDatabaseCreate(DATABASE_VERSION));
+		}
+
+		@Override
+		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
+		{
+			Log.w(TAG, "Upgrading database from version " + oldVersion +
+				  " to " + newVersion);
+			if (oldVersion < 2)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE +
+						   " TEXT;");
+			}
+			if (oldVersion < 3)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE +
+						   " TEXT DEFAULT '';");
+			}
+			if (oldVersion < 4)
+			{	/* remove NOT NULL constraint from username column */
+				updateColumns(db, 4);
+			}
+			if (oldVersion < 5)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU +
+						   " INTEGER;");
+			}
+			if (oldVersion < 6)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT +
+						   " INTEGER;");
+			}
+			if (oldVersion < 7)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING +
+						   " INTEGER;");
+			}
+			if (oldVersion < 8)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID +
+						   " TEXT;");
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
+						   " TEXT;");
+			}
+			if (oldVersion < 9)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID +
+						   " TEXT;");
+				updateColumns(db, 9);
+			}
+			if (oldVersion < 10)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS +
+						   " TEXT;");
+			}
+			if (oldVersion < 11)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS +
+						   " TEXT;");
+			}
+			if (oldVersion < 12)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS +
+						   " INTEGER;");
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST +
+						   " TEXT;");
+			}
+			if (oldVersion < 13)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_NAT_KEEPALIVE +
+						   " INTEGER;");
+			}
+			if (oldVersion < 14)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_FLAGS +
+						   " INTEGER;");
+			}
+			if (oldVersion < 15)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_IKE_PROPOSAL +
+						   " TEXT;");
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL +
+						   " TEXT;");
+			}
+			if (oldVersion < 16)
+			{	/* add a UUID to all entries that haven't one yet */
+				db.beginTransaction();
+				try
+				{
+					Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), KEY_UUID + " is NULL", null, null, null, null);
+					for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
+					{
+						ContentValues values = new ContentValues();
+						values.put(KEY_UUID, UUID.randomUUID().toString());
+						db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndex(KEY_ID)), null);
+					}
+					cursor.close();
+					db.setTransactionSuccessful();
+				}
+				finally
+				{
+					db.endTransaction();
+				}
+			}
+			if (oldVersion < 17)
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_DNS_SERVERS +
+						   " TEXT;");
+			}
+		}
+
+		private void updateColumns(SQLiteDatabase db, int version)
+		{
+			db.beginTransaction();
+			try
+			{
+				db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
+				db.execSQL(getDatabaseCreate(version));
+				StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
+				SQLiteQueryBuilder.appendColumns(insert, getColumns(version));
+				db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
+				db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
+				db.setTransactionSuccessful();
+			}
+			finally
+			{
+				db.endTransaction();
+			}
+		}
+	}
+
+	/**
+	 * Construct a new VPN profile data source. The context is used to
+	 * open/create the database.
+	 * @param context context used to access the database
+	 */
+	public VpnProfileDataSource(Context context)
+	{
+		this.mContext = context;
+	}
+
+	/**
+	 * Open the VPN profile data source. The database is automatically created
+	 * if it does not yet exist. If that fails an exception is thrown.
+	 * @return itself (allows to chain initialization calls)
+	 * @throws SQLException if the database could not be opened or created
+	 */
+	public VpnProfileDataSource open() throws SQLException
+	{
+		if (mDbHelper == null)
+		{
+			mDbHelper = new DatabaseHelper(mContext);
+			mDatabase = mDbHelper.getWritableDatabase();
+		}
+		return this;
+	}
+
+	/**
+	 * Close the data source.
+	 */
+	public void close()
+	{
+		if (mDbHelper != null)
+		{
+			mDbHelper.close();
+			mDbHelper = null;
+		}
+	}
+
+	/**
+	 * Insert the given VPN profile into the database.  On success the Id of
+	 * the object is updated and the object returned.
+	 *
+	 * @param profile the profile to add
+	 * @return the added VPN profile or null, if failed
+	 */
+	public VpnProfile insertProfile(VpnProfile profile)
+	{
+		ContentValues values = ContentValuesFromVpnProfile(profile);
+		long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values);
+		if (insertId == -1)
+		{
+			return null;
+		}
+		profile.setId(insertId);
+		return profile;
+	}
+
+	/**
+	 * Updates the given VPN profile in the database.
+	 * @param profile the profile to update
+	 * @return true if update succeeded, false otherwise
+	 */
+	public boolean updateVpnProfile(VpnProfile profile)
+	{
+		long id = profile.getId();
+		ContentValues values = ContentValuesFromVpnProfile(profile);
+		return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0;
+	}
+
+	/**
+	 * Delete the given VPN profile from the database.
+	 * @param profile the profile to delete
+	 * @return true if deleted, false otherwise
+	 */
+	public boolean deleteVpnProfile(VpnProfile profile)
+	{
+		long id = profile.getId();
+		return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0;
+	}
+
+	/**
+	 * Get a single VPN profile from the database.
+	 * @param id the ID of the VPN profile
+	 * @return the profile or null, if not found
+	 */
+	public VpnProfile getVpnProfile(long id)
+	{
+		VpnProfile profile = null;
+		Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
+										KEY_ID + "=" + id, null, null, null, null);
+		if (cursor.moveToFirst())
+		{
+			profile = VpnProfileFromCursor(cursor);
+		}
+		cursor.close();
+		return profile;
+	}
+
+	/**
+	 * Get a single VPN profile from the database by its UUID.
+	 * @param uuid the UUID of the VPN profile
+	 * @return the profile or null, if not found
+	 */
+	public VpnProfile getVpnProfile(UUID uuid)
+	{
+		VpnProfile profile = null;
+		Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
+										KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null);
+		if (cursor.moveToFirst())
+		{
+			profile = VpnProfileFromCursor(cursor);
+		}
+		cursor.close();
+		return profile;
+	}
+
+	/**
+	 * Get a single VPN profile from the database by its UUID as String.
+	 * @param uuid the UUID of the VPN profile as String
+	 * @return the profile or null, if not found
+	 */
+	public VpnProfile getVpnProfile(String uuid)
+	{
+		try
+		{
+			if (uuid != null)
+			{
+				return getVpnProfile(UUID.fromString(uuid));
+			}
+			return null;
+		}
+		catch (IllegalArgumentException e)
+		{
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * Get a list of all VPN profiles stored in the database.
+	 * @return list of VPN profiles
+	 */
+	public List<VpnProfile> getAllVpnProfiles()
+	{
+		List<VpnProfile> vpnProfiles = new ArrayList<VpnProfile>();
+
+		Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null);
+		cursor.moveToFirst();
+		while (!cursor.isAfterLast())
+		{
+			VpnProfile vpnProfile = VpnProfileFromCursor(cursor);
+			vpnProfiles.add(vpnProfile);
+			cursor.moveToNext();
+		}
+		cursor.close();
+		return vpnProfiles;
+	}
+
+	private VpnProfile VpnProfileFromCursor(Cursor cursor)
+	{
+		VpnProfile profile = new VpnProfile();
+		profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
+		profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndex(KEY_UUID))));
+		profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
+		profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY)));
+		profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE))));
+		profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME)));
+		profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD)));
+		profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE)));
+		profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE)));
+		profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU)));
+		profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT)));
+		profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING)));
+		profile.setLocalId(cursor.getString(cursor.getColumnIndex(KEY_LOCAL_ID)));
+		profile.setRemoteId(cursor.getString(cursor.getColumnIndex(KEY_REMOTE_ID)));
+		profile.setExcludedSubnets(cursor.getString(cursor.getColumnIndex(KEY_EXCLUDED_SUBNETS)));
+		profile.setIncludedSubnets(cursor.getString(cursor.getColumnIndex(KEY_INCLUDED_SUBNETS)));
+		profile.setSelectedAppsHandling(getInt(cursor, cursor.getColumnIndex(KEY_SELECTED_APPS)));
+		profile.setSelectedApps(cursor.getString(cursor.getColumnIndex(KEY_SELECTED_APPS_LIST)));
+		profile.setNATKeepAlive(getInt(cursor, cursor.getColumnIndex(KEY_NAT_KEEPALIVE)));
+		profile.setFlags(getInt(cursor, cursor.getColumnIndex(KEY_FLAGS)));
+		profile.setIkeProposal(cursor.getString(cursor.getColumnIndex(KEY_IKE_PROPOSAL)));
+		profile.setEspProposal(cursor.getString(cursor.getColumnIndex(KEY_ESP_PROPOSAL)));
+		profile.setDnsServers(cursor.getString(cursor.getColumnIndex(KEY_DNS_SERVERS)));
+		return profile;
+	}
+
+	private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
+	{
+		ContentValues values = new ContentValues();
+		values.put(KEY_UUID, profile.getUUID().toString());
+		values.put(KEY_NAME, profile.getName());
+		values.put(KEY_GATEWAY, profile.getGateway());
+		values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
+		values.put(KEY_USERNAME, profile.getUsername());
+		values.put(KEY_PASSWORD, profile.getPassword());
+		values.put(KEY_CERTIFICATE, profile.getCertificateAlias());
+		values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias());
+		values.put(KEY_MTU, profile.getMTU());
+		values.put(KEY_PORT, profile.getPort());
+		values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling());
+		values.put(KEY_LOCAL_ID, profile.getLocalId());
+		values.put(KEY_REMOTE_ID, profile.getRemoteId());
+		values.put(KEY_EXCLUDED_SUBNETS, profile.getExcludedSubnets());
+		values.put(KEY_INCLUDED_SUBNETS, profile.getIncludedSubnets());
+		values.put(KEY_SELECTED_APPS, profile.getSelectedAppsHandling().getValue());
+		values.put(KEY_SELECTED_APPS_LIST, profile.getSelectedApps());
+		values.put(KEY_NAT_KEEPALIVE, profile.getNATKeepAlive());
+		values.put(KEY_FLAGS, profile.getFlags());
+		values.put(KEY_IKE_PROPOSAL, profile.getIkeProposal());
+		values.put(KEY_ESP_PROPOSAL, profile.getEspProposal());
+		values.put(KEY_DNS_SERVERS, profile.getDnsServers());
+		return values;
+	}
+
+	private Integer getInt(Cursor cursor, int columnIndex)
+	{
+		return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
+	}
+
+	private static class DbColumn
+	{
+		public final String Name;
+		public final String Type;
+		public final Integer Since;
+
+		public DbColumn(String name, String type, Integer since)
+		{
+			Name = name;
+			Type = type;
+			Since = since;
+		}
+	}
+}

+ 94 - 0
app/src/main/java/org/strongswan/android/data/VpnType.java

@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012-2014 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.data;
+
+import java.util.EnumSet;
+
+public enum VpnType
+{
+	/* the order here must match the items in R.array.vpn_types */
+	IKEV2_EAP("ikev2-eap", EnumSet.of(VpnTypeFeature.USER_PASS)),
+	IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)),
+	IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)),
+	IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)),
+	IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD));
+
+	/**
+	 * Features of a VPN type.
+	 */
+	public enum VpnTypeFeature
+	{
+		/** client certificate is required */
+		CERTIFICATE,
+		/** username and password are required */
+		USER_PASS,
+		/** enable BYOD features */
+		BYOD;
+	}
+
+	private String mIdentifier;
+	private EnumSet<VpnTypeFeature> mFeatures;
+
+	/**
+	 * Enum which provides additional information about the supported VPN types.
+	 *
+	 * @param id identifier used to store and transmit this specific type
+	 * @param features of the given VPN type
+	 * @param certificate true if a client certificate is required
+	 */
+	VpnType(String id, EnumSet<VpnTypeFeature> features)
+	{
+		mIdentifier = id;
+		mFeatures = features;
+	}
+
+	/**
+	 * The identifier used to store this value in the database
+	 * @return identifier
+	 */
+	public String getIdentifier()
+	{
+		return mIdentifier;
+	}
+
+	/**
+	 * Checks whether a feature is supported/required by this type of VPN.
+	 *
+	 * @return true if the feature is supported/required
+	 */
+	public boolean has(VpnTypeFeature feature)
+	{
+		return mFeatures.contains(feature);
+	}
+
+	/**
+	 * Get the enum entry with the given identifier.
+	 *
+	 * @param identifier get the enum entry with this identifier
+	 * @return the enum entry, or the default if not found
+	 */
+	public static VpnType fromIdentifier(String identifier)
+	{
+		for (VpnType type : VpnType.values())
+		{
+			if (identifier.equals(type.mIdentifier))
+			{
+				return type;
+			}
+		}
+		return VpnType.IKEV2_EAP;
+	}
+}

ファイルの差分が大きいため隠しています
+ 1400 - 0
app/src/main/java/org/strongswan/android/logic/CharonVpnService.java


+ 125 - 0
app/src/main/java/org/strongswan/android/logic/NetworkManager.java

@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012-2015 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import java.util.LinkedList;
+
+public class NetworkManager extends BroadcastReceiver implements Runnable
+{
+	private final Context mContext;
+	private volatile boolean mRegistered;
+	private Thread mEventNotifier;
+	private LinkedList<Boolean> mEvents = new LinkedList<>();
+
+	public NetworkManager(Context context)
+	{
+		mContext = context;
+	}
+
+	public void Register()
+	{
+		mEvents.clear();
+		mRegistered = true;
+		mEventNotifier = new Thread(this);
+		mEventNotifier.start();
+		mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+	}
+
+	public void Unregister()
+	{
+		mContext.unregisterReceiver(this);
+		mRegistered = false;
+		synchronized (this)
+		{
+			notifyAll();
+		}
+		try
+		{
+			mEventNotifier.join();
+			mEventNotifier = null;
+		}
+		catch (InterruptedException e)
+		{
+			e.printStackTrace();
+		}
+	}
+
+	public boolean isConnected()
+	{
+		ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+		NetworkInfo info = null;
+		if (cm != null)
+		{
+			info = cm.getActiveNetworkInfo();
+		}
+		return info != null && info.isConnected();
+	}
+
+	@Override
+	public void onReceive(Context context, Intent intent)
+	{
+		synchronized (this)
+		{
+			mEvents.addLast(isConnected());
+			notifyAll();
+		}
+	}
+
+	@Override
+	public void run()
+	{
+		while (mRegistered)
+		{
+			boolean connected;
+
+			synchronized (this)
+			{
+				try
+				{
+					while (mRegistered && mEvents.isEmpty())
+					{
+						wait();
+					}
+				}
+				catch (InterruptedException ex)
+				{
+					break;
+				}
+				if (!mRegistered)
+				{
+					break;
+				}
+				connected = mEvents.removeFirst();
+			}
+			/* call the native parts without holding the lock */
+			networkChanged(!connected);
+		}
+	}
+
+	/**
+	 * Notify the native parts about a network change
+	 *
+	 * @param disconnected true if no connection is available at the moment
+	 */
+	public native void networkChanged(boolean disconnected);
+}

+ 165 - 0
app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java

@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017-2018 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic;
+
+import androidx.annotation.Keep;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@Keep
+public class SimpleFetcher
+{
+	private static ExecutorService mExecutor = Executors.newCachedThreadPool();
+	private static Object mLock = new Object();
+	private static ArrayList<Future> mFutures = new ArrayList<>();
+	private static boolean mDisabled;
+
+	public static byte[] fetch(String uri, byte[] data, String contentType)
+	{
+		Future<byte[]> future;
+
+		synchronized (mLock)
+		{
+			if (mDisabled)
+			{
+				return null;
+			}
+			future = mExecutor.submit(() -> {
+				URL url = new URL(uri);
+				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+				conn.setConnectTimeout(10000);
+				conn.setReadTimeout(10000);
+				try
+				{
+					if (contentType != null)
+					{
+						conn.setRequestProperty("Content-Type", contentType);
+					}
+					if (data != null)
+					{
+						conn.setDoOutput(true);
+						conn.setFixedLengthStreamingMode(data.length);
+						OutputStream out = new BufferedOutputStream(conn.getOutputStream());
+						out.write(data);
+						out.close();
+					}
+					return streamToArray(conn.getInputStream());
+				}
+				catch (SocketTimeoutException e)
+				{
+					return null;
+				}
+				finally
+				{
+					conn.disconnect();
+				}
+			});
+
+			mFutures.add(future);
+		}
+
+		try
+		{
+			/* this enforces a timeout as the ones set on HttpURLConnection might not work reliably */
+			return future.get(10000, TimeUnit.MILLISECONDS);
+		}
+		catch (InterruptedException|ExecutionException|TimeoutException|CancellationException e)
+		{
+			return null;
+		}
+		finally
+		{
+			synchronized (mLock)
+			{
+				mFutures.remove(future);
+			}
+		}
+	}
+
+	/**
+	 * Enable fetching after it has been disabled.
+	 */
+	public static void enable()
+	{
+		synchronized (mLock)
+		{
+			mDisabled = false;
+		}
+	}
+
+	/**
+	 * Disable the fetcher and abort any future requests.
+	 *
+	 * The native thread is not cancelable as it is working on an IKE_SA (cancelling the methods of
+	 * HttpURLConnection is not reliably possible anyway), so to abort while fetching we cancel the
+	 * Future (causing a return from fetch() immediately) and let the executor thread continue its
+	 * thing in the background.
+	 *
+	 * Also prevents future fetches until enabled again (e.g. if we aborted OCSP but would then
+	 * block in the subsequent fetch for a CRL).
+	 */
+	public static void disable()
+	{
+		synchronized (mLock)
+		{
+			mDisabled = true;
+			for (Future future : mFutures)
+			{
+				future.cancel(true);
+			}
+		}
+	}
+
+	private static byte[] streamToArray(InputStream in) throws IOException
+	{
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		byte[] buf = new byte[1024];
+		int len;
+
+		try
+		{
+			while ((len = in.read(buf)) != -1)
+			{
+				out.write(buf, 0, len);
+			}
+			return out.toByteArray();
+		}
+		catch (IOException e)
+		{
+			e.printStackTrace();
+		}
+		finally
+		{
+			in.close();
+		}
+		return null;
+	}
+}

+ 74 - 0
app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Build;
+
+import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
+
+import java.security.Security;
+
+public class StrongSwanApplication extends Application
+{
+	public static Context mContext;
+
+	static {
+		Security.addProvider(new LocalCertificateKeyStoreProvider());
+	}
+
+	@Override
+	public void onCreate()
+	{
+		super.onCreate();
+		StrongSwanApplication.mContext = getApplicationContext();
+	}
+
+	/**
+	 * Returns the current application context
+	 * @return context
+	 */
+	public static Context getContext()
+	{
+		return StrongSwanApplication.mContext;
+	}
+
+	/*https://wiki.strongswan.org › projects › activity
+10-Dec-2016 — 12:2
+	 * The libraries are extracted to /data/data/org.strongswan.android/...
+	 * during installation.  On newer releases most are loaded in JNI_OnLoad.
+	 */
+	static
+	{
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
+		{
+			System.loadLibrary("strongswan");
+
+			//if (MainActivity.USE_BYOD)
+			//{
+				System.loadLibrary("tpmtss");
+				System.loadLibrary("tncif");
+				System.loadLibrary("tnccs");
+				System.loadLibrary("imcv");
+			//}
+
+			System.loadLibrary("charon");
+			System.loadLibrary("ipsec");
+		}
+		System.loadLibrary("androidbridge");
+	}
+}

+ 262 - 0
app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java

@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012 Giuliano Grassi
+ * Copyright (C) 2012 Ralf Sager
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic;
+
+import android.util.Log;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Observable;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class TrustedCertificateManager extends Observable
+{
+	private static final String TAG = TrustedCertificateManager.class.getSimpleName();
+	private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+	private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>();
+	private volatile boolean mReload;
+	private boolean mLoaded;
+	private final ArrayList<KeyStore> mKeyStores = new ArrayList<KeyStore>();
+
+	public enum TrustedCertificateSource
+	{
+		SYSTEM("system:"),
+		USER("user:"),
+		LOCAL("local:");
+
+		private final String mPrefix;
+
+		private TrustedCertificateSource(String prefix)
+		{
+			mPrefix = prefix;
+		}
+
+		private String getPrefix()
+		{
+			return mPrefix;
+		}
+	}
+
+	/**
+	 * Private constructor to prevent instantiation from other classes.
+	 */
+	private TrustedCertificateManager()
+	{
+		for (String name : new String[]{"LocalCertificateStore", "AndroidCAStore"})
+		{
+			KeyStore store;
+			try
+			{
+				store = KeyStore.getInstance(name);
+				store.load(null, null);
+				mKeyStores.add(store);
+			}
+			catch (Exception e)
+			{
+				Log.e(TAG, "Unable to load KeyStore: " + name);
+				e.printStackTrace();
+			}
+		}
+	}
+
+	/**
+	 * This is not instantiated until the first call to getInstance()
+	 */
+	private static class Singleton
+	{
+		public static final TrustedCertificateManager mInstance = new TrustedCertificateManager();
+	}
+
+	/**
+	 * Get the single instance of the CA certificate manager.
+	 *
+	 * @return CA certificate manager
+	 */
+	public static TrustedCertificateManager getInstance()
+	{
+		return Singleton.mInstance;
+	}
+
+	/**
+	 * Invalidates the current load state so that the next call to load()
+	 * will force a reload of the cached CA certificates.
+	 *
+	 * Observers are notified when this method is called.
+	 *
+	 * @return reference to itself
+	 */
+	public TrustedCertificateManager reset()
+	{
+		Log.d(TAG, "Force reload of cached CA certificates on next load");
+		this.mReload = true;
+		this.setChanged();
+		this.notifyObservers();
+		return this;
+	}
+
+	/**
+	 * Ensures that the certificates are loaded but does not force a reload.
+	 * As this takes a while if the certificates are not loaded yet it should
+	 * be called asynchronously.
+	 *
+	 * Observers are only notified when the certificates are initially loaded, not when reloaded.
+	 *
+	 * @return reference to itself
+	 */
+	public TrustedCertificateManager load()
+	{
+		Log.d(TAG, "Ensure cached CA certificates are loaded");
+		this.mLock.writeLock().lock();
+		if (!this.mLoaded || this.mReload)
+		{
+			this.mReload = false;
+			loadCertificates();
+		}
+		this.mLock.writeLock().unlock();
+		return this;
+	}
+
+	/**
+	 * Opens the CA certificate KeyStore and loads the cached certificates.
+	 * The lock must be locked when calling this method.
+	 */
+	private void loadCertificates()
+	{
+		Log.d(TAG, "Load cached CA certificates");
+		Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
+		for (KeyStore store : this.mKeyStores)
+		{
+			fetchCertificates(certs, store);
+		}
+		this.mCACerts = certs;
+		if (!this.mLoaded)
+		{
+			this.setChanged();
+			this.notifyObservers();
+			this.mLoaded = true;
+		}
+		Log.d(TAG, "Cached CA certificates loaded");
+	}
+
+	/**
+	 * Load all X.509 certificates from the given KeyStore.
+	 *
+	 * @param certs Hashtable to store certificates in
+	 * @param store KeyStore to load certificates from
+	 */
+	private void fetchCertificates(Hashtable<String, X509Certificate> certs, KeyStore store)
+	{
+		try
+		{
+			Enumeration<String> aliases = store.aliases();
+			while (aliases.hasMoreElements())
+			{
+				String alias = aliases.nextElement();
+				Certificate cert;
+				cert = store.getCertificate(alias);
+				if (cert != null && cert instanceof X509Certificate)
+				{
+					certs.put(alias, (X509Certificate)cert);
+				}
+			}
+		}
+		catch (KeyStoreException ex)
+		{
+			ex.printStackTrace();
+		}
+	}
+
+	/**
+	 * Retrieve the CA certificate with the given alias.
+	 *
+	 * @param alias alias of the certificate to get
+	 * @return the certificate, null if not found
+	 */
+	public X509Certificate getCACertificateFromAlias(String alias)
+	{
+		X509Certificate certificate = null;
+
+		if (this.mLock.readLock().tryLock())
+		{
+			certificate = this.mCACerts.get(alias);
+			this.mLock.readLock().unlock();
+		}
+		else
+		{	/* if we cannot get the lock load it directly from the KeyStore,
+			 * should be fast for a single certificate */
+			for (KeyStore store : this.mKeyStores)
+			{
+				try
+				{
+					Certificate cert = store.getCertificate(alias);
+					if (cert != null && cert instanceof X509Certificate)
+					{
+						certificate = (X509Certificate)cert;
+						break;
+					}
+				}
+				catch (KeyStoreException e)
+				{
+					e.printStackTrace();
+				}
+			}
+		}
+		return certificate;
+	}
+
+	/**
+	 * Get all CA certificates (from all keystores).
+	 *
+	 * @return Hashtable mapping aliases to certificates
+	 */
+	@SuppressWarnings("unchecked")
+	public Hashtable<String, X509Certificate> getAllCACertificates()
+	{
+		Hashtable<String, X509Certificate> certs;
+		this.mLock.readLock().lock();
+		certs = (Hashtable<String, X509Certificate>)this.mCACerts.clone();
+		this.mLock.readLock().unlock();
+		return certs;
+	}
+
+	/**
+	 * Get all certificates from the given source.
+	 *
+	 * @param source type to filter certificates
+	 * @return Hashtable mapping aliases to certificates
+	 */
+	public Hashtable<String, X509Certificate> getCACertificates(TrustedCertificateSource source)
+	{
+		Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
+		this.mLock.readLock().lock();
+		for (String alias : this.mCACerts.keySet())
+		{
+			if (alias.startsWith(source.getPrefix()))
+			{
+				certs.put(alias, this.mCACerts.get(alias));
+			}
+		}
+		this.mLock.readLock().unlock();
+		return certs;
+	}
+}

+ 630 - 0
app/src/main/java/org/strongswan/android/logic/VpnStateService.java

@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2012-2017 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.vpn.fastestvpnservice.R;
+
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType;
+import org.strongswan.android.logic.imc.ImcState;
+import org.strongswan.android.logic.imc.RemediationInstruction;
+import org.strongswan.android.ui.VpnProfileControlActivity;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import de.blinkt.openvpn.core.App;
+
+public class VpnStateService extends Service
+{private static final String tag = "ttt VpnStateService";
+	private final HashSet<VpnStateListener> mListeners = new HashSet<VpnStateListener>();
+	private final IBinder mBinder = new LocalBinder();
+	private long mConnectionID = 0;
+	private Handler mHandler;
+	private VpnProfile mProfile;
+	private State mState = State.DISABLED;
+	private ErrorState mError = ErrorState.NO_ERROR;
+	private ImcState mImcState = ImcState.UNKNOWN;
+	private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
+	private static long RETRY_INTERVAL = 1000;
+	/* cap the retry interval at 2 minutes */
+	private static long MAX_RETRY_INTERVAL = 120000;
+	private static int RETRY_MSG = 1;
+	private RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider();
+	private long mRetryTimeout;
+	private long mRetryIn;
+
+	public enum State
+	{
+		DISABLED,
+		CONNECTING,
+		CONNECTED,
+		DISCONNECTING,
+	}
+
+	public enum ErrorState
+	{
+		NO_ERROR,
+		AUTH_FAILED,
+		PEER_AUTH_FAILED,
+		LOOKUP_FAILED,
+		UNREACHABLE,
+		GENERIC_ERROR,
+		PASSWORD_MISSING,
+		CERTIFICATE_UNAVAILABLE,
+	}
+
+	/**
+	 * Listener interface for bound clients that are interested in changes to
+	 * this Service.
+	 */
+	public interface VpnStateListener
+	{
+		public void stateChanged();
+	}
+
+	/**
+	 * Simple Binder that allows to directly access this Service class itself
+	 * after binding to it.
+	 */
+	public class LocalBinder extends Binder
+	{
+		public VpnStateService getService()
+		{
+			return VpnStateService.this;
+		}
+	}
+
+	@Override
+	public void onCreate()
+	{
+		/* this handler allows us to notify listeners from the UI thread and
+		 * not from the threads that actually report any state changes */
+		mHandler = new RetryHandler(this);
+	}
+
+	@Override
+	public IBinder onBind(Intent intent)
+	{
+		return mBinder;
+	}
+
+	@Override
+	public void onDestroy()
+	{
+	}
+
+	/**
+	 * Register a listener with this Service. We assume this is called from
+	 * the main thread so no synchronization is happening.
+	 *
+	 * @param listener listener to register
+	 */
+	public void registerListener(VpnStateListener listener)
+	{
+		mListeners.add(listener);
+	}
+
+	/**
+	 * Unregister a listener from this Service.
+	 *
+	 * @param listener listener to unregister
+	 */
+	public void unregisterListener(VpnStateListener listener)
+	{
+		mListeners.remove(listener);
+	}
+
+	/**
+	 * Get the current VPN profile.
+	 *
+	 * @return profile
+	 */
+	public VpnProfile getProfile()
+	{	/* only updated from the main thread so no synchronization needed */
+		return mProfile;
+	}
+
+	/**
+	 * Get the current connection ID.  May be used to track which state
+	 * changes have already been handled.
+	 *
+	 * Is increased when startConnection() is called.
+	 *
+	 * @return connection ID
+	 */
+	public long getConnectionID()
+	{	/* only updated from the main thread so no synchronization needed */
+		return mConnectionID;
+	}
+
+	/**
+	 * Get the total number of seconds until there is an automatic retry to reconnect.
+	 * @return total number of seconds until the retry
+	 */
+	public int getRetryTimeout()
+	{
+		return (int)(mRetryTimeout / 1000);
+	}
+
+	/**
+	 * Get the number of seconds until there is an automatic retry to reconnect.
+	 * @return number of seconds until the retry
+	 */
+	public int getRetryIn()
+	{
+		return (int)(mRetryIn / 1000);
+	}
+
+	/**
+	 * Get the current state.
+	 *
+	 * @return state
+	 */
+	public State getState()
+	{	/* only updated from the main thread so no synchronization needed */
+		return mState;
+	}
+
+	/**
+	 * Get the current error, if any.
+	 *
+	 * @return error
+	 */
+	public ErrorState getErrorState()
+	{	/* only updated from the main thread so no synchronization needed */
+		return mError;
+	}
+
+	/**
+	 * Get a description of the current error, if any.
+	 *
+	 * @return error description text id
+	 */
+	public int getErrorText()
+	{
+		switch (mError)
+		{
+			case AUTH_FAILED:
+				if (mImcState == ImcState.BLOCK)
+				{
+					return R.string.error_assessment_failed;
+				}
+				else
+				{
+					return R.string.error_auth_failed;
+				}
+			case PEER_AUTH_FAILED:
+				return R.string.error_peer_auth_failed;
+			case LOOKUP_FAILED:
+				return R.string.error_lookup_failed;
+			case UNREACHABLE:
+				return R.string.error_unreachable;
+			case PASSWORD_MISSING:
+				return R.string.error_password_missing;
+			case CERTIFICATE_UNAVAILABLE:
+				return R.string.error_certificate_unavailable;
+			default:
+				return R.string.error_generic;
+		}
+	}
+
+	/**
+	 * Get the current IMC state, if any.
+	 *
+	 * @return imc state
+	 */
+	public ImcState getImcState()
+	{	/* only updated from the main thread so no synchronization needed */
+		return mImcState;
+	}
+
+	/**
+	 * Get the remediation instructions, if any.
+	 *
+	 * @return read-only list of instructions
+	 */
+	public List<RemediationInstruction> getRemediationInstructions()
+	{	/* only updated from the main thread so no synchronization needed */
+		return Collections.unmodifiableList(mRemediationInstructions);
+	}
+
+	/**
+	 * Disconnect any existing connection and shutdown the daemon, the
+	 * VpnService is not stopped but it is reset so new connections can be
+	 * started.
+	 */
+	public void disconnect()
+	{
+		/* reset any potential retry timer and error state */
+		resetRetryTimer();
+		setError(ErrorState.NO_ERROR);
+
+		/* as soon as the TUN device is created by calling establish() on the
+		 * VpnService.Builder object the system binds to the service and keeps
+		 * bound until the file descriptor of the TUN device is closed.  thus
+		 * calling stopService() here would not stop (destroy) the service yet,
+		 * instead we call startService() with a specific action which shuts down
+		 * the daemon (and closes the TUN device, if any) */
+		Context context = getApplicationContext();
+		Intent intent = new Intent(context, CharonVpnService.class);
+		intent.setAction(CharonVpnService.DISCONNECT_ACTION);
+		context.startService(intent);
+	}
+
+	/**
+	 * Connect (or reconnect) a profile
+	 * @param profileInfo optional profile info (basically the UUID and password), taken from the
+	 *                    previous profile if null
+	 * @param fromScratch true if this is a manual retry/reconnect or a completely new connection
+	 */
+	public void connect(Bundle profileInfo, boolean fromScratch)
+	{
+		/* we assume we have the necessary permission */
+		Context context = getApplicationContext();
+		Intent intent = new Intent(context, CharonVpnService.class);
+		if (profileInfo == null)
+		{
+			profileInfo = new Bundle();
+			profileInfo.putString(VpnProfileDataSource.KEY_UUID, mProfile.getUUID().toString());
+			/* pass the previous password along */
+			profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, mProfile.getPassword());
+		}
+		if (fromScratch)
+		{Log.d(tag, "fromScratch");
+			/* reset if this is a manual retry or a new connection */
+			mTimeoutProvider.reset();
+		}
+		else
+		{	/* mark this as an automatic retry */
+			Log.d(tag, "from retry");
+			profileInfo.putBoolean(CharonVpnService.KEY_IS_RETRY, true);
+//			Toast.makeText(getBaseContext(), getString(R.string.server_not_responding), Toast.LENGTH_SHORT).show(); //three lines from this added be for, if server down in ikev
+			Intent in = new Intent(CharonVpnService.ACTION_VPN_SERVER_NOT_RESPONDING);
+			LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(in);
+			disconnect();
+			return;
+		}
+		intent.putExtras(profileInfo);
+		ContextCompat.startForegroundService(context, intent);
+	}
+
+	/**
+	 * Reconnect to the previous profile.
+	 */
+	public void reconnect()
+	{
+		if (mProfile == null)
+		{
+			return;
+		}
+		if (mProfile.getVpnType().has(VpnType.VpnTypeFeature.USER_PASS))
+		{
+			if (mProfile.getPassword() == null ||
+				mError == ErrorState.AUTH_FAILED)
+			{	/* show a dialog if we either don't have the password or if it might be the wrong
+				 * one (which is or isn't stored with the profile, let the activity decide)  */
+				Intent intent = new Intent(this, VpnProfileControlActivity.class);
+				intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+				intent.setAction(VpnProfileControlActivity.START_PROFILE);
+				intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, mProfile.getUUID().toString());
+				startActivity(intent);
+				/* reset the retry timer immediately in case the user needs more time to enter the password */
+				notifyListeners(() -> {
+					resetRetryTimer();
+					return true;
+				});
+				return;
+			}
+		}
+		connect(null, true);
+	}
+
+	/**
+	 * Update state and notify all listeners about the change. By using a Handler
+	 * this is done from the main UI thread and not the initial reporter thread.
+	 * Also, in doing the actual state change from the main thread, listeners
+	 * see all changes and none are skipped.
+	 *
+	 * @param change the state update to perform before notifying listeners, returns true if state changed
+	 */
+	private void notifyListeners(final Callable<Boolean> change)
+	{
+		mHandler.post(new Runnable() {
+			@Override
+			public void run()
+			{
+				try
+				{
+					if (change.call())
+					{	/* otherwise there is no need to notify the listeners */
+						for (VpnStateListener listener : mListeners)
+						{
+							listener.stateChanged();
+						}
+					}
+				}
+				catch (Exception e)
+				{
+					e.printStackTrace();
+				}
+			}
+		});
+	}
+
+	/**
+	 * Called when a connection is started.  Sets the currently active VPN
+	 * profile, resets IMC and Error state variables, sets the State to
+	 * CONNECTING, increases the connection ID, and notifies all listeners.
+	 *
+	 * May be called from threads other than the main thread.
+	 *
+	 * @param profile current profile
+	 */
+	public void startConnection(final VpnProfile profile)
+	{
+		notifyListeners(new Callable<Boolean>() {
+			@Override
+			public Boolean call() throws Exception
+			{
+				resetRetryTimer();
+				VpnStateService.this.mConnectionID++;
+				VpnStateService.this.mProfile = profile;
+				VpnStateService.this.mState = State.CONNECTING;
+				VpnStateService.this.mError = ErrorState.NO_ERROR;
+				VpnStateService.this.mImcState = ImcState.UNKNOWN;
+				VpnStateService.this.mRemediationInstructions.clear();
+				return true;
+			}
+		});
+	}
+
+	/**
+	 * Update the state and notify all listeners, if changed.
+	 *
+	 * May be called from threads other than the main thread.
+	 *
+	 * @param state new state
+	 */
+	public void setState(final State state)
+	{
+		notifyListeners(new Callable<Boolean>() {
+			@Override
+			public Boolean call() throws Exception
+			{
+				if (state == State.CONNECTED)
+				{	/* reset counter in case there is an error later on */
+					mTimeoutProvider.reset();
+				}
+				if (VpnStateService.this.mState != state)
+				{
+					VpnStateService.this.mState = state;
+					return true;
+				}
+				return false;
+			}
+		});
+	}
+
+	/**
+	 * Set the current error state and notify all listeners, if changed.
+	 *
+	 * May be called from threads other than the main thread.
+	 *
+	 * @param error error state
+	 */
+	public void setError(final ErrorState error)
+	{
+		notifyListeners(new Callable<Boolean>() {
+			@Override
+			public Boolean call() throws Exception
+			{
+				if (VpnStateService.this.mError != error)
+				{
+					if (VpnStateService.this.mError == ErrorState.NO_ERROR)
+					{
+						setRetryTimer(error);
+					}
+					else if (error == ErrorState.NO_ERROR)
+					{
+						resetRetryTimer();
+					}
+					VpnStateService.this.mError = error;
+					return true;
+				}
+				return false;
+			}
+		});
+	}
+
+	/**
+	 * Set the current IMC state and notify all listeners, if changed.
+	 *
+	 * Setting the state to UNKNOWN clears all remediation instructions.
+	 *
+	 * May be called from threads other than the main thread.
+	 *
+	 * @param state IMC state
+	 */
+	public void setImcState(final ImcState state)
+	{
+		notifyListeners(new Callable<Boolean>() {
+			@Override
+			public Boolean call() throws Exception
+			{
+				if (state == ImcState.UNKNOWN)
+				{
+					VpnStateService.this.mRemediationInstructions.clear();
+				}
+				if (VpnStateService.this.mImcState != state)
+				{
+					VpnStateService.this.mImcState = state;
+					return true;
+				}
+				return false;
+			}
+		});
+	}
+
+	/**
+	 * Add the given remediation instruction to the internal list.  Listeners
+	 * are not notified.
+	 *
+	 * Instructions are cleared if the IMC state is set to UNKNOWN.
+	 *
+	 * May be called from threads other than the main thread.
+	 *
+	 * @param instruction remediation instruction
+	 */
+	public void addRemediationInstruction(final RemediationInstruction instruction)
+	{
+		mHandler.post(new Runnable() {
+			@Override
+			public void run()
+			{
+				VpnStateService.this.mRemediationInstructions.add(instruction);
+			}
+		});
+	}
+
+	/**
+	 * Sets the retry timer
+	 */
+	private void setRetryTimer(ErrorState error)
+	{
+		mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error);
+		if (mRetryTimeout <= 0)
+		{
+			return;
+		}
+		mHandler.sendMessageAtTime(mHandler.obtainMessage(RETRY_MSG), SystemClock.uptimeMillis() + RETRY_INTERVAL);
+	}
+
+	/**
+	 * Reset the retry timer
+	 */
+	private void resetRetryTimer()
+	{
+		mRetryTimeout = 0;
+		mRetryIn = 0;
+	}
+
+	/**
+	 * Special Handler subclass that handles the retry countdown (more accurate than CountDownTimer)
+	 */
+	private static class RetryHandler extends Handler {
+		WeakReference<VpnStateService> mService;
+
+		public RetryHandler(VpnStateService service)
+		{
+			mService = new WeakReference<>(service);
+		}
+
+		@Override
+		public void handleMessage(Message msg)
+		{
+			/* handle retry countdown */
+			if (mService.get().mRetryTimeout <= 0)
+			{
+				return;
+			}
+			mService.get().mRetryIn -= RETRY_INTERVAL;
+			if (mService.get().mRetryIn > 0)
+			{
+				/* calculate next interval before notifying listeners */
+				long next = SystemClock.uptimeMillis() + RETRY_INTERVAL;
+
+				for (VpnStateListener listener : mService.get().mListeners)
+				{
+					listener.stateChanged();
+				}
+				sendMessageAtTime(obtainMessage(RETRY_MSG), next);
+			}
+			else
+			{
+				mService.get().connect(null, false);
+			}
+		}
+	}
+
+	/**
+	 * Class that handles an exponential backoff for retry timeouts
+	 */
+	private static class RetryTimeoutProvider
+	{
+		private long mRetry;
+
+		private long getBaseTimeout(ErrorState error)
+		{
+			switch (error)
+			{
+				case AUTH_FAILED:
+					return App.connectionWaitTime * 1000;
+				case PEER_AUTH_FAILED:
+				case LOOKUP_FAILED:
+				case UNREACHABLE:
+					return 5000;
+				case PASSWORD_MISSING:
+					/* this needs user intervention (entering the password) */
+					return 0;
+				case CERTIFICATE_UNAVAILABLE:
+					/* if this is because the device has to be unlocked we might be able to reconnect */
+					return 5000;
+				default:
+					return 10000;
+			}
+		}
+
+		/**
+		 * Called each time a new retry timeout is started. The timeout increases until reset() is
+		 * called and the base timeout is returned again.
+		 * @param error Error state
+		 */
+		public long getTimeout(ErrorState error)
+		{
+			long timeout = (long)(getBaseTimeout(error) * Math.pow(2, mRetry++));
+			/* return the result rounded to seconds */
+			return Math.min((timeout / 1000) * 1000, MAX_RETRY_INTERVAL);
+		}
+
+		/**
+		 * Reset the retry counter.
+		 */
+		public void reset()
+		{
+			mRetry = 0;
+		}
+	}
+}

+ 99 - 0
app/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc;
+
+import android.content.Context;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.AttributeType;
+import org.strongswan.android.logic.imc.collectors.Collector;
+import org.strongswan.android.logic.imc.collectors.DeviceIdCollector;
+import org.strongswan.android.logic.imc.collectors.InstalledPackagesCollector;
+import org.strongswan.android.logic.imc.collectors.PortFilterCollector;
+import org.strongswan.android.logic.imc.collectors.ProductInformationCollector;
+import org.strongswan.android.logic.imc.collectors.SettingsCollector;
+import org.strongswan.android.logic.imc.collectors.StringVersionCollector;
+
+public class AndroidImc
+{
+	private final Context mContext;
+
+	public AndroidImc(Context context)
+	{
+		mContext = context;
+	}
+
+	/**
+	 * Get a measurement (the binary encoding of the requested attribute) for
+	 * the given vendor specific attribute type.
+	 *
+	 * @param vendor vendor ID
+	 * @param type vendor specific attribute type
+	 * @return encoded attribute, or null if not available or failed
+	 */
+	public byte[] getMeasurement(int vendor, int type)
+	{
+		return getMeasurement(vendor, type, null);
+	}
+
+	/**
+	 * Get a measurement (the binary encoding of the requested attribute) for
+	 * the given vendor specific attribute type.
+	 *
+	 * @param vendor vendor ID
+	 * @param type vendor specific attribute type
+	 * @param args optional arguments for a measurement
+	 * @return encoded attribute, or null if not available or failed
+	 */
+	public byte[] getMeasurement(int vendor, int type, String[] args)
+	{
+		AttributeType attributeType = AttributeType.fromValues(vendor, type);
+		Collector collector = null;
+
+		switch (attributeType)
+		{
+			case IETF_PRODUCT_INFORMATION:
+				collector = new ProductInformationCollector();
+				break;
+			case IETF_STRING_VERSION:
+				collector = new StringVersionCollector();
+				break;
+			case IETF_PORT_FILTER:
+				collector = new PortFilterCollector();
+				break;
+			case IETF_INSTALLED_PACKAGES:
+				collector = new InstalledPackagesCollector(mContext);
+				break;
+			case ITA_SETTINGS:
+				collector = new SettingsCollector(mContext, args);
+				break;
+			case ITA_DEVICE_ID:
+				collector = new DeviceIdCollector(mContext);
+				break;
+			default:
+				break;
+		}
+		if (collector != null)
+		{
+			Attribute attribute = collector.getMeasurement();
+			if (attribute != null)
+			{
+				return attribute.getEncoding();
+			}
+		}
+		return null;
+	}
+}

+ 58 - 0
app/src/main/java/org/strongswan/android/logic/imc/ImcState.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc;
+
+public enum ImcState
+{
+	UNKNOWN(0),
+	ALLOW(1),
+	BLOCK(2),
+	ISOLATE(3);
+
+	private final int mValue;
+
+	private ImcState(int value)
+	{
+		mValue = value;
+	}
+
+	/**
+	 * Get the numeric value of the IMC state.
+	 * @return numeric value
+	 */
+	public int getValue()
+	{
+		return mValue;
+	}
+
+	/**
+	 * Get the enum entry from a numeric value, if defined
+	 *
+	 * @param value numeric value
+	 * @return the enum entry or null
+	 */
+	public static ImcState fromValue(int value)
+	{
+		for (ImcState state : ImcState.values())
+		{
+			if (state.mValue == value)
+			{
+				return state;
+			}
+		}
+		return null;
+	}
+}

+ 273 - 0
app/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java

@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+public class RemediationInstruction implements Parcelable
+{
+	private String mTitle;
+	private String mDescription;
+	private String mHeader;
+	private final List<String> mItems = new LinkedList<String>();
+
+	@Override
+	public int describeContents()
+	{
+		return 0;
+	}
+
+	@Override
+	public void writeToParcel(Parcel dest, int flags)
+	{
+		dest.writeString(mTitle);
+		dest.writeString(mDescription);
+		dest.writeString(mHeader);
+		dest.writeStringList(mItems);
+	}
+
+	public static final Creator<RemediationInstruction> CREATOR = new Creator<RemediationInstruction>() {
+
+		@Override
+		public RemediationInstruction[] newArray(int size)
+		{
+			return new RemediationInstruction[size];
+		}
+
+		@Override
+		public RemediationInstruction createFromParcel(Parcel source)
+		{
+			return new RemediationInstruction(source);
+		}
+	};
+
+	private RemediationInstruction()
+	{
+	}
+
+	private RemediationInstruction(Parcel source)
+	{
+		mTitle = source.readString();
+		mDescription = source.readString();
+		mHeader = source.readString();
+		source.readStringList(mItems);
+	}
+
+	public String getTitle()
+	{
+		return mTitle;
+	}
+
+	private void setTitle(String title)
+	{
+		mTitle = title;
+	}
+
+	public String getDescription()
+	{
+		return mDescription;
+	}
+
+	private void setDescription(String description)
+	{
+		mDescription = description;
+	}
+
+	public String getHeader()
+	{
+		return mHeader;
+	}
+
+	private void setHeader(String header)
+	{
+		mHeader = header;
+	}
+
+	public List<String> getItems()
+	{
+		return Collections.unmodifiableList(mItems);
+	}
+
+	private void addItem(String item)
+	{
+		mItems.add(item);
+	}
+
+	/**
+	 * Create a list of RemediationInstruction objects from the given XML data.
+	 *
+	 * @param xml XML data
+	 * @return list of RemediationInstruction objects
+	 */
+	public static List<RemediationInstruction> fromXml(String xml)
+	{
+		List<RemediationInstruction> instructions = new LinkedList<RemediationInstruction>();
+		XmlPullParser parser = Xml.newPullParser();
+		try
+		{
+			parser.setInput(new StringReader(xml));
+			parser.nextTag();
+			readInstructions(parser, instructions);
+		}
+		catch (XmlPullParserException e)
+		{
+			e.printStackTrace();
+		}
+		catch (IOException e)
+		{
+			e.printStackTrace();
+		}
+		return instructions;
+	}
+
+	/**
+	 * Read a &lt;remediationinstructions&gt; element and store the extracted
+	 * RemediationInstruction objects in the given list.
+	 *
+	 * @param parser
+	 * @param instructions
+	 * @throws XmlPullParserException
+	 * @throws IOException
+	 */
+	private static void readInstructions(XmlPullParser parser, List<RemediationInstruction> instructions) throws XmlPullParserException, IOException
+	{
+		parser.require(XmlPullParser.START_TAG, null, "remediationinstructions");
+		while (parser.next() != XmlPullParser.END_TAG)
+		{
+			if (parser.getEventType() != XmlPullParser.START_TAG)
+			{
+				continue;
+			}
+			if (parser.getName().equals("instruction"))
+			{
+				RemediationInstruction instruction = new RemediationInstruction();
+				readInstruction(parser, instruction);
+				instructions.add(instruction);
+			}
+			else
+			{
+				skipTag(parser);
+			}
+		}
+	}
+
+	/**
+	 * Read an &lt;instruction&gt; element and store the information in the
+	 * given RemediationInstruction object.
+	 *
+	 * @param parser
+	 * @param instruction
+	 * @throws XmlPullParserException
+	 * @throws IOException
+	 */
+	private static void readInstruction(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException
+	{
+		parser.require(XmlPullParser.START_TAG, null, "instruction");
+		while (parser.next() != XmlPullParser.END_TAG)
+		{
+			if (parser.getEventType() != XmlPullParser.START_TAG)
+			{
+				continue;
+			}
+			String name = parser.getName();
+			if (name.equals("title"))
+			{
+				instruction.setTitle(parser.nextText());
+			}
+			else if (name.equals("description"))
+			{
+				instruction.setDescription(parser.nextText());
+			}
+			else if (name.equals("itemsheader"))
+			{
+				instruction.setHeader(parser.nextText());
+			}
+			else if (name.equals("items"))
+			{
+				readItems(parser, instruction);
+			}
+			else
+			{
+				skipTag(parser);
+			}
+		}
+	}
+
+	/**
+	 * Read all items of an &lt;items&gt; node and add them to the given
+	 * RemediationInstruction object.
+	 *
+	 * @param parser
+	 * @param instruction
+	 * @throws XmlPullParserException
+	 * @throws IOException
+	 */
+	private static void readItems(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException
+	{
+		while (parser.next() != XmlPullParser.END_TAG)
+		{
+			if (parser.getEventType() != XmlPullParser.START_TAG)
+			{
+				continue;
+			}
+			if (parser.getName().equals("item"))
+			{
+				instruction.addItem(parser.nextText());
+			}
+			else
+			{
+				skipTag(parser);
+			}
+		}
+	}
+
+	/**
+	 * Skip the current tag and all child elements.
+	 *
+	 * @param parser
+	 * @throws XmlPullParserException
+	 * @throws IOException
+	 */
+	private static void skipTag(XmlPullParser parser) throws XmlPullParserException, IOException
+	{
+		int depth = 1;
+
+		parser.require(XmlPullParser.START_TAG, null, null);
+		while (depth != 0)
+		{
+			switch (parser.next())
+			{
+				case XmlPullParser.END_TAG:
+					depth--;
+					break;
+				case XmlPullParser.START_TAG:
+					depth++;
+					break;
+			}
+		}
+	}
+}

+ 28 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+/**
+ * Interface to be implemented by attribute classes
+ */
+public interface Attribute
+{
+	/**
+	 * Returns the binary encoding of the attribute
+	 * @return binary encoding
+	 */
+	public byte[] getEncoding();
+}

+ 100 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java

@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+public enum AttributeType
+{
+	/* IETF standard PA-TNC attribute types defined by RFC 5792 */
+	IETF_TESTING(PrivateEnterpriseNumber.IETF, 0),
+	IETF_ATTRIBUTE_REQUEST(PrivateEnterpriseNumber.IETF, 1),
+	IETF_PRODUCT_INFORMATION(PrivateEnterpriseNumber.IETF, 2),
+	IETF_NUMERIC_VERSION(PrivateEnterpriseNumber.IETF, 3),
+	IETF_STRING_VERSION(PrivateEnterpriseNumber.IETF, 4),
+	IETF_OPERATIONAL_STATUS(PrivateEnterpriseNumber.IETF, 5),
+	IETF_PORT_FILTER(PrivateEnterpriseNumber.IETF, 6),
+	IETF_INSTALLED_PACKAGES(PrivateEnterpriseNumber.IETF, 7),
+	IETF_PA_TNC_ERROR(PrivateEnterpriseNumber.IETF, 8),
+	IETF_ASSESSMENT_RESULT(PrivateEnterpriseNumber.IETF, 9),
+	IETF_REMEDIATION_INSTRUCTIONS(PrivateEnterpriseNumber.IETF, 10),
+	IETF_FORWARDING_ENABLED(PrivateEnterpriseNumber.IETF, 11),
+	IETF_FACTORY_DEFAULT_PWD_ENABLED(PrivateEnterpriseNumber.IETF, 12),
+	IETF_RESERVED(PrivateEnterpriseNumber.IETF, 0xffffffff),
+	/* ITA attributes */
+	ITA_SETTINGS(PrivateEnterpriseNumber.ITA, 4),
+	ITA_DEVICE_ID(PrivateEnterpriseNumber.ITA, 8);
+
+	private PrivateEnterpriseNumber mVendor;
+	private int mType;
+
+	/**
+	 * Enum type for vendor specific attributes (defined in their namespace)
+	 *
+	 * @param vendor private enterprise number of vendor
+	 * @param type vendor specific attribute type
+	 */
+	private AttributeType(PrivateEnterpriseNumber vendor, int type)
+	{
+		mVendor = vendor;
+		mType = type;
+	}
+
+	/**
+	 * Get private enterprise number of vendor
+	 *
+	 * @return PEN
+	 */
+	public PrivateEnterpriseNumber getVendor()
+	{
+		return mVendor;
+	}
+
+	/**
+	 * Get vendor specific type
+	 *
+	 * @return type
+	 */
+	public int getType()
+	{
+		return mType;
+	}
+
+	/**
+	 * Get the enum entry from the given numeric values, if defined
+	 *
+	 * @param vendor vendor id
+	 * @param type vendor specific type
+	 * @return enum entry or null
+	 */
+	public static AttributeType fromValues(int vendor, int type)
+	{
+		PrivateEnterpriseNumber pen = PrivateEnterpriseNumber.fromValue(vendor);
+
+		if (pen == null)
+		{
+			return null;
+		}
+		for (AttributeType attr : AttributeType.values())
+		{
+			if (attr.mVendor == pen && attr.mType == type)
+			{
+				return attr;
+			}
+		}
+		return null;
+	}
+}

+ 45 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+/**
+ * ITA Device ID attribute
+ *
+ *                       1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  | Device ID (Variable Length)                                   |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class DeviceIdAttribute implements Attribute
+{
+	private String mDeviceId;
+
+	/**
+	 * Set the device ID
+	 * @param version version number
+	 */
+	public void setDeviceId(String deviceId)
+	{
+		this.mDeviceId = deviceId;
+	}
+
+	@Override
+	public byte[] getEncoding()
+	{
+		return mDeviceId.getBytes();
+	}
+}

+ 67 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+import android.util.Pair;
+
+import org.strongswan.android.utils.BufferedByteWriter;
+
+import java.util.LinkedList;
+
+/**
+ * PA-TNC Installed Packages attribute (see section 4.2.7 of RFC 5792)
+ *
+ *                       1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |          Reserved             |         Package Count         |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  | Pkg Name Len  |        Package Name (Variable Length)         |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  Version Len  |    Package Version Number (Variable Length)   |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class InstalledPackagesAttribute implements Attribute
+{
+	private final short RESERVED = 0;
+	private final LinkedList<Pair<String, String>> mPackages = new LinkedList<Pair<String, String>>();
+
+	/**
+	 * Add an installed package to this attribute.
+	 * @param name name of the package
+	 * @param version version number of the package
+	 */
+	public void addPackage(String name, String version)
+	{
+		mPackages.add(new Pair<String, String>(name, version));
+	}
+
+	@Override
+	public byte[] getEncoding()
+	{
+		BufferedByteWriter writer = new BufferedByteWriter();
+		writer.put16(RESERVED);
+		writer.put16((short)mPackages.size());
+		for (Pair<String, String> pair : mPackages)
+		{
+			writer.putLen8(pair.first.getBytes());
+			writer.putLen8(pair.second.getBytes());
+		}
+		return writer.toByteArray();
+	}
+}

+ 65 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+import android.util.Pair;
+
+import org.strongswan.android.logic.imc.collectors.Protocol;
+import org.strongswan.android.utils.BufferedByteWriter;
+
+import java.util.LinkedList;
+
+/**
+ * PA-TNC Port Filter attribute (see section 4.2.6 of RFC 5792)
+ *
+ *                       1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |   Reserved  |B|    Protocol   |         Port Number           |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |   Reserved  |B|    Protocol   |         Port Number           |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class PortFilterAttribute implements Attribute
+{
+	private final LinkedList<Pair<Protocol, Short>> mPorts = new LinkedList<Pair<Protocol, Short>>();
+
+	/**
+	 * Add an open port with the given protocol and port number
+	 * @param protocol transport protocol
+	 * @param port port number
+	 */
+	public void addPort(Protocol protocol, short port)
+	{
+		mPorts.add(new Pair<Protocol, Short>(protocol, port));
+	}
+
+	@Override
+	public byte[] getEncoding()
+	{
+		BufferedByteWriter writer = new BufferedByteWriter();
+		for (Pair<Protocol, Short> port : mPorts)
+		{
+			/* we report open ports, so the BLOCKED flag is not set */
+			writer.put((byte)0);
+			writer.put(port.first.getValue());
+			writer.put16(port.second);
+		}
+		return writer.toByteArray();
+	}
+}

+ 65 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+public enum PrivateEnterpriseNumber
+{
+	IETF(0x000000),
+	GOOGLE(0x002B79),
+	ITA(0x00902a),
+	UNASSIGNED(0xfffffe),
+	RESERVED(0xffffff);
+
+	private int mValue;
+
+	/**
+	 * Enum for private enterprise numbers (PEN) as allocated by IANA
+	 *
+	 * @param value numeric value
+	 */
+	private PrivateEnterpriseNumber(int value)
+	{
+		mValue = value;
+	}
+
+	/**
+	 * Get the numeric value of a PEN
+	 *
+	 * @return numeric value
+	 */
+	public int getValue()
+	{
+		return mValue;
+	}
+
+	/**
+	 * Get the enum entry from a numeric value, if defined
+	 *
+	 * @param value numeric value
+	 * @return the enum entry or null
+	 */
+	public static PrivateEnterpriseNumber fromValue(int value)
+	{
+		for (PrivateEnterpriseNumber pen : PrivateEnterpriseNumber.values())
+		{
+			if (pen.mValue == value)
+			{
+				return pen;
+			}
+		}
+		return null;
+	}
+}

+ 47 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+import org.strongswan.android.utils.BufferedByteWriter;
+
+/**
+ * PA-TNC Product Information attribute (see section 4.2.2 of RFC 5792)
+ *
+ *                       1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |               Product Vendor ID               |  Product ID   |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  Product ID   |         Product Name (Variable Length)        |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class ProductInformationAttribute implements Attribute
+{
+	private final String PRODUCT_NAME = "Android";
+	private final short PRODUCT_ID = 0;
+
+	@Override
+	public byte[] getEncoding()
+	{
+		BufferedByteWriter writer = new BufferedByteWriter();
+		writer.put24(PrivateEnterpriseNumber.GOOGLE.getValue());
+		writer.put16(PRODUCT_ID);
+		writer.put(PRODUCT_NAME.getBytes());
+		return writer.toByteArray();
+	}
+}

+ 78 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+import android.util.Pair;
+
+import org.strongswan.android.utils.BufferedByteWriter;
+
+import java.util.LinkedList;
+
+/**
+ * ITA Settings attribute
+ *
+ *					   1				   2				   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                         Settings Count                        |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |        Name Length            |  Name (Variable Length)       ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  ~                      Name (Variable Length)                   ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |        Value Length           |  Value (Variable Length)      ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  ~                      Value (Variable Length)                  ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |        Name Length            |  Name (Variable Length)       ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  ~                      Name (Variable Length)                   ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |        Value Length           |  Value (Variable Length)      ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  ~                      Value (Variable Length)                  ~
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *					 ...........................
+ */
+public class SettingsAttribute implements Attribute
+{
+	private final LinkedList<Pair<String, String>> mSettings = new LinkedList<Pair<String, String>>();
+
+	/**
+	 * Add a setting to this attribute.
+	 * @param name name of the setting
+	 * @param value value of the setting
+	 */
+	public void addSetting(String name, String value)
+	{
+		mSettings.add(new Pair<String, String>(name, value));
+	}
+
+	@Override
+	public byte[] getEncoding()
+	{
+		BufferedByteWriter writer = new BufferedByteWriter();
+		writer.put32(mSettings.size());
+		for (Pair<String, String> pair : mSettings)
+		{
+			writer.putLen16(pair.first.getBytes());
+			writer.putLen16(pair.second.getBytes());
+		}
+		return writer.toByteArray();
+	}
+}

+ 68 - 0
app/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.attributes;
+
+import org.strongswan.android.utils.BufferedByteWriter;
+
+/**
+ * PA-TNC String Version attribute (see section 4.2.4 of RFC 5792)
+ *
+ *                       1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  Version Len  |   Product Version Number (Variable Length)    |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  | Build Num Len |   Internal Build Number (Variable Length)     |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  Config. Len  | Configuration Version Number (Variable Length)|
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class StringVersionAttribute implements Attribute
+{
+	private String mVersionNumber;
+	private String mBuildNumber;
+
+	/**
+	 * Set the product version number
+	 * @param version version number
+	 */
+	public void setProductVersionNumber(String version)
+	{
+		this.mVersionNumber = version;
+	}
+
+	/**
+	 * Set the internal build number
+	 * @param build build number
+	 */
+	public void setInternalBuildNumber(String build)
+	{
+		this.mBuildNumber = build;
+	}
+
+	@Override
+	public byte[] getEncoding()
+	{
+		BufferedByteWriter writer = new BufferedByteWriter();
+		writer.putLen8(mVersionNumber.getBytes());
+		writer.putLen8(mBuildNumber.getBytes());
+		/* we don't provide a configuration number */
+		writer.put((byte)0);
+		return writer.toByteArray();
+	}
+}

+ 30 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+
+/**
+ * Interface for measurement collectors
+ */
+public interface Collector
+{
+	/**
+	 * This method shall return the result of a measurement, if available
+	 * @return attribute or null
+	 */
+	public abstract Attribute getMeasurement();
+}

+ 45 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import android.content.ContentResolver;
+import android.content.Context;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.DeviceIdAttribute;
+
+public class DeviceIdCollector implements Collector
+{
+	private final ContentResolver mContentResolver;
+
+	public DeviceIdCollector(Context context)
+	{
+		mContentResolver = context.getContentResolver();
+	}
+
+	@Override
+	public Attribute getMeasurement()
+	{
+		String id = android.provider.Settings.Secure.getString(mContentResolver, "android_id");
+		if (id != null)
+		{
+			DeviceIdAttribute attribute = new DeviceIdAttribute();
+			attribute.setDeviceId(id);
+			return attribute;
+		}
+		return null;
+	}
+}

+ 55 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.InstalledPackagesAttribute;
+
+import java.util.List;
+
+public class InstalledPackagesCollector implements Collector
+{
+	private final PackageManager mPackageManager;
+
+	public InstalledPackagesCollector(Context context)
+	{
+		mPackageManager = context.getPackageManager();
+	}
+
+	@Override
+	public Attribute getMeasurement()
+	{
+		InstalledPackagesAttribute attribute = new InstalledPackagesAttribute();
+		List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
+		for (PackageInfo info : packages)
+		{
+			if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ||
+				info.packageName == null || info.versionName == null)
+			{	/* ignore packages installed in the system image */
+				continue;
+			}
+			attribute.addPackage(info.packageName, info.versionName);
+		}
+		return attribute;
+	}
+}

+ 79 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.PortFilterAttribute;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PortFilterCollector implements Collector
+{
+	private static Pattern LISTEN = Pattern.compile("\\bLISTEN\\b");
+	private static Pattern PROTOCOL = Pattern.compile("\\b(tcp|udp)6?\\b");
+	private static Pattern PORT = Pattern.compile("[:]{1,3}(\\d{1,5})\\b(?!\\.)");
+
+	@Override
+	public Attribute getMeasurement()
+	{
+		PortFilterAttribute attribute = null;
+		try
+		{
+			Process netstat = Runtime.getRuntime().exec("netstat -n");
+			try
+			{
+				BufferedReader reader = new BufferedReader(new InputStreamReader(netstat.getInputStream()));
+				String line;
+				attribute = new PortFilterAttribute();
+				while ((line = reader.readLine()) != null)
+				{
+					if (!LISTEN.matcher(line).find())
+					{
+						continue;
+					}
+					Matcher protocolMatcher = PROTOCOL.matcher(line);
+					Matcher portMatcher = PORT.matcher(line);
+					if (protocolMatcher.find() && portMatcher.find())
+					{
+						Protocol protocol = Protocol.fromName(protocolMatcher.group());
+						if (protocol == null)
+						{
+							continue;
+						}
+						int port = Integer.parseInt(portMatcher.group(1));
+						attribute.addPort(protocol, (short)port);
+					}
+				}
+			}
+			finally
+			{
+				netstat.destroy();
+			}
+		}
+		catch (IOException e)
+		{
+			e.printStackTrace();
+		}
+		return attribute;
+	}
+
+}

+ 30 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.ProductInformationAttribute;
+
+public class ProductInformationCollector implements Collector
+{
+	@Override
+	public Attribute getMeasurement()
+	{	/* this is currently hardcoded in the attribute */
+		return new ProductInformationAttribute();
+	}
+}

+ 60 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+public enum Protocol
+{
+	TCP((byte)6, "tcp", "tcp6"),
+	UDP((byte)17, "udp", "udp6");
+
+	private final byte mValue;
+	private String[] mNames;
+
+	private Protocol(byte value, String... names)
+	{
+		mValue = value;
+		mNames = names;
+	}
+
+	/**
+	 * Get the numeric value of the protocol.
+	 * @return numeric value
+	 */
+	public byte getValue()
+	{
+		return mValue;
+	}
+
+	/**
+	 * Get the protocol from the given protocol name, if found.
+	 * @param name protocol name (e.g. "udp" or "tcp")
+	 * @return enum entry or null
+	 */
+	public static Protocol fromName(String name)
+	{
+		for (Protocol protocol : Protocol.values())
+		{
+			for (String keyword : protocol.mNames)
+			{
+				if (keyword.equalsIgnoreCase(name))
+				{
+					return protocol;
+				}
+			}
+		}
+		return null;
+	}
+}

+ 61 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java

@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import android.content.ContentResolver;
+import android.content.Context;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.SettingsAttribute;
+
+import java.util.Locale;
+
+public class SettingsCollector implements Collector
+{
+	private final ContentResolver mContentResolver;
+	private final String[] mSettings;
+
+	public SettingsCollector(Context context, String[] args)
+	{
+		mContentResolver = context.getContentResolver();
+		mSettings = args;
+	}
+
+	@Override
+	public Attribute getMeasurement()
+	{
+		if (mSettings == null || mSettings.length == 0)
+		{
+			return null;
+		}
+		SettingsAttribute attribute = new SettingsAttribute();
+		for (String name : mSettings)
+		{
+			String value = android.provider.Settings.Secure.getString(mContentResolver, name.toLowerCase(Locale.US));
+			if (value == null)
+			{
+				value = android.provider.Settings.System.getString(mContentResolver, name.toLowerCase(Locale.US));
+			}
+			if (value != null)
+			{
+				attribute.addSetting(name, value);
+			}
+		}
+		return attribute;
+	}
+}

+ 33 - 0
app/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2012 Christoph Buehler
+ * Copyright (C) 2012 Patrick Loetscher
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.logic.imc.collectors;
+
+import org.strongswan.android.logic.imc.attributes.Attribute;
+import org.strongswan.android.logic.imc.attributes.StringVersionAttribute;
+
+public class StringVersionCollector implements Collector
+{
+	@Override
+	public Attribute getMeasurement()
+	{
+		StringVersionAttribute attribute = new StringVersionAttribute();
+		attribute.setProductVersionNumber(android.os.Build.VERSION.RELEASE);
+		attribute.setInternalBuildNumber(android.os.Build.DISPLAY);
+		return attribute;
+	}
+}

+ 29 - 0
app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.security;
+
+import java.security.Provider;
+
+public class LocalCertificateKeyStoreProvider extends Provider
+{
+	private static final long serialVersionUID = 3515038332469843219L;
+
+	public LocalCertificateKeyStoreProvider()
+	{
+		super("LocalCertificateKeyStoreProvider", 1.0, "KeyStore provider for local certificates");
+		put("KeyStore.LocalCertificateStore", LocalCertificateKeyStoreSpi.class.getName());
+	}
+}

+ 139 - 0
app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java

@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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.
+ */
+
+package org.strongswan.android.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+
+public class LocalCertificateKeyStoreSpi extends KeyStoreSpi
+{
+	private final LocalCertificateStore mStore = new LocalCertificateStore();
+
+	@Override
+	public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException
+	{
+		return null;
+	}
+
+	@Override
+	public Certificate[] engineGetCertificateChain(String alias)
+	{
+		return null;
+	}
+
+	@Override
+	public Certificate engineGetCertificate(String alias)
+	{
+		return mStore.getCertificate(alias);
+	}
+
+	@Override
+	public Date engineGetCreationDate(String alias)
+	{
+		return mStore.getCreationDate(alias);
+	}
+
+	@Override
+	public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException
+	{
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException
+	{
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException
+	{
+		/* we ignore the given alias as the store calculates it on its own,
+		 * duplicates are replaced */
+		if (!mStore.addCertificate(cert))
+		{
+			throw new KeyStoreException();
+		}
+	}
+
+	@Override
+	public void engineDeleteEntry(String alias) throws KeyStoreException
+	{
+		mStore.deleteCertificate(alias);
+	}
+
+	@Override
+	public Enumeration<String> engineAliases()
+	{
+		return Collections.enumeration(mStore.aliases());
+	}
+
+	@Override
+	public boolean engineContainsAlias(String alias)
+	{
+		return mStore.containsAlias(alias);
+	}
+
+	@Override
+	public int engineSize()
+	{
+		return mStore.aliases().size();
+	}
+
+	@Override
+	public boolean engineIsKeyEntry(String alias)
+	{
+		return false;
+	}
+
+	@Override
+	public boolean engineIsCertificateEntry(String alias)
+	{
+		return engineContainsAlias(alias);
+	}
+
+	@Override
+	public String engineGetCertificateAlias(Certificate cert)
+	{
+		return mStore.getCertificateAlias(cert);
+	}
+
+	@Override
+	public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException
+	{
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException
+	{
+		if (stream != null)
+		{
+			throw new UnsupportedOperationException();
+		}
+	}
+}

+ 0 - 0
app/src/main/java/org/strongswan/android/security/LocalCertificateStore.java


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません