Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d984fd62 authored by Liz Prucka's avatar Liz Prucka Committed by Android (Google) Code Review
Browse files

Merge "[Forensic] Refactor ForensicEvent" into main

parents a1c40525 20042247
Loading
Loading
Loading
Loading
+104 −19
Original line number Diff line number Diff line
@@ -17,13 +17,17 @@
package android.security.forensic;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
import android.app.admin.SecurityLog.SecurityEvent;
import android.os.Parcel;
import android.os.Parcelable;
import android.security.Flags;
import android.util.ArrayMap;

import java.util.Map;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A class that represents a forensic event.
@@ -33,11 +37,24 @@ import java.util.Map;
public final class ForensicEvent implements Parcelable {
    private static final String TAG = "ForensicEvent";

    @NonNull
    private final String mType;
    public static final int SECURITY_EVENT = 0;
    public static final int NETWORK_EVENT_DNS = 1;
    public static final int NETWORK_EVENT_CONNECT = 2;

    @NonNull
    private final Map<String, String> mKeyValuePairs;
    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        ForensicEvent.SECURITY_EVENT,
        ForensicEvent.NETWORK_EVENT_DNS,
        ForensicEvent.NETWORK_EVENT_CONNECT,
    })
    public @interface EventType {}

    @NonNull @EventType private final int mType;

    private final SecurityEvent mSecurityEvent;
    private final DnsEvent mNetworkEventDns;
    private final ConnectEvent mNetworkEventConnect;

    public static final @NonNull Parcelable.Creator<ForensicEvent> CREATOR =
            new Parcelable.Creator<>() {
@@ -50,30 +67,99 @@ public final class ForensicEvent implements Parcelable {
                }
            };

    public ForensicEvent(@NonNull String type, @NonNull Map<String, String> keyValuePairs) {
        mType = type;
        mKeyValuePairs = keyValuePairs;
    public ForensicEvent(@NonNull SecurityEvent securityEvent) {
        mType = SECURITY_EVENT;
        mSecurityEvent = securityEvent;
        mNetworkEventDns = null;
        mNetworkEventConnect = null;
    }

    public ForensicEvent(@NonNull DnsEvent dnsEvent) {
        mType = NETWORK_EVENT_DNS;
        mNetworkEventDns = dnsEvent;
        mSecurityEvent = null;
        mNetworkEventConnect = null;
    }

    public ForensicEvent(@NonNull ConnectEvent connectEvent) {
        mType = NETWORK_EVENT_CONNECT;
        mNetworkEventConnect = connectEvent;
        mSecurityEvent = null;
        mNetworkEventDns = null;
    }

    private ForensicEvent(@NonNull Parcel in) {
        mType = in.readString();
        mKeyValuePairs = new ArrayMap<>(in.readInt());
        in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
        mType = in.readInt();
        switch (mType) {
            case SECURITY_EVENT:
                mSecurityEvent = SecurityEvent.CREATOR.createFromParcel(in);
                mNetworkEventDns = null;
                mNetworkEventConnect = null;
                break;
            case NETWORK_EVENT_DNS:
                mNetworkEventDns = DnsEvent.CREATOR.createFromParcel(in);
                mSecurityEvent = null;
                mNetworkEventConnect = null;
                break;
            case NETWORK_EVENT_CONNECT:
                mNetworkEventConnect = ConnectEvent.CREATOR.createFromParcel(in);
                mSecurityEvent = null;
                mNetworkEventDns = null;
                break;
            default:
                throw new IllegalArgumentException("Invalid event type: " + mType);
        }
    }

    public String getType() {
    /** Returns the type of the forensic event. */
    @NonNull
    public @EventType int getType() {
        return mType;
    }

    public Map<String, String> getKeyValuePairs() {
        return mKeyValuePairs;
    /** Returns the SecurityEvent object. */
    @NonNull
    public SecurityEvent getSecurityEvent() {
        if (mType == SECURITY_EVENT) {
            return mSecurityEvent;
        }
        throw new IllegalArgumentException("Event type is not security event: " + mType);
    }

    /** Returns the DnsEvent object. */
    @NonNull
    public DnsEvent getDnsEvent() {
        if (mType == NETWORK_EVENT_DNS) {
            return mNetworkEventDns;
        }
        throw new IllegalArgumentException("Event type is not network DNS event: " + mType);
    }

    /** Returns the ConnectEvent object. */
    @NonNull
    public ConnectEvent getConnectEvent() {
        if (mType == NETWORK_EVENT_CONNECT) {
            return mNetworkEventConnect;
        }
        throw new IllegalArgumentException("Event type is not network connect event: " + mType);
    }

    @Override
    public void writeToParcel(@NonNull Parcel out, int flags) {
        out.writeString(mType);
        out.writeInt(mKeyValuePairs.size());
        out.writeMap(mKeyValuePairs);
        out.writeInt(mType);
        switch (mType) {
            case SECURITY_EVENT:
                out.writeParcelable(mSecurityEvent, flags);
                break;
            case NETWORK_EVENT_DNS:
                out.writeParcelable(mNetworkEventDns, flags);
                break;
            case NETWORK_EVENT_CONNECT:
                out.writeParcelable(mNetworkEventConnect, flags);
                break;
            default:
                throw new IllegalArgumentException("Invalid event type: " + mType);
        }
    }

    @FlaggedApi(Flags.FLAG_AFL_API)
@@ -86,7 +172,6 @@ public final class ForensicEvent implements Parcelable {
    public String toString() {
        return "ForensicEvent{"
                + "mType=" + mType
                + ", mKeyValuePairs=" + mKeyValuePairs
                + '}';
    }
}
+1 −44
Original line number Diff line number Diff line
@@ -22,9 +22,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
import android.security.forensic.ForensicEvent;
import android.util.ArrayMap;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -34,10 +32,6 @@ import java.util.stream.Collectors;
public class SecurityLogSource implements DataSource {

    private static final String TAG = "Forensic SecurityLogSource";
    private static final String EVENT_TYPE = "SecurityEvent";
    private static final String EVENT_TAG = "TAG";
    private static final String EVENT_TIME = "TIME";
    private static final String EVENT_DATA = "DATA";

    private SecurityEventCallback mEventCallback = new SecurityEventCallback();
    private DevicePolicyManager mDpm;
@@ -94,46 +88,9 @@ public class SecurityLogSource implements DataSource {
            List<ForensicEvent> forensicEvents =
                    events.stream()
                            .filter(event -> event != null)
                            .map(event -> toForensicEvent(event))
                            .map(event -> new ForensicEvent(event))
                            .collect(Collectors.toList());
            mDataAggregator.addBatchData(forensicEvents);
        }

        private ForensicEvent toForensicEvent(SecurityEvent event) {
            ArrayMap<String, String> keyValuePairs = new ArrayMap<>();
            keyValuePairs.put(EVENT_TIME, String.valueOf(event.getTimeNanos()));
            // TODO: Map tag to corresponding string
            keyValuePairs.put(EVENT_TAG, String.valueOf(event.getTag()));
            keyValuePairs.put(EVENT_DATA, eventDataToString(event.getData()));
            return new ForensicEvent(EVENT_TYPE, keyValuePairs);
        }

        /**
         * Convert event data to a String.
         *
         * @param obj Object containing an Integer, Long, Float, String, null, or Object[] of the
         *     same.
         * @return String representation of event data.
         */
        private String eventDataToString(Object obj) {
            if (obj == null) {
                return "";
            } else if (obj instanceof Integer
                    || obj instanceof Long
                    || obj instanceof Float
                    || obj instanceof String) {
                return String.valueOf(obj);
            } else if (obj instanceof Object[]) {
                Object[] objArray = (Object[]) obj;
                String[] strArray = new String[objArray.length];
                for (int i = 0; i < objArray.length; ++i) {
                    strArray[i] = eventDataToString(objArray[i]);
                }
                return Arrays.toString((String[]) strArray);
            } else {
                throw new IllegalArgumentException(
                        "Unsupported data type: " + obj.getClass().getSimpleName());
            }
        }
    }
}
+18 −23
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.annotation.SuppressLint;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
import android.os.Looper;
import android.os.PermissionEnforcer;
@@ -40,7 +43,6 @@ import android.os.test.TestLooper;
import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;
import android.util.ArrayMap;

import androidx.test.core.app.ApplicationProvider;

@@ -53,7 +55,6 @@ import org.mockito.ArgumentCaptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ForensicServiceTest {
    private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
@@ -300,23 +301,19 @@ public class ForensicServiceTest {
        ServiceThread mockThread = spy(ServiceThread.class);
        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);

        String eventOneType = "event_one_type";
        String eventOneMapKey = "event_one_map_key";
        String eventOneMapVal = "event_one_map_val";
        Map<String, String> eventOneMap = new ArrayMap<String, String>();
        eventOneMap.put(eventOneMapKey, eventOneMapVal);
        ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap);
        SecurityEvent securityEvent = new SecurityEvent(0, new byte[0]);
        ForensicEvent eventOne = new ForensicEvent(securityEvent);

        String eventTwoType = "event_two_type";
        String eventTwoMapKey = "event_two_map_key";
        String eventTwoMapVal = "event_two_map_val";
        Map<String, String> eventTwoMap = new ArrayMap<String, String>();
        eventTwoMap.put(eventTwoMapKey, eventTwoMapVal);
        ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap);
        ConnectEvent connectEvent = new ConnectEvent("127.0.0.1", 80, null, 0);
        ForensicEvent eventTwo = new ForensicEvent(connectEvent);

        DnsEvent dnsEvent = new DnsEvent(null, new String[] {"127.0.0.1"}, 1, null, 0);
        ForensicEvent eventThree = new ForensicEvent(dnsEvent);

        List<ForensicEvent> events = new ArrayList<>();
        events.add(eventOne);
        events.add(eventTwo);
        events.add(eventThree);

        doReturn(true).when(mForensicEventTransportConnection).addData(any());

@@ -327,18 +324,16 @@ public class ForensicServiceTest {
        ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class);
        verify(mForensicEventTransportConnection).addData(captor.capture());
        List<ForensicEvent> receivedEvents = captor.getValue();
        assertEquals(receivedEvents.size(), 2);
        assertEquals(receivedEvents.size(), 3);

        assertEquals(receivedEvents.getFirst().getType(), eventOneType);
        assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1);
        assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey),
                eventOneMapVal);
        assertEquals(receivedEvents.get(0).getType(), ForensicEvent.SECURITY_EVENT);
        assertNotNull(receivedEvents.get(0).getSecurityEvent());

        assertEquals(receivedEvents.getLast().getType(), eventTwoType);
        assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1);
        assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey),
                eventTwoMapVal);
        assertEquals(receivedEvents.get(1).getType(), ForensicEvent.NETWORK_EVENT_CONNECT);
        assertNotNull(receivedEvents.get(1).getConnectEvent());

        assertEquals(receivedEvents.get(2).getType(), ForensicEvent.NETWORK_EVENT_DNS);
        assertNotNull(receivedEvents.get(2).getDnsEvent());
    }

    private class MockInjector implements ForensicService.Injector {