فهرست منبع

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


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است