diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java index 3a728c7e..a65a853a 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -196,7 +196,7 @@ public class CaptureService extends VpnService implements Runnable { last_bytes = 0; last_connections = 0; root_capture = Prefs.isRootCaptureEnabled(prefs); - conn_reg = new ConnectionsRegister(CONNECTIONS_LOG_SIZE, prefs); + conn_reg = new ConnectionsRegister(CONNECTIONS_LOG_SIZE, this, prefs); if(dump_mode != Prefs.DumpMode.HTTP_SERVER) mHttpServer = null; diff --git a/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java b/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java index a8754c5a..f4f45850 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java +++ b/app/src/main/java/com/emanuelef/remote_capture/ConnectionsRegister.java @@ -19,6 +19,7 @@ package com.emanuelef.remote_capture; +import android.content.Context; import android.content.SharedPreferences; import android.util.Log; @@ -52,7 +53,7 @@ public class ConnectionsRegister { public final ConnectionsMatcher mExclusions; public boolean mExclusionsEnabled; - public ConnectionsRegister(int _size, SharedPreferences prefs) { + public ConnectionsRegister(int _size, Context context, SharedPreferences prefs) { mTail = 0; mNumItems = 0; mUntrackedItems = 0; @@ -62,7 +63,7 @@ public class ConnectionsRegister { mAppsStats = new HashMap<>(); // uid -> AppStats mExclusionsEnabled = true; mPrefs = prefs; - mExclusions = new ConnectionsMatcher(); + mExclusions = new ConnectionsMatcher(context); // Try to restore the exclusions String serialized = prefs.getString(Prefs.PREF_EXCLUSIONS, ""); diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java index 1843339c..a4d4ac82 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java @@ -25,13 +25,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.text.style.StyleSpan; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -70,6 +68,7 @@ import com.emanuelef.remote_capture.model.ConnectionDescriptor; import com.emanuelef.remote_capture.activities.ConnectionDetailsActivity; import com.emanuelef.remote_capture.adapters.ConnectionsAdapter; import com.emanuelef.remote_capture.model.ConnectionsMatcher; +import com.emanuelef.remote_capture.model.ConnectionsMatcher.ItemType; import com.emanuelef.remote_capture.views.EmptyRecyclerView; import com.emanuelef.remote_capture.interfaces.ConnectionsListener; import com.google.android.material.floatingactionbutton.FloatingActionButton; @@ -302,18 +301,17 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener return; AppDescriptor app = mApps.get(conn.uid); - StyleSpan italic = new StyleSpan(Typeface.ITALIC); Context ctx = requireContext(); if(app != null) { MenuItem item = menu.findItem(R.id.exclude_app); - item.setTitle(Utils.formatTextValue(ctx, null, italic, R.string.app_val, app.getName())); + item.setTitle(ConnectionsMatcher.getLabel(ctx, ItemType.APP, app.getName())); item.setVisible(true); } if((conn.info != null) && (!conn.info.isEmpty())) { MenuItem item = menu.findItem(R.id.exclude_host); - item.setTitle(Utils.formatTextValue(ctx, null, italic, R.string.host_val, conn.info)); + item.setTitle(ConnectionsMatcher.getLabel(ctx, ItemType.HOST, conn.info)); item.setVisible(true); String rootDomain = Utils.getRootDomain(conn.info); @@ -321,13 +319,13 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener if(!rootDomain.equals(conn.info)) { String val = "*" + rootDomain; item = menu.findItem(R.id.exclude_root_domain); - item.setTitle(Utils.formatTextValue(ctx, null, italic, R.string.host_val, val)); + item.setTitle(ConnectionsMatcher.getLabel(ctx, ItemType.ROOT_DOMAIN, val)); item.setVisible(true); } } - menu.findItem(R.id.exclude_ip).setTitle(Utils.formatTextValue(ctx, null, italic, R.string.ip_address_val, conn.dst_ip)); - menu.findItem(R.id.exclude_proto).setTitle(Utils.formatTextValue(ctx, null, italic, R.string.protocol_val, conn.l7proto)); + menu.findItem(R.id.exclude_ip).setTitle(ConnectionsMatcher.getLabel(ctx, ItemType.IP, conn.dst_ip)); + menu.findItem(R.id.exclude_proto).setTitle(ConnectionsMatcher.getLabel(ctx, ItemType.PROTOCOL, conn.l7proto)); } @Override @@ -685,8 +683,6 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener ListView exclusion = ((ListView)exclListView.findViewById(R.id.list)); exclusion.setAdapter(adapter); exclusion.setOnItemClickListener((parent, view, position, id) -> { - Log.d(TAG, "TODO:2 on click view " + position); - if(adapter.getCount() > 1) adapter.remove(adapter.getItem(position)); }); @@ -704,6 +700,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener private void updateExclusions(ExclusionsEditAdapter adapter) { ConnectionsRegister reg = CaptureService.getConnsRegister(); + ArrayList toRemove = new ArrayList<>(); if(reg == null) return; @@ -715,14 +712,14 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener while(iter.hasNext()) { ConnectionsMatcher.Item item = iter.next(); - if(adapter.getPosition(item) < 0) { - iter.remove(); - changed = true; - } + if(adapter.getPosition(item) < 0) + toRemove.add(item); } - if(changed) + if(toRemove.size() > 0) { + reg.mExclusions.removeItems(toRemove); refreshFilteredConnections(); + } } private void deleteExclusions() { diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/ConnectionsMatcher.java b/app/src/main/java/com/emanuelef/remote_capture/model/ConnectionsMatcher.java index ade50bf6..14885994 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/ConnectionsMatcher.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/ConnectionsMatcher.java @@ -19,17 +19,20 @@ package com.emanuelef.remote_capture.model; +import android.content.Context; +import android.graphics.Typeface; +import android.text.style.StyleSpan; import android.util.Log; +import androidx.annotation.Nullable; + +import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.Utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; @@ -37,60 +40,76 @@ import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; +import java.util.List; public class ConnectionsMatcher { private static final String TAG = "ConnectionsMatcher"; + private static final StyleSpan italic = new StyleSpan(Typeface.ITALIC); + private final Context mContext; private ArrayList mItems = new ArrayList<>(); + private final HashMap mMatches = new HashMap<>(); - public static abstract class Item { - protected String mLabel; + public enum ItemType { + APP, + IP, + HOST, + ROOT_DOMAIN, + PROTOCOL + } - Item(String label) { + public static class Item { + private final String mLabel; + private final ItemType mType; + private final Object mValue; + + Item(ItemType tp, Object value, String label) { mLabel = label; + mType = tp; + mValue = value; } public String getLabel() { return mLabel; } - abstract public String getValue(); - abstract boolean matches(ConnectionDescriptor conn); + public ItemType getType() { + return mType; + } + + public Object getValue() { + return mValue; + } + + @Override + public boolean equals(@Nullable Object obj) { + if(!(obj instanceof Item)) + return super.equals(obj); + + Item other = (Item) obj; + return((mType == other.mType) && (mValue.equals(other.mValue))); + } } - private static class AppItem extends Item { - int mUid; - AppItem(int uid, String label) { super(label); mUid = uid; } - public boolean matches(ConnectionDescriptor conn) { return conn.uid == mUid; } - public String getValue() { return Integer.toString(mUid); } + public ConnectionsMatcher(Context ctx) { + mContext = ctx; } - private static class IpItem extends Item { - String mIp; - IpItem(String ip, String label) { super(label); mIp = ip; } - public boolean matches(ConnectionDescriptor conn) { return conn.dst_ip.equals(mIp); } - public String getValue() { return mIp; } - } + public static String getLabel(Context ctx, ItemType tp, String value) { + int resid; - private static class HostItem extends Item { - String mInfo; - HostItem(String info, String label) { super(label); mInfo = info; } - public boolean matches(ConnectionDescriptor conn) { return conn.info.equals(mInfo); } - public String getValue() { return mInfo; } - } + switch(tp) { + case APP: resid = R.string.app_val; break; + case IP: resid = R.string.ip_address_val; break; + case HOST: + case ROOT_DOMAIN: resid = R.string.host_val; break; + case PROTOCOL: resid = R.string.protocol_val; break; + default: + return ""; + } - private static class RootDomainItem extends Item { - String mRootDomain; - RootDomainItem(String domain, String label) { super(label); mRootDomain = domain; } - public boolean matches(ConnectionDescriptor conn) { return Utils.getRootDomain(conn.info).equals(mRootDomain); } - public String getValue() { return mRootDomain; } - } - - private static class ProtoItem extends Item { - String mProto; - ProtoItem(String info, String label) { super(label); mProto = info; } - public boolean matches(ConnectionDescriptor conn) { return conn.l7proto.equals(mProto); } - public String getValue() { return mProto; } + return Utils.formatTextValue(ctx, null, italic, resid, value).toString(); } private static class Serializer implements JsonSerializer { @@ -102,9 +121,9 @@ public class ConnectionsMatcher { for(Item item : src.mItems) { JsonObject itemObject = new JsonObject(); - itemObject.add("type", new JsonPrimitive(item.getClass().getSimpleName())); + itemObject.add("type", new JsonPrimitive(item.getType().name())); itemObject.add("label", new JsonPrimitive(item.getLabel())); - itemObject.add("value", new JsonPrimitive(item.getValue())); + itemObject.add("value", new JsonPrimitive(item.getValue().toString())); itemsArr.add(itemObject); } @@ -118,62 +137,61 @@ public class ConnectionsMatcher { mItems = new ArrayList<>(); JsonArray itemArray = object.getAsJsonArray("items"); - String appItemClass = AppItem.class.getSimpleName(); - String ipItemClass = IpItem.class.getSimpleName(); - String hostItemClass = HostItem.class.getSimpleName(); - String protoItemClass = ProtoItem.class.getSimpleName(); - String rootDomainItemClass = RootDomainItem.class.getSimpleName(); - for(JsonElement el: itemArray) { JsonObject itemObj = el.getAsJsonObject(); + ItemType type; - String type = itemObj.get("type").getAsString(); - String label = itemObj.get("label").getAsString(); - JsonElement val = itemObj.get("value"); + try { + type = ItemType.valueOf(itemObj.get("type").getAsString()); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + continue; + } - if(type.equals(appItemClass)) - addApp(val.getAsInt(), label); - else if(type.equals(ipItemClass)) - addIp(val.getAsString(), label); - else if(type.equals(hostItemClass)) - addHost(val.getAsString(), label); - else if(type.equals(protoItemClass)) - addProto(val.getAsString(), label); - else if(type.equals(rootDomainItemClass)) - addRootDomain(val.getAsString(), label); - else - Log.w(TAG, "unknown item type: " + type); + String val = itemObj.get("value").getAsString(); + String label = getLabel(mContext, type, val); + + addItem(new Item(type, val, label)); } } - public void addApp(int uid, String label) { addItem(new AppItem(uid, label)); } - public void addIp(String ip, String label) { addItem(new IpItem(ip, label)); } - public void addHost(String info, String label) { addItem(new HostItem(info, label)); } - public void addProto(String proto, String label) { addItem(new ProtoItem(proto, label)); } - public void addRootDomain(String domain, String label) { addItem(new RootDomainItem(domain, label)); } + public void addApp(int uid, String label) { addItem(new Item(ItemType.APP, uid, label)); } + public void addIp(String ip, String label) { addItem(new Item(ItemType.IP, ip, label)); } + public void addHost(String info, String label) { addItem(new Item(ItemType.HOST, info, label)); } + public void addProto(String proto, String label) { addItem(new Item(ItemType.PROTOCOL, proto, label)); } + public void addRootDomain(String domain, String label) { addItem(new Item(ItemType.ROOT_DOMAIN, domain, label)); } + + static private String matchKey(ItemType tp, Object val) { + return tp + "@" + val; + } private void addItem(Item item) { - // Avoid duplicates - if(!hasItem(item)) + String key = matchKey(item.getType(), item.getValue().toString()); + Log.d(TAG, key); + + if(!mMatches.containsKey(key)) { mItems.add(item); + mMatches.put(key, item); + } } - private boolean hasItem(Item search) { - for(Item item : mItems) { - if(item.mLabel.equals(search.mLabel)) - return true; - } + public void removeItems(List items) { + mItems.removeAll(items); - return false; + for(Item item: items) { + String key = matchKey(item.getType(), item.getValue().toString()); + mMatches.remove(key); + } } public boolean matches(ConnectionDescriptor conn) { - for(Item item : mItems) { - if(item.matches(conn)) - return true; - } + boolean hasInfo = ((conn.info != null) && (!conn.info.isEmpty())); - return false; + return(mMatches.containsKey(matchKey(ItemType.APP, conn.uid)) || + mMatches.containsKey(matchKey(ItemType.IP, conn.dst_ip)) || + mMatches.containsKey(matchKey(ItemType.PROTOCOL, conn.l7proto)) || + (hasInfo && mMatches.containsKey(matchKey(ItemType.HOST, conn.info))) || + (hasInfo && mMatches.containsKey(matchKey(ItemType.ROOT_DOMAIN, Utils.getRootDomain(conn.info))))); } public Iterator iterItems() { @@ -182,6 +200,7 @@ public class ConnectionsMatcher { public void clear() { mItems.clear(); + mMatches.clear(); } public boolean isEmpty() {