summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/main/AndroidManifest.xml8
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/LogItem.java377
-rw-r--r--app/src/test/java/de/blinkt/openvpn/core/TestLogFileHandler.java119
3 files changed, 500 insertions, 4 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 73808ffb..7e0c9c0b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="se.leap.bitmaskclient"
- android:versionCode="127"
- android:versionName="0.9.5RC2" >
+ android:versionCode="128"
+ android:versionName="0.9.6Beta" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -28,7 +28,7 @@
<uses-sdk
android:minSdkVersion="14"
- android:targetSdkVersion="21"/>
+ android:targetSdkVersion="24"/>
<application
android:allowBackup="true"
@@ -65,7 +65,7 @@
<activity
android:name="se.leap.bitmaskclient.eip.VoidVpnLauncher"
- android:theme="@android:style/Theme.NoDisplay" />
+ android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name="de.blinkt.openvpn.activities.DisconnectVPN" />
diff --git a/app/src/main/java/de/blinkt/openvpn/core/LogItem.java b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java
new file mode 100644
index 00000000..6aefbb2e
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/LogItem.java
@@ -0,0 +1,377 @@
+/*
+ * 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 android.text.TextUtils;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+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;
+
+import se.leap.bitmaskclient.R;
+
+/**
+ * Created by arne on 24.04.16.
+ */
+public class LogItem implements Parcelable {
+ private Object[] mArgs = null;
+ private String mMessage = null;
+ private int mRessourceId;
+ // Default log priority
+ VpnStatus.LogLevel mLevel = VpnStatus.LogLevel.INFO;
+ 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;
+ }
+
+ @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 {
+ 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);
+
+ }
+
+ 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");
+ }
+
+ 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 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 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];
+ }
+ };
+
+ 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;
+ }
+
+ 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 (java.util.FormatFlagsConversionMismatchException e) {
+ if (c != null)
+ throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion());
+ else
+ throw e;
+ }
+
+ }
+
+
+ // 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();
+ }
+
+
+ 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;
+
+ if (mMessage == null && mRessourceId == 0)
+ return false;
+
+ return true;
+ }
+}
diff --git a/app/src/test/java/de/blinkt/openvpn/core/TestLogFileHandler.java b/app/src/test/java/de/blinkt/openvpn/core/TestLogFileHandler.java
new file mode 100644
index 00000000..c35df598
--- /dev/null
+++ b/app/src/test/java/de/blinkt/openvpn/core/TestLogFileHandler.java
@@ -0,0 +1,119 @@
+package de.blinkt.openvpn.core;
+
+import android.annotation.SuppressLint;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+public class TestLogFileHandler {
+
+ byte[] testUnescaped = new byte[]{0x00, 0x55, -27, 0x00, 0x56, 0x10, -128, 0x55, 0x54};
+ byte[] expectedEscaped = new byte[]{0x55, 0x00, 0x00, 0x00, 0x09, 0x00, 0x56, 0x00, -27, 0x00, 0x56, 0x01, 0x10, -128, 0x56, 0x00, 0x54};
+ private TestingLogFileHandler lfh;
+
+
+ @Before
+ public void setup() {
+ lfh = new TestingLogFileHandler();
+ }
+
+ @Test
+ public void testWriteByteArray() throws IOException {
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+ lfh.setLogFile(byteArrayOutputStream);
+
+ lfh.writeEscapedBytes(testUnescaped);
+
+ byte[] result = byteArrayOutputStream.toByteArray();
+ Assert.assertTrue(Arrays.equals(expectedEscaped, result));
+ }
+
+ @Test
+ public void readByteArray() throws IOException {
+
+ ByteArrayInputStream in = new ByteArrayInputStream(expectedEscaped);
+
+ lfh.readCacheContents(in);
+
+ Assert.assertTrue(Arrays.equals(testUnescaped, lfh.mRestoredByteArray));
+
+ }
+
+ @Test
+ public void testMarschal() throws UnsupportedEncodingException {
+ LogItem li = new LogItem(VpnStatus.LogLevel.DEBUG, 72, "foobar");
+ LogItem li2 = marschalAndBack(li);
+ testEquals(li, li2);
+ Assert.assertEquals(li, li2);
+ }
+
+ @Test
+ public void testMarschalArgs() throws UnsupportedEncodingException {
+ LogItem li = new LogItem(VpnStatus.LogLevel.DEBUG, 72, 772, "sinnloser Text", 7723, 723.2f, 7.2);
+ LogItem li2 = marschalAndBack(li);
+ testEquals(li, li2);
+ Assert.assertEquals(li, li2);
+ }
+
+ @Test
+ public void testMarschalString() throws UnsupportedEncodingException {
+ LogItem li = new LogItem(VpnStatus.LogLevel.DEBUG, "Nutzlose Nachricht");
+ LogItem li2 = marschalAndBack(li);
+ testEquals(li, li2);
+ Assert.assertEquals(li, li2);
+ }
+
+
+ private void testEquals(LogItem li, LogItem li2) {
+ Assert.assertEquals(li.getLogLevel(), li2.getLogLevel());
+ Assert.assertEquals(li.getLogtime(), li2.getLogtime());
+ Assert.assertEquals(li.getVerbosityLevel(), li2.getVerbosityLevel());
+ Assert.assertEquals(li.toString(), li2.toString());
+
+ }
+
+ private LogItem marschalAndBack(LogItem li) throws UnsupportedEncodingException {
+ byte[] bytes = li.getMarschaledBytes();
+
+ return new LogItem(bytes, bytes.length);
+ }
+
+
+ @SuppressLint("HandlerLeak")
+ static class TestingLogFileHandler extends LogFileHandler {
+
+ public byte[] mRestoredByteArray;
+
+ public TestingLogFileHandler() {
+ super(null);
+ }
+
+ public void setLogFile(OutputStream out) {
+ mLogFile = out;
+ }
+
+ @Override
+ public void readCacheContents(InputStream in) throws IOException {
+ super.readCacheContents(in);
+ }
+
+ @Override
+ protected void restoreLogItem(byte[] buf, int len) {
+ mRestoredByteArray = Arrays.copyOf(buf, len);
+ }
+ }
+
+
+} \ No newline at end of file