spade.reporter.Audit.java Source code

Java tutorial

Introduction

Here is the source code for spade.reporter.Audit.java

Source

/*
 --------------------------------------------------------------------------------
 SPADE - Support for Provenance Auditing in Distributed Environments.
 Copyright (C) 2015 SRI International
    
 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 3 of the
 License, or (at your option) any later version.
    
 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.
    
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
 --------------------------------------------------------------------------------
 */
package spade.reporter;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;

import spade.core.AbstractEdge;
import spade.core.AbstractReporter;
import spade.core.AbstractVertex;
import spade.core.Settings;
import spade.edge.opm.Used;
import spade.edge.opm.WasDerivedFrom;
import spade.edge.opm.WasGeneratedBy;
import spade.reporter.audit.AuditEventReader;
import spade.reporter.audit.Globals;
import spade.reporter.audit.MalformedAuditDataException;
import spade.reporter.audit.OPMConstants;
import spade.reporter.audit.SYSCALL;
import spade.reporter.audit.artifact.ArtifactIdentifier;
import spade.reporter.audit.artifact.ArtifactManager;
import spade.reporter.audit.artifact.BlockDeviceIdentifier;
import spade.reporter.audit.artifact.CharacterDeviceIdentifier;
import spade.reporter.audit.artifact.DirectoryIdentifier;
import spade.reporter.audit.artifact.FileIdentifier;
import spade.reporter.audit.artifact.LinkIdentifier;
import spade.reporter.audit.artifact.MemoryIdentifier;
import spade.reporter.audit.artifact.NamedPipeIdentifier;
import spade.reporter.audit.artifact.NetworkSocketIdentifier;
import spade.reporter.audit.artifact.PathIdentifier;
import spade.reporter.audit.artifact.UnixSocketIdentifier;
import spade.reporter.audit.artifact.UnknownIdentifier;
import spade.reporter.audit.artifact.UnnamedNetworkSocketPairIdentifier;
import spade.reporter.audit.artifact.UnnamedPipeIdentifier;
import spade.reporter.audit.artifact.UnnamedUnixSocketPairIdentifier;
import spade.reporter.audit.process.ProcessManager;
import spade.reporter.audit.process.ProcessWithAgentManager;
import spade.reporter.audit.process.ProcessWithoutAgentManager;
import spade.utility.CommonFunctions;
import spade.utility.Execute;
import spade.utility.FileUtility;
import spade.vertex.opm.Artifact;
import spade.vertex.opm.Process;

/**
 * @author Dawood Tariq, Sharjeel Ahmed Qureshi
 */
public class Audit extends AbstractReporter {

    static final Logger logger = Logger.getLogger(Audit.class.getName());

    /********************** LINUX CONSTANTS - START *************************/

    //  Following constant values are taken from:
    //  http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L14
    private final int S_IFIFO = 0010000, S_IFREG = 0100000, S_IFSOCK = 0140000, S_IFLNK = 0120000,
            S_IFBLK = 0060000, S_IFDIR = 0040000, S_IFCHR = 0020000, S_IFMT = 00170000;

    //  Following constant values are taken from:
    //  http://lxr.free-electrons.com/source/include/uapi/linux/fcntl.h#L56
    private final int AT_FDCWD = -100;

    //  Following constant values are taken from:
    //  http://lxr.free-electrons.com/source/include/uapi/asm-generic/fcntl.h#L19
    private final int O_RDONLY = 00000000, O_WRONLY = 00000001, O_RDWR = 00000002, O_CREAT = 00000100,
            O_TRUNC = 00001000, O_APPEND = 00002000;

    //  Following constant values are taken from:
    //  http://lxr.free-electrons.com/source/include/uapi/asm-generic/mman-common.h#L21
    private final int MAP_ANONYMOUS = 0x20;

    //  Following constant values are taken from:
    //  http://lxr.free-electrons.com/source/include/uapi/asm-generic/fcntl.h#L99
    private final int F_LINUX_SPECIFIC_BASE = 1024, F_DUPFD = 0, F_SETFL = 4;
    //  Following constant values are taken from:
    //  http://lxr.free-electrons.com/source/include/uapi/linux/fcntl.h#L16
    private final int F_DUPFD_CLOEXEC = F_LINUX_SPECIFIC_BASE + 6;
    // Source of following: http://elixir.free-electrons.com/linux/latest/source/include/uapi/asm-generic/errno.h#L97
    private final int EINPROGRESS = -115;
    // Source: http://elixir.free-electrons.com/linux/latest/source/include/linux/net.h#L65
    private final int SOCK_STREAM = 1, SOCK_DGRAM = 2, SOCK_SEQPACKET = 5;
    // Source: https://elixir.bootlin.com/linux/latest/source/include/linux/socket.h#L162
    private final int AF_UNIX = 1, AF_LOCAL = 1, AF_INET = 2, AF_INET6 = 10;
    private final int PF_UNIX = AF_UNIX, PF_LOCAL = AF_LOCAL, PF_INET = AF_INET, PF_INET6 = AF_INET6;
    /********************** LINUX CONSTANTS - END *************************/

    /********************** PROCESS STATE - START *************************/

    private ProcessManager processManager;

    /********************** PROCESS STATE - END *************************/

    /********************** ARITFACT STATE - START *************************/

    private ArtifactManager artifactManager;

    /********************** ARTIFACT STATE - END *************************/

    /********************** NETFILTER - START *************************/

    private String[] iptablesRules = null;

    private int matchedNetfilterSyscall = 0, matchedSyscallNetfilter = 0;

    private List<Map<String, String>> networkAnnotationsFromSyscalls = new ArrayList<Map<String, String>>();
    private List<Map<String, String>> networkAnnotationsFromNetfilter = new ArrayList<Map<String, String>>();

    private Map<String, String> getNetworkAnnotationsSeenInList(List<Map<String, String>> list,
            String remoteAddress, String remotePort) {
        for (int a = 0; a < list.size(); a++) {
            Map<String, String> artifactAnnotation = list.get(a);
            if (String.valueOf(artifactAnnotation.get(OPMConstants.ARTIFACT_REMOTE_ADDRESS)).equals(remoteAddress)
                    && String.valueOf(artifactAnnotation.get(OPMConstants.ARTIFACT_REMOTE_PORT))
                            .equals(remotePort)) {
                return artifactAnnotation;
            }
        }
        return null;
    }

    /********************** NETFILTER - END *************************/

    /********************** BEHAVIOR FLAGS - START *************************/

    private Globals globals = null;
    //Reporting variables
    private boolean reportingEnabled = false;
    private long reportEveryMs;
    private long lastReportedTime;

    // These are the default values
    private boolean FAIL_FAST = true;
    private boolean USE_READ_WRITE = false;
    private boolean USE_SOCK_SEND_RCV = false;
    private boolean CREATE_BEEP_UNITS = false;
    private boolean SIMPLIFY = true;
    private boolean PROCFS = false;
    private boolean WAIT_FOR_LOG_END = true;
    private boolean AGENTS = false;
    private boolean CONTROL = true;
    private boolean USE_MEMORY_SYSCALLS = true;
    private String AUDITCTL_SYSCALL_SUCCESS_FLAG = "1";
    private boolean ANONYMOUS_MMAP = true;
    private boolean NETFILTER_RULES = false;
    private boolean REFINE_NET = false;
    private String ADD_KM_KEY = "localEndpoints";
    private boolean ADD_KM; // Default value set where flags are being initialized from arguments (unlike the variables above).
    private String HANDLE_KM_RECORDS_KEY = "handleLocalEndpoints";
    // Handle the flag below with care!
    private Boolean HANDLE_KM_RECORDS = null; // Default value set where flags are being initialized from arguments (unlike the variables above).
    private Integer mergeUnit = null;
    /********************** BEHAVIOR FLAGS - END *************************/

    private Set<String> namesOfProcessesToIgnoreFromConfig = new HashSet<String>();

    private String spadeAuditBridgeProcessPid = null;
    // true if live audit, false if log file. null not set.
    private Boolean isLiveAudit = null;
    // a flag to block on shutdown call if buffers are being emptied and events are still being read
    private volatile boolean eventReaderThreadRunning = false;

    private final long PID_MSG_WAIT_TIMEOUT = 1 * 1000;

    private final String AUDIT_SYSCALL_SOURCE = OPMConstants.SOURCE_AUDIT_SYSCALL;

    private final String kernelModulePath = "lib/kernel-modules/netio.ko";
    private final String kernelModuleControllerPath = "lib/kernel-modules/netio_controller.ko";

    private static final String PROTOCOL_NAME_UDP = "udp", PROTOCOL_NAME_TCP = "tcp";
    private final String IPV4_NETWORK_SOCKET_SADDR_PREFIX = "02";
    private final String IPV6_NETWORK_SOCKET_SADDR_PREFIX = "0A";
    private final String UNIX_SOCKET_SADDR_PREFIX = "01";
    private final String NETLINK_SOCKET_SADDR_PREFIX = "10";

    private boolean isNetlinkSaddr(String saddr) {
        return saddr != null && saddr.startsWith(NETLINK_SOCKET_SADDR_PREFIX);
    }

    private boolean isUnixSaddr(String saddr) {
        return saddr != null && saddr.startsWith(UNIX_SOCKET_SADDR_PREFIX);
    }

    private boolean isNetworkSaddr(String saddr) {
        return saddr != null && (isIPv4Saddr(saddr) || isIPv6Saddr(saddr));
    }

    private boolean isIPv4Saddr(String saddr) {
        return saddr != null && saddr.startsWith(IPV4_NETWORK_SOCKET_SADDR_PREFIX);
    }

    private boolean isIPv6Saddr(String saddr) {
        return saddr != null && saddr.startsWith(IPV6_NETWORK_SOCKET_SADDR_PREFIX);
    }

    /**
     * Returns a map which contains all the keys and values defined 
     * in the default config file. 
     * 
     * Returns empty map if failed to read the config file.
     * 
     * @return HashMap<String, String>
     */
    private Map<String, String> readDefaultConfigMap() {
        Map<String, String> configMap = new HashMap<String, String>();
        try {
            Map<String, String> temp = FileUtility
                    .readConfigFileAsKeyValueMap(Settings.getDefaultConfigFilePath(this.getClass()), "=");
            if (temp != null) {
                configMap.putAll(temp);
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "Failed to read config", e);
        }
        return configMap;
    }

    /**
     * Initializes the reporting globals based on the argument
     * 
     * The argument is read from the config file
     * 
     * Returns true if the value in the config file was defined properly or not 
     * defined. If the value in the config value is ill-defined then returns false.
     * 
     * @param reportingIntervalSeconds Interval time in seconds to report stats after
     */
    private boolean initReporting(String reportingIntervalSeconds) {
        Long reportingInterval = CommonFunctions.parseLong(reportingIntervalSeconds, null);
        if (reportingInterval != null) {
            if (reportingInterval < 1) { //at least 1 ms
                logger.log(Level.INFO, "Statistics reporting turned off");
            } else {
                reportingEnabled = true;
                reportEveryMs = reportingInterval * 1000;
                lastReportedTime = System.currentTimeMillis();
            }
        } else if (reportingInterval == null
                && (reportingIntervalSeconds != null && !reportingIntervalSeconds.isEmpty())) {
            logger.log(Level.SEVERE, "Invalid value for reporting interval in the config file");
            return false;
        }
        return true;
    }

    /**
     * Returns true if the argument is null, true, false, 1, 0, yes or no.
     * Else returns false.
     * 
     * @param str value to check
     * @return true/false
     */
    private boolean isValidBoolean(String str) {
        return str == null || "true".equalsIgnoreCase(str.trim()) || "false".equalsIgnoreCase(str.trim())
                || "1".equals(str.trim()) || "0".equals(str.trim()) || "yes".equalsIgnoreCase(str.trim())
                || "no".equals(str.trim()) || "on".equalsIgnoreCase(str.trim()) || "off".equals(str.trim());

    }

    /**
     * If the argument is null or doesn't match any of the value boolean options:
     * true, false, 1, 0, yes or no then default value is returned. Else the parsed
     * value is returned.
     * 
     * @param str string to convert to boolean
     * @param defaultValue default value
     * @return true/false
     */
    private boolean parseBoolean(String str, boolean defaultValue) {
        if (str == null) {
            return defaultValue;
        } else {
            str = str.trim();
            if (str.equals("1") || str.equalsIgnoreCase("yes") || str.equalsIgnoreCase("true")
                    || str.equalsIgnoreCase("on")) {
                return true;
            } else if (str.equals("0") || str.equalsIgnoreCase("no") || str.equalsIgnoreCase("false")
                    || str.equalsIgnoreCase("off")) {
                return false;
            } else {
                return defaultValue;
            }
        }
    }

    /**
     * Initializes global boolean flags for this reporter
     * 
     * @param args a map made from arguments key-values
     * @return true if all flags had valid values / false if any of the flags had a non-boolean value
     */
    private boolean initFlagsFromArguments(Map<String, String> args) {
        try {
            globals = Globals.parseArguments(args);
            if (globals == null) {
                throw new Exception("NULL globals object. Failed to initialize flags.");
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to parse arguments", e);
            return false;
        }

        String argValue = args.get("failfast");
        if (isValidBoolean(argValue)) {
            FAIL_FAST = parseBoolean(argValue, FAIL_FAST);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'failfast': " + argValue);
            return false;
        }

        argValue = args.get("fileIO");
        if (isValidBoolean(argValue)) {
            USE_READ_WRITE = parseBoolean(argValue, USE_READ_WRITE);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'fileIO': " + argValue);
            return false;
        }

        argValue = args.get("netIO");
        if (isValidBoolean(argValue)) {
            USE_SOCK_SEND_RCV = parseBoolean(argValue, USE_SOCK_SEND_RCV);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'netIO': " + argValue);
            return false;
        }

        argValue = args.get("units");
        if (isValidBoolean(argValue)) {
            CREATE_BEEP_UNITS = parseBoolean(argValue, CREATE_BEEP_UNITS);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'units': " + argValue);
            return false;
        }

        argValue = args.get("agents");
        if (isValidBoolean(argValue)) {
            AGENTS = parseBoolean(argValue, AGENTS);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'agents': " + argValue);
            return false;
        }

        // Arguments below are only for experimental use
        argValue = args.get("simplify");
        if (isValidBoolean(argValue)) {
            SIMPLIFY = parseBoolean(argValue, SIMPLIFY);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'simplify': " + argValue);
            return false;
        }

        argValue = args.get("procFS");
        if (isValidBoolean(argValue)) {
            PROCFS = parseBoolean(argValue, PROCFS);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'procFS': " + argValue);
            return false;
        }

        argValue = args.get("waitForLog");
        if (isValidBoolean(argValue)) {
            WAIT_FOR_LOG_END = parseBoolean(argValue, WAIT_FOR_LOG_END);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'waitForLog': " + argValue);
            return false;
        }

        argValue = args.get("memorySyscalls");
        if (isValidBoolean(argValue)) {
            USE_MEMORY_SYSCALLS = parseBoolean(argValue, USE_MEMORY_SYSCALLS);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'memorySyscalls': " + argValue);
            return false;
        }

        argValue = args.get("control");
        if (isValidBoolean(argValue)) {
            CONTROL = parseBoolean(argValue, CONTROL);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'control': " + argValue);
            return false;
        }

        // Ignore for now. Changing it now would break code in places.
        // Sucess always assumed to be '1' for now (default value)
        // TODO
        //      if("0".equals(args.get("auditctlSuccessFlag"))){
        //         AUDITCTL_SYSCALL_SUCCESS_FLAG = "0";
        //      }

        argValue = args.get("anonymousMmap");
        if (isValidBoolean(argValue)) {
            ANONYMOUS_MMAP = parseBoolean(argValue, ANONYMOUS_MMAP);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'anonymousMmap': " + argValue);
            return false;
        }

        argValue = args.get("netfilter");
        if (isValidBoolean(argValue)) {
            NETFILTER_RULES = parseBoolean(argValue, NETFILTER_RULES);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'netfilter': " + argValue);
            return false;
        }

        argValue = args.get("refineNet");
        if (isValidBoolean(argValue)) {
            REFINE_NET = parseBoolean(argValue, REFINE_NET);
        } else {
            logger.log(Level.SEVERE, "Invalid flag value for 'refineNet': " + argValue);
            return false;
        }

        // Setting default values here instead of where variables are defined because default values for KM vars depend
        // on whether the data is live or playback.
        boolean logPlayback = argsSpecifyLogPlayback(args);
        String addKmArgValue = args.get(ADD_KM_KEY);
        String handleKmArgValue = args.get(HANDLE_KM_RECORDS_KEY);
        if (logPlayback) { // Can't use isLiveAudit flag because not set yet.
            // default values
            ADD_KM = false; // Doesn't matter for log playback so always false.
            if ("true".equals(handleKmArgValue)) {
                HANDLE_KM_RECORDS = true;
            } else if ("false".equals(handleKmArgValue)) {
                HANDLE_KM_RECORDS = false;
            } else if (handleKmArgValue == null) {
                HANDLE_KM_RECORDS = null; // To be decided by the first network related record
            } else {
                logger.log(Level.SEVERE, "Invalid flag value for '" + HANDLE_KM_RECORDS_KEY + "': " + argValue);
                return false;
            }
        } else { // live audit
            // Default values
            ADD_KM = true;

            // Parsing the values for the KM vars after the default values have be set appropriately (see above)
            if (isValidBoolean(addKmArgValue)) {
                ADD_KM = parseBoolean(addKmArgValue, ADD_KM);
            } else {
                logger.log(Level.SEVERE, "Invalid flag value for '" + ADD_KM_KEY + "': " + addKmArgValue);
                return false;
            }

            // If added modules then also must handle. If not added then cannot handle.
            HANDLE_KM_RECORDS = ADD_KM;
        }

        String mergeUnitKey = "mergeUnit";
        String mergeUnitValue = args.get(mergeUnitKey);
        if (mergeUnitValue != null) {
            mergeUnit = CommonFunctions.parseInt(mergeUnitValue, null);
            if (mergeUnit != null) {
                if (mergeUnit < 0) { // must be positive
                    mergeUnit = null;
                    logger.log(Level.SEVERE,
                            "'" + mergeUnitKey + "' must be non-negative: '" + mergeUnitValue + "'");
                    return false;
                }
            } else {
                logger.log(Level.SEVERE, "'" + mergeUnitKey + "' must be an integer: '" + mergeUnitValue + "'");
                return false;
            }
        }

        if ((ADD_KM && NETFILTER_RULES) // both can't be true
                || ((HANDLE_KM_RECORDS != null && HANDLE_KM_RECORDS) && REFINE_NET)) { // both can't be true
            logger.log(Level.SEVERE,
                    "Incompatible flags value (Can only handle data from either module or iptables): "
                            + "netfilter={0}, refineNet={1}, {2}={3}, {4}={5}",
                    new Object[] { NETFILTER_RULES, REFINE_NET, ADD_KM_KEY, ADD_KM, HANDLE_KM_RECORDS_KEY,
                            HANDLE_KM_RECORDS });
            return false;
        } else {
            if (ADD_KM && (HANDLE_KM_RECORDS != null && !HANDLE_KM_RECORDS)) {
                logger.log(Level.SEVERE, "Must handle kernel module data if kernel module added.");
                return false;
            } else {
                // Logging only relevant flags now for debugging
                logger.log(Level.INFO,
                        "Audit flags: {0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}, {10}={11}, {12}={13}, "
                                + "{14}={15}, {16}={17}, {18}={19}, {20}={21}",
                        new Object[] { "syscall", args.get("syscall"), "fileIO", USE_READ_WRITE, "netIO",
                                USE_SOCK_SEND_RCV, "units", CREATE_BEEP_UNITS, "waitForLog", WAIT_FOR_LOG_END,
                                "netfilter", NETFILTER_RULES, "refineNet", REFINE_NET, ADD_KM_KEY, ADD_KM,
                                HANDLE_KM_RECORDS_KEY, HANDLE_KM_RECORDS, "failfast", FAIL_FAST, mergeUnitKey,
                                mergeUnit });
                logger.log(Level.INFO, globals.toString());
                return true;
            }
        }
    }

    /**
     * Creates a temp file in the given tempDir from the list of audit log files
     * 
     * Returns the path of the temp file is that is created successfully
     * Else returns null
     * 
     * @param spadeAuditBridgeBinaryName name of the spade audit bridge binary
     * @param inputLogFiles list of audit log files (in the defined order)
     * @param tempDirPath path of the temp dir
     * @return path of the temp file which contains the paths of audit logs OR null
     */
    private String createLogListFileForSpadeAuditBridge(String spadeAuditBridgeBinaryName,
            List<String> inputLogFiles, String tempDirPath) {
        try {
            String spadeAuditBridgeInputFilePath = tempDirPath + File.separatorChar + spadeAuditBridgeBinaryName
                    + "." + System.nanoTime();
            File spadeAuditBridgeInputFile = new File(spadeAuditBridgeInputFilePath);
            if (!spadeAuditBridgeInputFile.createNewFile()) {
                logger.log(Level.SEVERE, "Failed to create input file list file for " + spadeAuditBridgeBinaryName);
                return null;
            } else {
                FileUtils.writeLines(spadeAuditBridgeInputFile, inputLogFiles);
                return spadeAuditBridgeInputFile.getAbsolutePath();
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to create input file for " + spadeAuditBridgeBinaryName, e);
        }
        return null;
    }

    /**
     * Returns a list of audit log files if the rotate flag is true.
     * Else returns a list with only the given audit log file as it's element.
     * 
     * The audit log files are added in the convention defined in the function code.
     * 
     * @param inputAuditLogFilePath path of the audit log file
     * @param rotate a flag to tell whether to read the rotated logs or not
     * @return list if input log files or null if error
     */
    private List<String> getListOfInputAuditLogs(String inputAuditLogFilePath, boolean rotate) {
        // Build a list of audit log files to be read
        LinkedList<String> inputAuditLogFiles = new LinkedList<String>();
        inputAuditLogFiles.addFirst(inputAuditLogFilePath); //add the file in the argument
        if (rotate) { //if rotate is true then add the rest too based on the decided convention
            //convention: name format of files to be processed -> name.1, name.2 and so on where 
            //name is the name of the file passed in as argument
            //can only process 99 logs
            for (int logCount = 1; logCount <= 99; logCount++) {
                String logPath = inputAuditLogFilePath + "." + logCount;
                try {
                    if (FileUtility.doesPathExist(logPath)) {
                        if (FileUtility.isFile(logPath)) {
                            if (FileUtility.isFileReadable(logPath)) {
                                inputAuditLogFiles.addFirst(logPath);
                                //adding first so that they are added in the reverse order
                            } else {
                                logger.log(Level.WARNING, "Log skipped because file not readable: " + logPath);
                            }
                        }
                    }
                } catch (Exception e) {
                    logger.log(Level.SEVERE, "Failed to check if log path is readable: " + logPath, e);
                    return null;
                }
            }
        }
        return inputAuditLogFiles;
    }

    private String[] buildIptableRules(String uid, boolean ignore) {
        String tcpInput = "INPUT -p tcp -m state --state NEW -j AUDIT --type accept",
                tcpOutput = "OUTPUT -p tcp -m state --state NEW -j AUDIT --type accept",
                udpInput = "INPUT -p udp -m state --state NEW -j AUDIT --type accept",
                udpOutput = "OUTPUT -p udp -m state --state NEW -j AUDIT --type accept",
                nonNewInput = "INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT",
                nonNewOutput = "OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT";

        String uidOutput = null;
        if (ignore) { // ignore only the given uid
            uidOutput = "OUTPUT -m owner --uid-owner " + uid + " -j ACCEPT";
        } else { // capture only the given uid
            uidOutput = "OUTPUT -m owner ! --uid-owner " + uid + " -j ACCEPT";
        }
        // Order matters
        /*
         * The rules are going to be inserted because we want to precede any other rules that might already
         * exist. That's why the rules are inserted in reverse order so that they are in the order that we want
         * them to be. So, first add the rules to exclude activity that we don't  want and then add the rules 
         * for the activity which we want to go to linux audit.
         */
        return new String[] { tcpInput, tcpOutput, udpInput, udpOutput, nonNewInput, nonNewOutput, uidOutput };
    }

    private void doCleanup(String rulesType, String logListFile) {
        if (isLiveAudit) {
            if (!"none".equals(rulesType)) {
                removeAuditctlRules();
            }
            if (NETFILTER_RULES) {
                removeIptablesRules(iptablesRules);
            }
            if (ADD_KM) {
                removeControllerNetworkKernelModule();
            }
        } else {
            try {
                if (FileUtility.doesPathExist(logListFile)) {
                    if (FileUtility.isFile(logListFile)) {
                        if (!FileUtility.deleteFile(logListFile)) {
                            logger.log(Level.WARNING, "Failed to delete temp log list file: " + logListFile);
                        }
                    } else {
                        logger.log(Level.WARNING,
                                "Failed to delete log list temp file. Not a file: " + logListFile);
                    }
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to check and delete log list file: " + logListFile, e);
            }
        }
        if (artifactManager != null) {
            artifactManager.doCleanUp();
        }
        if (processManager != null) {
            processManager.doCleanUp();
        }
    }

    private boolean argsSpecifyLogPlayback(Map<String, String> args) {
        return args.containsKey("inputDir") || args.containsKey("inputLog");
    }

    @Override
    public boolean launch(String arguments) {
        String spadeAuditBridgeBinaryName = null;
        String spadeAuditBridgeBinaryPath = null;
        String outputLogFilePath = null;
        long recordsToRotateOutputLogAfter = 0;
        String spadeAuditBridgeCommand = null;
        String rulesType = null;
        String logListFile = null;

        Map<String, String> argsMap = CommonFunctions.parseKeyValPairs(arguments);
        Map<String, String> configMap = readDefaultConfigMap();

        // Init reporting globals
        if (!initReporting(configMap.get("reportingIntervalSeconds"))) {
            return false;
        }

        // Get path of spadeAuditBridge binary from the config file
        spadeAuditBridgeBinaryPath = configMap.get("spadeAuditBridge");
        try {
            if (!FileUtility.isFileReadable(spadeAuditBridgeBinaryPath)) {
                logger.log(Level.SEVERE, "File specified in config by 'spadeAuditBridge' key is not readable: "
                        + spadeAuditBridgeBinaryPath);
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE,
                    "Failed to check if file specified in config by 'spadeAuditBridge' key is readable: "
                            + spadeAuditBridgeBinaryPath,
                    e);
            return false;
        }

        String spadeAuditBridgeBinaryPathArray[] = spadeAuditBridgeBinaryPath.split(File.separator);
        spadeAuditBridgeBinaryName = spadeAuditBridgeBinaryPathArray[spadeAuditBridgeBinaryPathArray.length - 1];

        // Init boolean flags from the arguments
        if (!initFlagsFromArguments(argsMap)) {
            return false;
        }

        // Check if the outputLog argument is valid or not
        outputLogFilePath = argsMap.get("outputLog");
        if (outputLogFilePath != null) {
            try {
                if (!FileUtility.createFile(outputLogFilePath)) {
                    logger.log(Level.SEVERE,
                            "Failed to create file specified by 'outputLog' argument: " + outputLogFilePath);
                    return false;
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE,
                        "Failed to create file specified by 'outputLog' argument: " + outputLogFilePath, e);
                return false;
            }
            String recordsToRotateOutputLogAfterArgument = argsMap.get("outputLogRotate");
            if (recordsToRotateOutputLogAfterArgument != null) {
                Long parsedOutputLogRotate = CommonFunctions.parseLong(recordsToRotateOutputLogAfterArgument, null);
                if (parsedOutputLogRotate == null) {
                    logger.log(Level.SEVERE,
                            "Invalid value for 'outputLogRotate': " + recordsToRotateOutputLogAfterArgument);
                    return false;
                } else {
                    recordsToRotateOutputLogAfter = parsedOutputLogRotate;
                }
            }
        }

        String inputLogDirectoryArgument = argsMap.get("inputDir");
        String inputAuditLogFileArgument = argsMap.get("inputLog");
        if (argsSpecifyLogPlayback(argsMap)) {
            // is log playback
            isLiveAudit = false;

            if (inputAuditLogFileArgument != null) {

                try {
                    if (!FileUtility.isFileReadable(inputAuditLogFileArgument)) {
                        logger.log(Level.SEVERE, "File specified for 'inputLog' argument not readable: "
                                + inputAuditLogFileArgument);
                        return false;
                    }
                } catch (Exception e) {
                    logger.log(Level.SEVERE,
                            "Failed to check if file specified for 'inputLog' argument is readable: "
                                    + inputAuditLogFileArgument,
                            e);
                    return false;
                }

                // Whether to read from rotated logs or not
                boolean rotate = false;
                String rotateArgument = argsMap.get("rotate");
                if (isValidBoolean(rotateArgument)) {
                    rotate = parseBoolean(rotateArgument, false);
                } else {
                    logger.log(Level.SEVERE, "Invalid value for 'rotate' flag: " + rotateArgument);
                    return false;
                }

                List<String> inputAuditLogFiles = getListOfInputAuditLogs(inputAuditLogFileArgument, rotate);

                if (inputAuditLogFiles == null) {
                    logger.log(Level.SEVERE, "Failed to get list of input log");
                    return false;
                } else {
                    logger.log(Level.INFO, "Total logs to process: " + inputAuditLogFiles.size() + " and list = "
                            + inputAuditLogFiles);
                }

                // Only needed in case of audit log files and not in case of live audit
                String tempDirPath = configMap.get("tempDir");
                try {
                    if (!FileUtility.createDirectories(tempDirPath)) {
                        logger.log(Level.SEVERE,
                                "Failed to create temp directory defined in config with key 'tempDir': "
                                        + tempDirPath);
                        return false;
                    }
                } catch (Exception e) {
                    logger.log(Level.SEVERE,
                            "Failed to create temp directory defined in config with key 'tempDir': " + tempDirPath,
                            e);
                    return false;
                }

                // Create the input file for spadeAuditBridge to read the audit logs from 
                logListFile = createLogListFileForSpadeAuditBridge(spadeAuditBridgeBinaryName, inputAuditLogFiles,
                        tempDirPath);
                if (logListFile == null) {
                    return false;
                }

                // Build the command to use
                spadeAuditBridgeCommand = spadeAuditBridgeBinaryPath + ((CREATE_BEEP_UNITS) ? " -u" : "")
                        + ((WAIT_FOR_LOG_END) ? " -w" : "") + " -f " + logListFile;
            } else {
                // Input log directory section

                try {
                    File dir = new File(inputLogDirectoryArgument);

                    if (dir.exists() && dir.isDirectory()) {

                        // Check if logs exist
                        if (dir.list().length != 0) {

                            // Confirm timestamp
                            String inputLogTimeArgument = argsMap.get("inputTime");
                            if (inputLogTimeArgument != null) {
                                try {
                                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
                                    dateFormat.parse(inputLogTimeArgument);
                                    // parsed successfully
                                } catch (Exception e) {
                                    logger.log(Level.SEVERE, "Invalid time format for argument 'inputTime'. "
                                            + "Expected: yyyy-MM-dd:HH:mm:ss", e);
                                    return false;
                                }
                            }

                            // Build the command to use
                            spadeAuditBridgeCommand = spadeAuditBridgeBinaryPath
                                    + ((CREATE_BEEP_UNITS) ? " -u" : "") + ((WAIT_FOR_LOG_END) ? " -w" : "")
                                    + " -d " + inputLogDirectoryArgument
                                    + ((mergeUnit != null) ? " -m " + mergeUnit : "")
                                    + ((inputLogTimeArgument != null) ? " -t " + inputLogTimeArgument : "");

                        } else {
                            logger.log(Level.SEVERE, "No log file in 'inputDir' to process");
                            return false;
                        }

                    } else {
                        logger.log(Level.SEVERE, "Path for 'inputDir' doesn't exist or isn't a directory");
                        return false;
                    }

                } catch (Exception e) {
                    logger.log(Level.SEVERE, "Failed to process 'inputDir' argument", e);
                    return false;
                }

            }

        } else { // live audit

            isLiveAudit = true;

            //valid values: null (i.e. default), 'none' no rules, 'all' an audit rule with all system calls
            rulesType = argsMap.get("syscall");
            if (rulesType != null && !rulesType.equals("none") && !rulesType.equals("all")) {
                logger.log(Level.SEVERE, "Invalid value for 'rules' argument: " + rulesType);
                return false;
            }

            spadeAuditBridgeCommand = spadeAuditBridgeBinaryPath + ((CREATE_BEEP_UNITS) ? " -u" : "")
                    + ((mergeUnit != null) ? " -m " + mergeUnit : "") +
                    // Don't use WAIT_FOR_LOG_END here because the interrupt would be ignored by spadeAuditBridge then
                    " -s " + "/var/run/audispd_events";

        }

        // used to identify failure and do cleanup.
        boolean success = true;

        if (success) {
            try {
                artifactManager = new ArtifactManager(this, globals);
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to instantiate artifact manager", e);
                success = false;
            }
        }

        if (success) {
            try {
                if (AGENTS) { // Make sure that this is done before starting the event reader thread
                    processManager = new ProcessWithoutAgentManager(this, SIMPLIFY, CREATE_BEEP_UNITS);
                } else {
                    processManager = new ProcessWithAgentManager(this, SIMPLIFY, CREATE_BEEP_UNITS);
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to instantiate process manager", e);
                success = false;
            }
        }

        if (success) {
            if (isLiveAudit) {
                // if live audit and no km but handling records then error
                if (!ADD_KM && HANDLE_KM_RECORDS) { // in case of live audit HANDLE_KM_RECORDS will never be null
                    logger.log(Level.SEVERE,
                            "Can't handle kernel module data without kernel module added for Live Audit");
                    success = false;
                }
            }
        }

        if (success) {
            try {

                java.lang.Process spadeAuditBridgeProcess = runSpadeAuditBridge(spadeAuditBridgeCommand);

                Thread errorReaderThread = getErrorStreamReaderForProcess(spadeAuditBridgeBinaryName,
                        spadeAuditBridgeProcess.getErrorStream());
                errorReaderThread.start();

                AuditEventReader auditEventReader = getAuditEventReader(spadeAuditBridgeCommand,
                        spadeAuditBridgeProcess.getInputStream(), outputLogFilePath, recordsToRotateOutputLogAfter);

                Thread auditEventReaderThread = getAuditEventReaderThread(spadeAuditBridgeBinaryName,
                        auditEventReader, isLiveAudit, rulesType, logListFile);
                auditEventReaderThread.start();

                try {
                    Thread.sleep(PID_MSG_WAIT_TIMEOUT);
                } catch (Exception e) {
                }

                if (spadeAuditBridgeProcessPid == null) {
                    // still didn't get the pid that means the process didn't start successfully
                    logger.log(Level.SEVERE, "Process didn't start successfully");
                    success = false;
                }

            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to start Audit", e);
                success = false;
            }
        }

        if (success) {
            if (isLiveAudit) {

                if (success) {
                    if (ADD_KM || NETFILTER_RULES || rulesType == null || rulesType.equals("all")) {
                        String uid = null;
                        boolean ignoreUid; // if true then exclude the user else only include the given user
                        String argsUsername = argsMap.get("user");
                        if (argsUsername == null) {
                            ignoreUid = true;
                            uid = getOwnUid();
                        } else {
                            ignoreUid = false;
                            uid = checkIfValidUsername(argsUsername);
                        }
                        if (uid != null) {
                            String ignoreProcesses = "auditd kauditd audispd " + spadeAuditBridgeBinaryName;
                            List<String> pidsToIgnore = listOfPidsToIgnore(ignoreProcesses);
                            if (pidsToIgnore != null) {

                                String ignoreProcessesValueFromConfig = configMap.get("ignoreProcesses");
                                if (ignoreProcessesValueFromConfig != null) {
                                    String[] ignoreProcessesArray = ignoreProcessesValueFromConfig.split(",");
                                    for (String ignoreProcess : ignoreProcessesArray) {
                                        namesOfProcessesToIgnoreFromConfig.add(ignoreProcess.trim());
                                    }
                                }

                                List<String> ppidsToIgnore = new ArrayList<String>(pidsToIgnore); // same as pids
                                List<String> pidsToIgnoreFromConfig = getPidsFromConfig(configMap,
                                        "ignoreProcesses");
                                List<String> ppidsToIgnoreFromConfig = getPidsFromConfig(configMap,
                                        "ignoreParentProcesses");
                                if (pidsToIgnoreFromConfig != null) { // optional
                                    pidsToIgnore.addAll(pidsToIgnoreFromConfig);
                                    logger.log(Level.INFO,
                                            "Ignoring pids {0} for processes with names from config: {1}",
                                            new Object[] { pidsToIgnoreFromConfig,
                                                    configMap.get("ignoreProcesses") });
                                }
                                if (ppidsToIgnoreFromConfig != null) { // optional
                                    ppidsToIgnore.addAll(ppidsToIgnoreFromConfig);
                                    logger.log(Level.INFO,
                                            "Ignoring ppids {0} for processes with names from config: {1}",
                                            new Object[] { ppidsToIgnoreFromConfig,
                                                    configMap.get("ignoreParentProcesses") });
                                }

                                if (ADD_KM) {
                                    success = addNetworkKernelModule(kernelModulePath, kernelModuleControllerPath,
                                            uid, ignoreUid, pidsToIgnore, ppidsToIgnore, USE_SOCK_SEND_RCV);
                                }
                                if (success) {
                                    if (NETFILTER_RULES) {
                                        iptablesRules = buildIptableRules(uid, ignoreUid);
                                        success = setIptablesRules(iptablesRules);
                                    }
                                    if (success) {
                                        success = setAuditControlRules(rulesType, uid, ignoreUid, pidsToIgnore,
                                                ppidsToIgnore, ADD_KM);
                                    }
                                }
                            } else {
                                success = false;
                            }
                        } else {
                            success = false;
                        }
                    }
                }
            }
        }

        if (success) {
            return true;
        } else {
            // The spadeAuditBridge might have started
            if (spadeAuditBridgeProcessPid != null) {
                sendSignalToPid(spadeAuditBridgeProcessPid, "9"); // force kill since Audit not added
            }
            doCleanup(rulesType, logListFile);
            return false;
        }
    }

    private List<String> getPidsFromConfig(Map<String, String> configMap, String processNamesKey) {
        if (configMap != null && processNamesKey != null) {
            String processNames = configMap.get(processNamesKey);
            if (processNames != null) {
                processNames = processNames.trim();
                if (!processNames.isEmpty()) {
                    // The value is comma-separated. Replacing ',' with ' ' because that is the format
                    // expected by the 'pidof' command.
                    processNames = processNames.replace(',', ' ');
                    List<String> pids = listOfPidsToIgnore(processNames); // Can return null;
                    return pids;
                }
            }
        }
        return null;
    }

    private AuditEventReader getAuditEventReader(String spadeAuditBridgeCommand, InputStream stdoutStream,
            String outputLogFilePath, Long recordsToRotateOutputLogAfter) {

        try {
            // Create the audit event reader using the STDOUT of the spadeAuditBridge process
            AuditEventReader auditEventReader = new AuditEventReader(spadeAuditBridgeCommand, stdoutStream,
                    FAIL_FAST);
            if (outputLogFilePath != null) {
                auditEventReader.setOutputLog(outputLogFilePath, recordsToRotateOutputLogAfter);
            }
            return auditEventReader;
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to create audit event reader", e);
            return null;
        }
    }

    private boolean sendSignalToPid(String pid, String signal) {
        try {
            Runtime.getRuntime().exec("kill -" + signal + " " + pid);
            return true;
        } catch (Exception e) {
            logger.log(Level.WARNING, "Failed to send signal '" + signal + "' to pid '" + pid + "'", e);
            return false;
        }
    }

    private Thread getErrorStreamReaderForProcess(final String processName, final InputStream errorStream) {
        try {

            Thread errorStreamReaderThread = new Thread(new Runnable() {
                public void run() {
                    BufferedReader errorStreamReader = null;
                    try {
                        errorStreamReader = new BufferedReader(new InputStreamReader(errorStream));
                        String line = null;
                        while ((line = errorStreamReader.readLine()) != null) {
                            if (line.startsWith("#CONTROL_MSG#")) {
                                spadeAuditBridgeProcessPid = line.split("=")[1];
                            } else {
                                logger.log(Level.INFO, processName + " output: " + line);
                            }
                        }
                    } catch (Exception e) {
                        logger.log(Level.WARNING, "Failed to read error stream for process: " + processName);
                    } finally {
                        if (errorStreamReader != null) {
                            try {
                                errorStreamReader.close();
                            } catch (Exception e) {
                                //ignore
                            }
                        }
                    }
                    logger.log(Level.INFO, "Exiting error reader thread for process: " + processName);
                }
            });

            return errorStreamReaderThread;

        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to create error reader thread for " + processName, e);
            return null;
        }
    }

    private Thread getAuditEventReaderThread(final String processName, final AuditEventReader auditEventReader,
            final boolean isLiveAudit, final String rulesType, final String logListFile) {
        Runnable runnable = new Runnable() {

            @Override
            public void run() {

                eventReaderThreadRunning = true;

                if (isLiveAudit) {
                    if (PROCFS) {
                        processManager.putProcessesFromProcFs();
                    }
                }

                while (true) {
                    Map<String, String> eventData = null;
                    try {
                        eventData = auditEventReader.readEventData();
                        if (eventData == null) {
                            // EOF
                            break;
                        } else {
                            try {
                                finishEvent(eventData);
                            } catch (Exception e) {
                                logger.log(Level.SEVERE, "Failed to handle event: " + eventData, e);
                                if (FAIL_FAST) {
                                    break;
                                }
                            }
                        }
                    } catch (MalformedAuditDataException made) {
                        logger.log(Level.SEVERE, "Failed to parse event", made);
                        if (FAIL_FAST) {
                            break;
                        }
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, "Stopped reading event stream. ", e);
                        break;
                    }
                }
                try {
                    if (auditEventReader != null) {
                        auditEventReader.close();
                    }
                } catch (Exception e) {
                    logger.log(Level.WARNING, "Failed to close audit event reader", e);
                }

                // Sent a signal to the process in shutdown to stop reading.
                // That's why here.
                doCleanup(rulesType, logListFile);
                logger.log(Level.INFO, "Exiting event reader thread for process: " + processName);
                eventReaderThreadRunning = false;
            }
        };
        return new Thread(runnable);
    }

    private java.lang.Process runSpadeAuditBridge(String command) {
        try {
            java.lang.Process spadeAuditBridgeProcess = Runtime.getRuntime().exec(command);
            logger.log(Level.INFO, "Succesfully executed the command: '" + command + "'");
            return spadeAuditBridgeProcess;
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to execute command: '" + command + "'", e);
            return null;
        }
    }

    private boolean setIptablesRules(String[] iptablesRules) {
        try {
            for (String iptablesRule : iptablesRules) {
                // Using insert to precede any existing rules
                String executeCommand = "iptables -I " + iptablesRule;
                Execute.Output output = Execute.getOutput(executeCommand);
                output.log();
                if (output.hasError()) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to set iptable rules", e);
            return false;
        }
    }

    private boolean removeIptablesRules(String[] iptablesRules) {
        boolean allRemoved = true;
        for (String iptablesRule : iptablesRules) {
            String executeCommand = "iptables -D " + iptablesRule;
            try {
                Execute.Output output = Execute.getOutput(executeCommand);
                output.log();
                allRemoved = allRemoved && (!output.hasError());
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to remove iptables rule. Remove manually.", e);
                return false;
            }
        }
        return allRemoved;
    }

    private Boolean kernelModuleExists(String kernelModuleName) {
        try {
            Execute.Output output = Execute.getOutput("lsmod");
            if (output.hasError()) {
                output.log();
                return null;
            } else {
                List<String> stdOutLines = output.getStdOut();
                for (String line : stdOutLines) {
                    String[] tokens = line.split("\\s+");
                    if (tokens[0].equals(kernelModuleName)) {
                        return true;
                    }
                }
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to check if module '" + kernelModuleName + "' exists", e);
            return null;
        }
    }

    private String getKernelModuleName(String kernelModulePath) {
        if (kernelModulePath != null) {
            try {
                String tokens[] = kernelModulePath.split("/");
                String name = tokens[tokens.length - 1];
                tokens = name.split("\\.");
                name = tokens[0];
                return name;
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to get module name for: " + kernelModulePath, e);
                return null;
            }
        } else {
            return null;
        }
    }

    private boolean addKernelModule(String command) {
        try {
            Execute.Output output = Execute.getOutput(command);
            if (!output.getStdErr().isEmpty()) {
                logger.log(Level.SEVERE, "Command \"{0}\" failed with error: {1}.",
                        new Object[] { command, output.getStdErr() });
                logger.log(Level.SEVERE, "Run 'grep netio <dmesg-logs> | tail -5' to check for exact error.");
                return false;
            } else {
                logger.log(Level.INFO, "Command \"{0}\" succeeded with output: {1}.",
                        new Object[] { command, output.getStdOut() });
                return true;
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to add kernel module with command: " + command, e);
            return false;
        }
    }

    private boolean addNetworkKernelModule(String kernelModulePath, String kernelModuleControllerPath, String uid,
            boolean ignoreUid, List<String> ignorePids, List<String> ignorePpids, boolean interceptSendRecv) {
        if (uid == null || uid.isEmpty() || ignorePids == null || ignorePids.isEmpty() || ignorePpids == null
                || ignorePpids.isEmpty()) {
            logger.log(Level.SEVERE, "Invalid args. uid={0}, pids={1}, ppids={2}",
                    new Object[] { uid, ignorePids, ignorePpids });
            return false;
        } else {
            try {
                if (!FileUtility.isFileReadable(kernelModulePath)) {
                    logger.log(Level.SEVERE, "Kernel module path not readable: " + kernelModulePath);
                    return false;
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to check if kernel module path is readable: " + kernelModulePath,
                        e);
                return false;
            }
            try {
                if (!FileUtility.isFileReadable(kernelModuleControllerPath)) {
                    logger.log(Level.SEVERE,
                            "Controller kernel module path not readable: " + kernelModuleControllerPath);
                    return false;
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to check if controller kernel module path is readable: "
                        + kernelModuleControllerPath, e);
                return false;
            }
            String kernelModuleName = getKernelModuleName(kernelModulePath);
            if (kernelModuleName != null) {
                String kernelModuleControllerName = getKernelModuleName(kernelModuleControllerPath);
                if (kernelModuleControllerName != null) {
                    Boolean kernelModuleControllerExists = kernelModuleExists(kernelModuleControllerName);
                    if (kernelModuleControllerExists != null) {
                        if (kernelModuleControllerExists) {
                            logger.log(Level.SEVERE, "Kernel module controller '" + kernelModuleControllerPath
                                    + "' " + "already exists.");
                            return false;
                        } else {
                            Boolean kernelModuleExists = kernelModuleExists(kernelModuleName);
                            if (kernelModuleExists != null) {
                                if (kernelModuleExists == false) {
                                    // add the main kernel module
                                    String kernelModuleAddCommand = "insmod " + kernelModulePath;
                                    if (!addKernelModule(kernelModuleAddCommand)) {
                                        return false;
                                    }
                                }
                                // add the controller kernel module
                                StringBuffer pids = new StringBuffer();
                                ignorePids.forEach(ignorePid -> {
                                    pids.append(ignorePid).append(",");
                                });
                                pids.deleteCharAt(pids.length() - 1);// delete trailing comma

                                StringBuffer ppids = new StringBuffer();
                                ignorePpids.forEach(ignorePpid -> {
                                    ppids.append(ignorePpid).append(",");
                                });
                                ppids.deleteCharAt(ppids.length() - 1);// delete trailing comma

                                String ignoreUidsArg = ignoreUid ? "1" : "0"; // 0 is capture

                                String kernelModuleControllerAddCommand = String.format(
                                        "insmod %s uids=\"%s\" syscall_success=\"1\" "
                                                + "pids_ignore=\"%s\" ppids_ignore=\"%s\" net_io=\"%s\" "
                                                + "ignore_uids=\"%s\"",
                                        kernelModuleControllerPath, uid, pids, ppids, interceptSendRecv ? "1" : "0",
                                        ignoreUidsArg);

                                if (!addKernelModule(kernelModuleControllerAddCommand)) {
                                    return false;
                                } else {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    private boolean removeControllerNetworkKernelModule() {
        String controllerModulePath = kernelModuleControllerPath;
        if (CommonFunctions.isNullOrEmpty(controllerModulePath)) {
            logger.log(Level.WARNING, "NULL/Empty controller kernel module path: " + controllerModulePath);
        } else {
            String controllerModuleName = getKernelModuleName(controllerModulePath);
            if (CommonFunctions.isNullOrEmpty(controllerModuleName)) {
                logger.log(Level.SEVERE, "Failed to get module name from module path: " + controllerModulePath);
            } else {
                Boolean controllerModuleExists = kernelModuleExists(controllerModuleName);
                if (controllerModuleExists == null) {
                    logger.log(Level.SEVERE,
                            "Failed to check if controller module '" + controllerModuleName + "' exists");
                } else {
                    if (controllerModuleExists == false) {
                        logger.log(Level.INFO, "Controller kernel module not added : " + controllerModuleName);
                    } else {
                        String command = "rmmod " + controllerModuleName;
                        try {
                            Execute.Output output = Execute.getOutput(command);
                            output.log();
                            return !output.hasError();
                        } catch (Exception e) {
                            logger.log(Level.SEVERE, "Failed to controller module with command: " + command, e);
                        }
                    }
                }
            }
        }
        return false;
    }

    private boolean setAuditControlRules(String rulesType, String uid, boolean ignoreUid, List<String> ignorePids,
            List<String> ignorePpids, boolean kmAdded) {
        try {

            if (uid == null || uid.isEmpty() || ignorePids == null || ignorePids.isEmpty() || ignorePpids == null
                    || ignorePpids.isEmpty()) {
                logger.log(Level.SEVERE, "Invalid args. uid={0}, pids={1}, ppids={2}",
                        new Object[] { uid, ignorePids, ignorePpids });
                return false;
            }

            if ("none".equals(rulesType)) {
                // do nothing
                return true;
            } else {

                // Remove any existing audit rules
                if (!removeAuditctlRules()) {
                    return false;
                }

                // Set arch to use in the rules
                String archField = "-F arch=b64 ";

                String uidField = null;
                if (ignoreUid) { // ignore the given uid
                    uidField = "-F uid!=" + uid + " ";
                } else { // only capture the given uid
                    uidField = "-F uid=" + uid + " ";
                }

                StringBuffer pidFields = new StringBuffer();
                ignorePids.forEach(ignorePid -> {
                    pidFields.append("-F pid!=").append(ignorePid).append(" ");
                });

                StringBuffer ppidFields = new StringBuffer();
                ignorePpids.forEach(ignorePpid -> {
                    ppidFields.append("-F ppid!=").append(ignorePpid).append(" ");
                });

                String pidAndPpidFields = pidFields.toString() + ppidFields.toString();

                List<String> auditRules = new ArrayList<String>();

                if ("all".equals(rulesType)) {

                    String netIONeverSyscallsRule = null;
                    if (kmAdded) {
                        netIONeverSyscallsRule = "auditctl -a exit,never ";
                        netIONeverSyscallsRule += archField;
                        netIONeverSyscallsRule += "-S kill -S socket -S bind -S accept -S accept4 -S connect ";
                        netIONeverSyscallsRule += "-S sendmsg -S sendto -S recvmsg -S recvfrom -S sendmmsg -S recvmmsg ";
                    }

                    String specialSyscallsRule = "auditctl -a exit,always ";
                    String allSyscallsAuditRule = "auditctl -a exit,always ";

                    allSyscallsAuditRule += archField;
                    specialSyscallsRule += archField;

                    allSyscallsAuditRule += "-S all ";
                    // The connect syscall rule won't be matched even if module added because the 'never' rule is before this one
                    specialSyscallsRule += "-S exit -S exit_group -S kill -S connect ";

                    allSyscallsAuditRule += uidField;
                    specialSyscallsRule += uidField;

                    allSyscallsAuditRule += "-F success=" + AUDITCTL_SYSCALL_SUCCESS_FLAG + " ";

                    // THE NEVER RULE SHOULD ALWAYS BE THE FIRST IF IT IS INITIALIZED
                    if (kmAdded && netIONeverSyscallsRule != null) {
                        auditRules.add(netIONeverSyscallsRule);
                    }
                    auditRules.add(specialSyscallsRule + pidAndPpidFields);
                    auditRules.add(allSyscallsAuditRule + pidAndPpidFields);

                } else if (rulesType == null) {

                    String auditRuleWithoutSuccess = "auditctl -a exit,always ";
                    String auditRuleWithSuccess = "auditctl -a exit,always ";

                    auditRuleWithSuccess += archField;
                    auditRuleWithoutSuccess += archField;

                    auditRuleWithSuccess += uidField;
                    auditRuleWithoutSuccess += uidField;

                    auditRuleWithoutSuccess += "-S exit -S exit_group ";
                    if (!kmAdded) {
                        auditRuleWithoutSuccess += "-S connect -S kill ";
                    }

                    if (USE_READ_WRITE) {
                        auditRuleWithSuccess += "-S read -S readv -S pread -S preadv -S write -S writev -S pwrite -S pwritev ";
                    }
                    if (!kmAdded) { // since km not added we don't need to log these syscalls
                        if (USE_SOCK_SEND_RCV) {
                            auditRuleWithSuccess += "-S sendto -S recvfrom -S sendmsg -S recvmsg ";
                        }
                        auditRuleWithSuccess += "-S bind -S accept -S accept4 -S socket ";
                    }
                    if (USE_MEMORY_SYSCALLS) {
                        auditRuleWithSuccess += "-S mmap -S mprotect ";
                    }
                    auditRuleWithSuccess += "-S unlink -S unlinkat ";
                    auditRuleWithSuccess += "-S link -S linkat -S symlink -S symlinkat ";
                    auditRuleWithSuccess += "-S clone -S fork -S vfork -S execve ";
                    auditRuleWithSuccess += "-S open -S close -S creat -S openat -S mknodat -S mknod ";
                    auditRuleWithSuccess += "-S dup -S dup2 -S dup3 ";
                    auditRuleWithSuccess += "-S fcntl ";
                    auditRuleWithSuccess += "-S rename -S renameat ";
                    auditRuleWithSuccess += "-S setuid -S setreuid ";
                    auditRuleWithSuccess += "-S setgid -S setregid ";
                    if (!SIMPLIFY) {
                        auditRuleWithSuccess += "-S setresuid -S setfsuid ";
                        auditRuleWithSuccess += "-S setresgid -S setfsgid ";
                    }
                    auditRuleWithSuccess += "-S chmod -S fchmod -S fchmodat ";
                    auditRuleWithSuccess += "-S pipe -S pipe2 ";
                    auditRuleWithSuccess += "-S truncate -S ftruncate ";
                    auditRuleWithSuccess += "-S init_module -S finit_module ";
                    auditRuleWithSuccess += "-S tee -S splice -S vmsplice ";
                    auditRuleWithSuccess += "-S socketpair ";

                    auditRuleWithSuccess += "-F success=" + AUDITCTL_SYSCALL_SUCCESS_FLAG + " ";

                    auditRules.add(auditRuleWithoutSuccess + pidAndPpidFields);
                    auditRules.add(auditRuleWithSuccess + pidAndPpidFields);

                } else {
                    logger.log(Level.SEVERE, "Invalid rules arguments: " + rulesType);
                    return false;
                }

                // Execute in provided order!
                for (String auditRule : auditRules) {
                    if (!executeAuditctlRule(auditRule)) {
                        removeAuditctlRules();
                        return false;
                    }
                }
            }

            return true;
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Error configuring audit rules", e);
            return false;
        }

    }

    private boolean executeAuditctlRule(String auditctlRule) {
        try {
            Execute.Output output = Execute.getOutput(auditctlRule);
            output.log();
            return !output.hasError();
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to set audit rule: " + auditctlRule, e);
            return false;
        }
    }

    private boolean removeAuditctlRules() {
        try {
            Execute.Output output = Execute.getOutput("auditctl -D");
            output.log();
            return !output.hasError();
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to remove audit rules", e);
            return false;
        }
    }

    private List<String> listOfPidsToIgnore(String ignoreProcesses) {
        //      ignoreProcesses argument is a string of process names separated by blank space
        BufferedReader pidReader = null;
        try {
            List<String> pids = new ArrayList<String>();
            if (ignoreProcesses != null && !ignoreProcesses.trim().isEmpty()) {
                // Using pidof command now to get all pids of the mentioned processes
                java.lang.Process pidChecker = Runtime.getRuntime().exec("pidof " + ignoreProcesses);
                // pidof returns pids of given processes as a string separated by a blank space
                pidReader = new BufferedReader(new InputStreamReader(pidChecker.getInputStream()));
                String pidline = pidReader.readLine();
                if (pidline != null) {
                    //    added all returned from pidof command
                    pids.addAll(Arrays.asList(pidline.split("\\s+")));
                } else {
                    logger.log(Level.INFO, "No running process(es) with name(s): " + ignoreProcesses);
                }
            }
            return pids;
        } catch (Exception e) {
            logger.log(Level.WARNING, "Error building list of processes to ignore: " + ignoreProcesses, e);
            return null;
        } finally {
            if (pidReader != null) {
                try {
                    pidReader.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }

    // Returns the uid if valid username
    private String checkIfValidUsername(String name) {
        String command = "id -u " + name;
        try {
            Execute.Output output = Execute.getOutput(command);
            if (output.hasError()) {
                logger.log(Level.SEVERE, "Invalid username provided. Command: {0}. Error: {1}",
                        new Object[] { command, output.getStdErr() });
            } else {
                List<String> stdOutLines = output.getStdOut();
                if (stdOutLines.size() == 0) {
                    logger.log(Level.SEVERE, "No uid in output for command: {0}. Output: {1}.",
                            new Object[] { command, stdOutLines });
                } else {
                    String uidLine = stdOutLines.get(0);
                    if (uidLine == null || (uidLine = uidLine.trim()).isEmpty()) {
                        logger.log(Level.SEVERE, "NULL/Empty uid for command: {0}. Output: {1}.",
                                new Object[] { command, stdOutLines });
                    } else {
                        return uidLine;
                    }
                }
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to execute command: " + command, e);
        }
        return null;
    }

    private String getOwnUid() {
        String command = "id -u";
        try {
            String uid = null;
            Execute.Output output = Execute.getOutput(command);
            if (output.hasError()) {
                logger.log(Level.SEVERE, "Failed to get user id of JVM. Command: {0}. Error: {1}.",
                        new Object[] { command, output.getStdErr() });
                return null;
            } else {
                List<String> stdOutLines = output.getStdOut();
                if (stdOutLines.size() == 0) {
                    logger.log(Level.SEVERE, "No uid in output for command: {0}. Output: {1}.",
                            new Object[] { command, stdOutLines });
                    return null;
                } else {
                    String uidLine = stdOutLines.get(0);
                    if (uidLine == null || (uidLine = uidLine.trim()).isEmpty()) {
                        logger.log(Level.SEVERE, "NULL/Empty uid for command: {0}. Output: {1}.",
                                new Object[] { command, stdOutLines });
                        return null;
                    } else {
                        uid = uidLine;
                    }
                }
                return uid;
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to get user id of JVM using command: " + command, e);
            return null;
        }
    }

    @Override
    public boolean shutdown() {

        // Send an interrupt to the spadeAuditBridgeProcess

        sendSignalToPid(spadeAuditBridgeProcessPid, "2");

        // Return. The event reader thread and the error reader thread will exit on their own.
        // The event reader thread will do the state cleanup

        while (eventReaderThreadRunning) {
            // Wait while the event reader thread is still running i.e. buffer being emptied
            try {
                Thread.sleep(PID_MSG_WAIT_TIMEOUT);
            } catch (Exception e) {
            }
        }

        // force print stats before exiting
        printStats(true);

        return true;
    }

    private void printStats(boolean forcePrint) {
        if (reportingEnabled || forcePrint) {
            long currentTime = System.currentTimeMillis();
            if (((currentTime - lastReportedTime) >= reportEveryMs) || forcePrint) {
                Runtime runtime = Runtime.getRuntime();
                long usedMemoryMB = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
                int internalBufferSize = getBuffer().size();
                String statString = String.format("Internal buffer size: %d, JVM memory in use: %dMB",
                        internalBufferSize, usedMemoryMB);
                if (REFINE_NET) {
                    String netfilterStat = String.format(
                            "Unmatched: " + "%d netfilter-syscall, " + "%d syscall-netfilter. " + "Matched: "
                                    + "%d netfilter-syscall, " + "%d syscall-netfilter.",
                            networkAnnotationsFromNetfilter.size(), networkAnnotationsFromSyscalls.size(),
                            matchedNetfilterSyscall, matchedSyscallNetfilter);
                    statString += ", " + netfilterStat;
                }
                logger.log(Level.INFO, statString);
                lastReportedTime = currentTime;
            }
        }
    }

    private void setHandleKMRecordsFlag(boolean isLiveAudit, boolean valueOfHandleKMRecords) {
        // Only set the value if it hasn't been set and is log playback
        if (HANDLE_KM_RECORDS == null && !isLiveAudit) {
            HANDLE_KM_RECORDS = valueOfHandleKMRecords;
            logger.log(Level.INFO, "'handleLocalEndpoints' value set to '" + valueOfHandleKMRecords + "'");
        }
    }

    private void finishEvent(Map<String, String> eventData) {

        printStats(false);

        if (eventData == null) {
            logger.log(Level.WARNING, "Null event data read");
            return;
        }

        try {
            String recordType = eventData.get(AuditEventReader.RECORD_TYPE_KEY);
            if (AuditEventReader.RECORD_TYPE_UBSI_ENTRY.equals(recordType)) {
                processManager.handleUnitEntry(eventData);
            } else if (AuditEventReader.RECORD_TYPE_UBSI_EXIT.equals(recordType)) {
                processManager.handleUnitExit(eventData);
            } else if (AuditEventReader.RECORD_TYPE_UBSI_DEP.equals(recordType)) {
                processManager.handleUnitDependency(eventData);
            } else if (AuditEventReader.RECORD_TYPE_DAEMON_START.equals(recordType)) {
                //processManager.daemonStart(); TODO Not being used until figured out how to handle it.
            } else if (AuditEventReader.RECORD_TYPE_NETFILTER_PKT.equals(recordType)) {
                setHandleKMRecordsFlag(isLiveAudit, false); // Always do first because HANDLE_KM_RECORDS can be null when playback
                if (REFINE_NET) {
                    handleNetfilterPacketEvent(eventData);
                }
            } else if (AuditEventReader.KMODULE_RECORD_TYPE.equals(recordType)) {
                setHandleKMRecordsFlag(isLiveAudit, true); // Always do first because HANDLE_KM_RECORDS can be null when playback
                if (HANDLE_KM_RECORDS) {
                    handleKernelModuleEvent(eventData);
                }
            } else {
                handleSyscallEvent(eventData);
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "Failed to process eventData: " + eventData, e);
        }
    }

    private void handleKernelModuleEvent(Map<String, String> eventData) {
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String time = eventData.get(AuditEventReader.TIME);
        SYSCALL syscall = null;
        try {
            String pid = eventData.get(AuditEventReader.PID);
            Integer syscallNumber = CommonFunctions.parseInt(eventData.get(AuditEventReader.SYSCALL), null);
            String exit = eventData.get(AuditEventReader.EXIT);
            int success = CommonFunctions.parseInt(eventData.get(AuditEventReader.SUCCESS), -1);
            String sockFd = eventData.get(AuditEventReader.KMODULE_FD);
            int sockType = Integer.parseInt(eventData.get(AuditEventReader.KMODULE_SOCKTYPE));
            String localSaddr = eventData.get(AuditEventReader.KMODULE_LOCAL_SADDR);
            String remoteSaddr = eventData.get(AuditEventReader.KMODULE_REMOTE_SADDR);

            if (success == 1) {
                syscall = getSyscall(syscallNumber);
                if (syscall == null || syscall == SYSCALL.UNSUPPORTED) {
                    log(Level.WARNING, "Invalid syscall: " + syscallNumber, null, time, eventId, null);
                } else {
                    switch (syscall) {
                    case BIND:
                        handleBindKernelModule(eventData, time, eventId, syscall, pid, exit, sockFd, sockType,
                                localSaddr, remoteSaddr);
                        break;
                    case ACCEPT:
                    case ACCEPT4:
                        handleAcceptKernelModule(eventData, time, eventId, syscall, pid, exit, sockFd, sockType,
                                localSaddr, remoteSaddr);
                        break;
                    case CONNECT:
                        handleConnectKernelModule(eventData, time, eventId, syscall, pid, exit, sockFd, sockType,
                                localSaddr, remoteSaddr);
                        break;
                    case SENDMSG:
                    case SENDTO:
                        //                  case SENDMMSG: // TODO
                        handleNetworkIOKernelModule(eventData, time, eventId, syscall, pid, exit, sockFd, sockType,
                                localSaddr, remoteSaddr, false);
                        break;
                    case RECVMSG:
                    case RECVFROM:
                        //                  case RECVMMSG:
                        handleNetworkIOKernelModule(eventData, time, eventId, syscall, pid, exit, sockFd, sockType,
                                localSaddr, remoteSaddr, true);
                        break;
                    default:
                        log(Level.WARNING, "Unexpected syscall: " + syscallNumber, null, time, eventId, syscall);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            log(Level.WARNING, "Failed to parse kernel module event", null, time, eventId, syscall);
        }
    }

    private SYSCALL getSyscall(int syscallNumber) {
        return SYSCALL.get64BitSyscall(syscallNumber);
    }

    /**
     * Converts syscall args: 'a0', 'a1', 'a2', and 'a3' from hexadecimal values to decimal values
     * 
     * Conversion done based on the length of the hex value string. If length <= 8 then integer else a long.
     * If length > 16 then truncated to long.
     * 
     * Done so to avoid the issue of incorrectly fitting a small negative (i.e. int) value into a big (i.e. long) value
     * causing a wrong interpretation of bits.
     * 
     * @param eventData map that contains the above-mentioned args keys and values
     * @param time time of the event
     * @param eventId id of the event
     * @param syscall syscall of the event
     */
    private void convertArgsHexToDec(Map<String, String> eventData, String time, String eventId, SYSCALL syscall) {
        String[] argKeys = { AuditEventReader.ARG0, AuditEventReader.ARG1, AuditEventReader.ARG2,
                AuditEventReader.ARG3 };
        for (String argKey : argKeys) {
            String hexArgValue = eventData.get(argKey);
            if (hexArgValue != null) {
                int hexArgValueLength = hexArgValue.length();
                try {
                    BigInteger bigInt = new BigInteger(hexArgValue, 16);
                    String argValueString = null;
                    if (hexArgValueLength <= 8) {
                        int argInt = bigInt.intValue();
                        argValueString = Integer.toString(argInt);
                    } else { // greater than 8
                        if (hexArgValueLength > 16) {
                            log(Level.SEVERE, "Truncated value for '" + argKey + "': '" + hexArgValue
                                    + "'. Too big for 'long' datatype", null, time, eventId, syscall);
                        }
                        long argLong = bigInt.longValue();
                        argValueString = Long.toString(argLong);
                    }
                    eventData.put(argKey, argValueString);
                } catch (Exception e) {
                    log(Level.SEVERE, "Non-numerical value for '" + argKey + "': '" + hexArgValue + "'", e, time,
                            eventId, syscall);
                }
            } else {
                log(Level.SEVERE, "NULL value for '" + argKey + "'", null, time, eventId, syscall);
            }
        }
    }

    /**
     * Gets the key value map from the internal data structure and gets the system call from the map.
     * Gets the appropriate system call based on current architecture
     * If global flag to log only successful events is set to true but the current event wasn't successful then only handle it if was either a kill
     * system call or exit system call or exit_group system call.
     * 
     * IMPORTANT: Converts all 4 arguments, a0 o a3 to decimal integers from hexadecimal integers and puts them back in the key value map
     * 
     * Calls the appropriate system call handler based on the system call
     * 
     * @param eventId id of the event against which the key value maps are saved
     */
    private void handleSyscallEvent(Map<String, String> eventData) {
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        try {

            processManager.processSeenInUnsupportedSyscall(eventData); // Always set first because that is what is done in spadeAuditBridge and it is updated if syscall handled.

            int syscallNum = CommonFunctions.parseInt(eventData.get(AuditEventReader.SYSCALL), -1);

            if (syscallNum == -1) {
                return;
            }

            SYSCALL syscall = getSyscall(syscallNum);

            if (syscall == null) {
                log(Level.WARNING, "Invalid syscall: " + syscallNum, null, time, eventId, null);
                return;
            } else if (syscall == SYSCALL.UNSUPPORTED) {
                return;
            }

            if ("1".equals(AUDITCTL_SYSCALL_SUCCESS_FLAG)
                    && AuditEventReader.SUCCESS_NO.equals(eventData.get(AuditEventReader.SUCCESS))) {
                //if only log successful events but the current event had success no then only monitor the following calls.
                if (syscall == SYSCALL.EXIT || syscall == SYSCALL.EXIT_GROUP || syscall == SYSCALL.CONNECT) {
                    //continue and log these syscalls irrespective of success
                    // Syscall connect can fail with EINPROGRESS flag which we want to 
                    // mark as successful even though we don't know yet
                } else { //for all others don't log
                    return;
                }
            }

            //convert all arguments from hexadecimal format to decimal format and replace them. done for convenience here and to avoid issues. 
            convertArgsHexToDec(eventData, time, eventId, syscall);

            // Check if one of the network related syscalls. Must do this check before because HANDLE_KM_RECORDS can be null
            switch (syscall) {
            case SENDMSG:
            case SENDTO:
            case RECVFROM:
            case RECVMSG:
            case SOCKET:
            case BIND:
            case ACCEPT:
            case ACCEPT4:
            case CONNECT:
                setHandleKMRecordsFlag(isLiveAudit, false);
                break;
            default:
                break;
            }

            switch (syscall) {
            case SOCKETPAIR:
                handleSocketPair(eventData, syscall);
                break;
            case TEE:
            case SPLICE:
                handleTeeSplice(eventData, syscall);
                break;
            case VMSPLICE:
                handleVmsplice(eventData, syscall);
                break;
            case INIT_MODULE:
            case FINIT_MODULE:
                handleInitModule(eventData, syscall);
                break;
            case FCNTL:
                handleFcntl(eventData, syscall);
                break;
            case EXIT:
            case EXIT_GROUP:
                handleExit(eventData, syscall);
                break;
            case WRITE:
            case WRITEV:
            case PWRITE:
            case PWRITEV:
                handleIOEvent(syscall, eventData, false);
                break;
            case SENDMSG:
            case SENDTO:
                if (!HANDLE_KM_RECORDS) {
                    handleIOEvent(syscall, eventData, false);
                }
                break;
            case RECVFROM:
            case RECVMSG:
                if (!HANDLE_KM_RECORDS) {
                    handleIOEvent(syscall, eventData, true);
                }
                break;
            case READ:
            case READV:
            case PREAD:
            case PREADV:
                handleIOEvent(syscall, eventData, true);
                break;
            case MMAP:
                handleMmap(eventData, syscall);
                break;
            case MPROTECT:
                handleMprotect(eventData, syscall);
                break;
            case SYMLINK:
            case LINK:
            case SYMLINKAT:
            case LINKAT:
                handleLinkSymlink(eventData, syscall);
                break;
            case UNLINK:
            case UNLINKAT:
                handleUnlink(eventData, syscall);
                break;
            case VFORK:
            case FORK:
            case CLONE:
                processManager.handleForkVforkClone(eventData, syscall);
                break;
            case EXECVE:
                handleExecve(eventData, syscall);
                break;
            case OPEN:
                handleOpen(eventData, syscall);
                break;
            case CLOSE:
                handleClose(eventData);
                break;
            case CREAT:
                handleCreat(eventData);
                break;
            case OPENAT:
                handleOpenat(eventData, syscall);
                break;
            case MKNODAT:
                handleMknodat(eventData);
                break;
            case MKNOD:
                handleMknod(eventData, syscall);
                break;
            case DUP:
            case DUP2:
            case DUP3:
                handleDup(eventData, syscall);
                break;
            case SOCKET:
                if (!HANDLE_KM_RECORDS) {
                    handleSocket(eventData, syscall);
                }
                break;
            case BIND:
                if (!HANDLE_KM_RECORDS) {
                    handleBind(eventData, syscall);
                }
                break;
            case ACCEPT4:
            case ACCEPT:
                if (!HANDLE_KM_RECORDS) {
                    handleAccept(eventData, syscall);
                }
                break;
            case CONNECT:
                if (!HANDLE_KM_RECORDS) {
                    handleConnect(eventData, syscall);
                }
                break;
            case RENAME:
            case RENAMEAT:
                handleRename(eventData, syscall);
                break;
            case SETUID:
            case SETREUID:
            case SETRESUID:
            case SETFSUID:
            case SETGID:
            case SETREGID:
            case SETRESGID:
            case SETFSGID:
                handleSetuidAndSetgid(eventData, syscall);
                break;
            case CHMOD:
            case FCHMOD:
            case FCHMODAT:
                handleChmod(eventData, syscall);
                break;
            case PIPE:
            case PIPE2:
                handlePipe(eventData, syscall);
                break;
            case TRUNCATE:
            case FTRUNCATE:
                handleTruncate(eventData, syscall);
                break;
            default: //SYSCALL.UNSUPPORTED
                //log(Level.INFO, "Unsupported syscall '"+syscallNum+"'", null, eventData.get("time"), eventId, syscall);
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "Error processing finish syscall event with eventid '" + eventId + "'", e);
        }
    }

    private void handleIOEvent(SYSCALL syscall, Map<String, String> eventData, boolean isRead) {
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String time = eventData.get(AuditEventReader.TIME);
        String pid = eventData.get(AuditEventReader.PID);
        String bytesTransferred = eventData.get(AuditEventReader.EXIT);
        String saddr = eventData.get(AuditEventReader.SADDR);
        String fd = eventData.get(AuditEventReader.ARG0);
        String offset = null;
        ArtifactIdentifier artifactIdentifier = null;

        if (!isNetlinkSaddr(saddr)) {
            artifactIdentifier = getNetworkIdentifierFromFdAndOrSaddr(syscall, time, eventId, pid, fd, saddr);
            if (syscall == SYSCALL.PREAD || syscall == SYSCALL.PREADV || syscall == SYSCALL.PWRITE
                    || syscall == SYSCALL.PWRITEV) {
                offset = eventData.get(AuditEventReader.ARG3);
            }

            putIO(eventData, time, eventId, syscall, pid, fd, artifactIdentifier, bytesTransferred, offset, isRead);
        }
    }

    private void handleUnlink(Map<String, String> eventData, SYSCALL syscall) {
        // unlink() and unlinkat() receive the following messages(s):
        // - SYSCALL
        // - PATH with PARENT nametype
        // - PATH with DELETE nametype relative to CWD
        // - CWD
        // - EOE

        if (CONTROL) {
            String time = eventData.get(AuditEventReader.TIME);
            String eventId = eventData.get(AuditEventReader.EVENT_ID);
            String pid = eventData.get(AuditEventReader.PID);
            String cwd = eventData.get(AuditEventReader.CWD);

            String path = null;
            PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_DELETE);

            if (pathRecord == null) {
                log(Level.INFO, "PATH record with nametype DELETE missing", null, time, eventId, syscall);
                return;
            } else {
                path = pathRecord.getPath();
            }

            if (syscall == SYSCALL.UNLINK) {
                path = constructAbsolutePath(path, cwd, pid);
            } else if (syscall == SYSCALL.UNLINKAT) {
                path = constructPathSpecial(path, eventData.get(AuditEventReader.ARG0), cwd, pid, time, eventId,
                        syscall);
            } else {
                log(Level.INFO, "Unexpected syscall '" + syscall + "' in UNLINK handler", null, time, eventId,
                        syscall);
                return;
            }

            if (path == null) {
                log(Level.INFO, "Failed to build absolute path from log data", null, time, eventId, syscall);
                return;
            }

            ArtifactIdentifier artifactIdentifier = getArtifactIdentifierFromPathMode(path,
                    pathRecord.getPathType(), time, eventId, syscall);

            artifactManager.artifactPermissioned(artifactIdentifier, pathRecord.getPermissions());

            Process process = processManager.handleProcessFromSyscall(eventData);
            Artifact artifact = putArtifactFromSyscall(eventData, artifactIdentifier);
            WasGeneratedBy deletedEdge = new WasGeneratedBy(artifact, process);
            putEdge(deletedEdge, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
        }
    }

    private UnknownIdentifier addUnknownFd(String pid, String fd) {
        String fdTgid = processManager.getFdTgid(pid);
        UnknownIdentifier unknown = new UnknownIdentifier(fdTgid, fd);
        unknown.setOpenedForRead(null);
        artifactManager.artifactCreated(unknown);
        processManager.setFd(pid, fd, unknown);
        return unknown;
    }

    private void handleFcntl(Map<String, String> eventData, SYSCALL syscall) {
        // fcntl() receives the following message(s):
        // - SYSCALL
        // - EOE

        String exit = eventData.get(AuditEventReader.EXIT);
        if ("-1".equals(exit)) { // Failure check
            return;
        }

        String pid = eventData.get(AuditEventReader.PID);
        String fd = eventData.get(AuditEventReader.ARG0);
        String cmdString = eventData.get(AuditEventReader.ARG1);
        String flagsString = eventData.get(AuditEventReader.ARG2);

        int cmd = CommonFunctions.parseInt(cmdString, -1);
        int flags = CommonFunctions.parseInt(flagsString, -1);

        if (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC) {
            // In eventData, there should be a pid, a0 should be fd, and exit should be the new fd 
            handleDup(eventData, syscall);
        } else if (cmd == F_SETFL) {
            if ((flags & O_APPEND) == O_APPEND) {
                ArtifactIdentifier artifactIdentifier = processManager.getFd(pid, fd);
                if (artifactIdentifier == null) {
                    artifactIdentifier = addUnknownFd(pid, fd);
                }
                // Made file descriptor 'appendable', so set open for read to false so 
                // that the edge on close is a WGB edge and not a Used edge
                if (artifactIdentifier.wasOpenedForRead() != null) {
                    artifactIdentifier.setOpenedForRead(false);
                }
            }
        }
    }

    private void handleExit(Map<String, String> eventData, SYSCALL syscall) {
        // exit(), and exit_group() receives the following message(s):
        // - SYSCALL
        // - EOE
        processManager.handleExit(eventData, syscall, CONTROL);
    }

    private void handleMmap(Map<String, String> eventData, SYSCALL syscall) {
        // mmap() receive the following message(s):
        // - MMAP
        // - SYSCALL
        // - EOE

        if (!USE_MEMORY_SYSCALLS) {
            return;
        }

        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String time = eventData.get(AuditEventReader.TIME);
        String address = new BigInteger(eventData.get(AuditEventReader.EXIT)).toString(16); //convert to hexadecimal
        String length = new BigInteger(eventData.get(AuditEventReader.ARG1)).toString(16); //convert to hexadecimal
        String protection = new BigInteger(eventData.get(AuditEventReader.ARG2)).toString(16); //convert to hexadecimal

        int flags = CommonFunctions.parseInt(eventData.get(AuditEventReader.ARG3), 0);

        // Put Process, Memory artifact and WasGeneratedBy edge always but return if flag
        // is MAP_ANONYMOUS

        if (((flags & MAP_ANONYMOUS) == MAP_ANONYMOUS) && !ANONYMOUS_MMAP) {
            return;
        }

        Process process = processManager.handleProcessFromSyscall(eventData);
        String tgid = processManager.getMemoryTgid(pid);
        ArtifactIdentifier memoryArtifactIdentifier = new MemoryIdentifier(tgid, address, length);
        artifactManager.artifactVersioned(memoryArtifactIdentifier);
        Artifact memoryArtifact = putArtifactFromSyscall(eventData, memoryArtifactIdentifier);
        WasGeneratedBy wgbEdge = new WasGeneratedBy(memoryArtifact, process);
        wgbEdge.addAnnotation(OPMConstants.EDGE_PROTECTION, protection);
        putEdge(wgbEdge, getOperation(syscall, SYSCALL.WRITE), time, eventId, AUDIT_SYSCALL_SOURCE);

        if ((flags & MAP_ANONYMOUS) == MAP_ANONYMOUS) {
            return;
        } else {

            String fd = eventData.get(AuditEventReader.FD);

            if (fd == null) {
                log(Level.INFO, "FD record missing", null, time, eventId, syscall);
                return;
            }

            ArtifactIdentifier artifactIdentifier = processManager.getFd(pid, fd);

            if (artifactIdentifier == null) {
                artifactIdentifier = addUnknownFd(pid, fd);
            }

            Artifact artifact = putArtifactFromSyscall(eventData, artifactIdentifier);

            Used usedEdge = new Used(process, artifact);
            putEdge(usedEdge, getOperation(syscall, SYSCALL.READ), time, eventId, AUDIT_SYSCALL_SOURCE);

            WasDerivedFrom wdfEdge = new WasDerivedFrom(memoryArtifact, artifact);
            wdfEdge.addAnnotation(OPMConstants.EDGE_PID, pid);
            putEdge(wdfEdge, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
        }

    }

    private void handleMprotect(Map<String, String> eventData, SYSCALL syscall) {
        // mprotect() receive the following message(s):
        // - SYSCALL
        // - EOE

        if (!USE_MEMORY_SYSCALLS) {
            return;
        }

        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String time = eventData.get(AuditEventReader.TIME);
        String address = new BigInteger(eventData.get(AuditEventReader.ARG0)).toString(16);
        String length = new BigInteger(eventData.get(AuditEventReader.ARG1)).toString(16);
        String protection = new BigInteger(eventData.get(AuditEventReader.ARG2)).toString(16);

        String tgid = processManager.getMemoryTgid(pid);

        ArtifactIdentifier memoryIdentifier = new MemoryIdentifier(tgid, address, length);
        artifactManager.artifactVersioned(memoryIdentifier);
        Artifact memoryArtifact = putArtifactFromSyscall(eventData, memoryIdentifier);

        Process process = processManager.handleProcessFromSyscall(eventData);
        WasGeneratedBy edge = new WasGeneratedBy(memoryArtifact, process);
        edge.addAnnotation(OPMConstants.EDGE_PROTECTION, protection);
        putEdge(edge, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
    }

    private void handleExecve(Map<String, String> eventData, SYSCALL syscall) {
        // execve() receives the following message(s):
        // - SYSCALL
        // - EXECVE
        // - BPRM_FCAPS (ignored)
        // - CWD
        // - PATH
        // - PATH
        // - EOE

        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String cwd = eventData.get(AuditEventReader.CWD);
        String pid = eventData.get(AuditEventReader.PID);

        Process process = processManager.handleExecve(eventData, syscall);

        //add used edge to the paths in the event data. get the number of paths using the 'items' key and then iterate

        List<PathRecord> loadPathRecords = getPathsWithNametype(eventData, AuditEventReader.NAMETYPE_NORMAL);
        for (PathRecord loadPathRecord : loadPathRecords) {
            String path = constructAbsolutePath(loadPathRecord.getPath(), cwd, pid);
            if (path == null) {
                log(Level.INFO, "Missing PATH or CWD record", null, time, eventId, syscall);
                continue;
            }
            ArtifactIdentifier artifactIdentifier = getArtifactIdentifierFromPathMode(path,
                    loadPathRecord.getPathType(), time, eventId, syscall);
            artifactManager.artifactPermissioned(artifactIdentifier, loadPathRecord.getPermissions());
            Artifact usedArtifact = putArtifactFromSyscall(eventData, artifactIdentifier);
            Used usedEdge = new Used(process, usedArtifact);
            putEdge(usedEdge, getOperation(SYSCALL.LOAD), time, eventId, AUDIT_SYSCALL_SOURCE);
        }

        String processName = process.getAnnotation(OPMConstants.PROCESS_NAME);
        if (namesOfProcessesToIgnoreFromConfig.contains(processName)) {
            log(Level.INFO,
                    "'" + processName + "' (pid=" + pid
                            + ") process seen in execve and present in list of processes to ignore",
                    null, time, eventId, syscall);
        }
    }

    private void handleCreat(Map<String, String> eventData) {
        //creat() receives the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH of the parent with nametype=PARENT
        // - PATH of the created file with nametype=CREATE
        // - EOE

        //as mentioned in open syscall manpage       
        int defaultFlags = O_CREAT | O_WRONLY | O_TRUNC;

        //modify the eventData as expected by open syscall and call open syscall function
        eventData.put(AuditEventReader.ARG2, eventData.get(AuditEventReader.ARG1)); //set mode to argument 3 (in open) from 2 (in creat)
        eventData.put(AuditEventReader.ARG1, String.valueOf(defaultFlags)); //flags is argument 2 in open

        handleOpen(eventData, SYSCALL.CREATE); //TODO change to creat. kept as create to keep current CDM data consistent

    }

    /**
     * Get path from audit log. First see, if a path with CREATE nametype exists.
     * If yes then return that. If no then check if path with NORMAL nametype exists.
     * If yes then return that else return null.
     * 
     * @param eventData audit log event data as key values
     * @return path/null
     */
    private PathRecord getPathWithCreateOrNormalNametype(Map<String, String> eventData) {
        PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_CREATE);
        if (pathRecord != null) {
            return pathRecord;
        } else {
            pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_NORMAL);
            return pathRecord;
        }
    }

    private void handleOpenat(Map<String, String> eventData, SYSCALL syscall) {
        //openat() receives the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH
        // - PATH
        // - EOE

        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);

        PathRecord pathRecord = getPathWithCreateOrNormalNametype(eventData);

        if (pathRecord == null) {
            log(Level.INFO, "Missing PATH record", null, time, eventId, syscall);
            return;
        }

        String path = pathRecord.getPath();
        // If not absolute then only run the following logic according to the manpage
        if (!path.startsWith(File.separator)) {
            Long dirFd = CommonFunctions.parseLong(eventData.get(AuditEventReader.ARG0), -1L);

            //according to manpage if following true then use cwd if path not absolute, which is already handled by open
            if (dirFd != AT_FDCWD) { //checking if cwd needs to be replaced by dirFd's path
                String pid = eventData.get(AuditEventReader.PID);
                String dirFdString = String.valueOf(dirFd);
                //if null of if not file then cannot process it
                ArtifactIdentifier artifactIdentifier = processManager.getFd(pid, dirFdString);
                if (artifactIdentifier == null || !(artifactIdentifier instanceof PathIdentifier)) {
                    log(Level.INFO, "Expected 'dir' type fd: '" + artifactIdentifier + "'", null, time, eventId,
                            syscall);
                    return;
                } else { //is file
                    String dirPath = ((PathIdentifier) artifactIdentifier).getPath();
                    eventData.put(AuditEventReader.CWD, dirPath); //replace cwd with dirPath to make eventData compatible with open
                }
            }
        }

        //modify the eventData to match open syscall and then call it's function

        eventData.put(AuditEventReader.ARG0, eventData.get(AuditEventReader.ARG1)); //moved pathname address to first like in open
        eventData.put(AuditEventReader.ARG1, eventData.get(AuditEventReader.ARG2)); //moved flags to second like in open
        eventData.put(AuditEventReader.ARG2, eventData.get(AuditEventReader.ARG3)); //moved mode to third like in open

        handleOpen(eventData, syscall);
    }

    private void handleOpen(Map<String, String> eventData, SYSCALL syscall) {
        // open() receives the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH with nametype CREATE (file operated on) or NORMAL (file operated on) or PARENT (parent of file operated on) or DELETE (file operated on) or UNKNOWN (only when syscall fails)
        // - PATH with nametype CREATE or NORMAL or PARENT or DELETE or UNKNOWN
        // - EOE

        //three syscalls can come here: OPEN (for files and pipes), OPENAT (for files and pipes), CREAT (only for files)

        Long flags = CommonFunctions.parseLong(eventData.get(AuditEventReader.ARG1), 0L);
        Long modeArg = CommonFunctions.parseLong(eventData.get(AuditEventReader.ARG2), 0L);

        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String cwd = eventData.get(AuditEventReader.CWD);
        String fd = eventData.get(AuditEventReader.EXIT);
        String time = eventData.get(AuditEventReader.TIME);

        boolean isCreate = syscall == SYSCALL.CREATE || syscall == SYSCALL.CREAT; //TODO later on change only to CREAT only

        PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_CREATE);
        if (pathRecord == null) {
            isCreate = false;
            pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_NORMAL);
            if (pathRecord == null) {
                log(Level.INFO, "Missing PATH record", null, time, eventId, syscall);
                return;
            }
        } else {
            isCreate = true;
        }

        String path = pathRecord.getPath();
        path = constructAbsolutePath(path, cwd, pid);

        if (path == null) {
            log(Level.INFO, "Missing CWD or PATH record", null, time, eventId, syscall);
            return;
        }

        Process process = processManager.handleProcessFromSyscall(eventData);
        ArtifactIdentifier artifactIdentifier = getArtifactIdentifierFromPathMode(path, pathRecord.getPathType(),
                time, eventId, syscall);
        AbstractEdge edge = null;
        boolean openedForRead = false;

        String flagsArgs = "";

        flagsArgs += ((flags & O_WRONLY) == O_WRONLY) ? "O_WRONLY|" : "";
        flagsArgs += ((flags & O_RDWR) == O_RDWR) ? "O_RDWR|" : "";
        // if neither write only nor read write then must be read only
        if (((flags & O_WRONLY) != O_WRONLY) && ((flags & O_RDWR) != O_RDWR)) {
            // O_RDONLY is 0, so always true
            flagsArgs += ((flags & O_RDONLY) == O_RDONLY) ? "O_RDONLY|" : "";
        }

        flagsArgs += ((flags & O_APPEND) == O_APPEND) ? "O_APPEND|" : "";
        flagsArgs += ((flags & O_TRUNC) == O_TRUNC) ? "O_TRUNC|" : "";
        flagsArgs += ((flags & O_CREAT) == O_CREAT) ? "O_CREAT|" : "";

        if (!flagsArgs.isEmpty()) {
            flagsArgs = flagsArgs.substring(0, flagsArgs.length() - 1);
        }

        if (isCreate) {
            artifactManager.artifactCreated(artifactIdentifier);
            syscall = SYSCALL.CREATE;
        }

        if ((flags & O_WRONLY) == O_WRONLY || (flags & O_RDWR) == O_RDWR || (flags & O_APPEND) == O_APPEND
                || (flags & O_TRUNC) == O_TRUNC) {
            if (!isCreate) {
                // If artifact not created
                artifactManager.artifactVersioned(artifactIdentifier);
            }
            artifactManager.artifactPermissioned(artifactIdentifier, pathRecord.getPermissions());
            Artifact vertex = putArtifactFromSyscall(eventData, artifactIdentifier);
            edge = new WasGeneratedBy(vertex, process);
            openedForRead = false;
        } else if ((flags & O_RDONLY) == O_RDONLY) {
            artifactManager.artifactPermissioned(artifactIdentifier, pathRecord.getPermissions());
            if (isCreate) {
                Artifact vertex = putArtifactFromSyscall(eventData, artifactIdentifier);
                edge = new WasGeneratedBy(vertex, process);
            } else {
                Artifact vertex = putArtifactFromSyscall(eventData, artifactIdentifier);
                edge = new Used(process, vertex);
            }
            openedForRead = true;
        } else {
            log(Level.INFO, "Unhandled value of FLAGS argument '" + flags + "'", null, time, eventId, syscall);
            return;
        }

        if (edge != null) {
            edge.addAnnotation(OPMConstants.EDGE_MODE, Long.toOctalString(modeArg));
            if (!flagsArgs.isEmpty()) {
                edge.addAnnotation(OPMConstants.EDGE_FLAGS, flagsArgs);
            }
            //everything happened successfully. add it to descriptors
            processManager.setFd(pid, fd, artifactIdentifier, openedForRead);

            putEdge(edge, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
        }
    }

    private void handleClose(Map<String, String> eventData) {
        // close() receives the following message(s):
        // - SYSCALL
        // - EOE
        String pid = eventData.get(AuditEventReader.PID);
        String fd = String.valueOf(CommonFunctions.parseLong(eventData.get(AuditEventReader.ARG0), -1L));
        ArtifactIdentifier closedArtifactIdentifier = processManager.removeFd(pid, fd);

        if (CONTROL) {
            SYSCALL syscall = SYSCALL.CLOSE;
            String time = eventData.get(AuditEventReader.TIME);
            String eventId = eventData.get(AuditEventReader.EVENT_ID);
            if (closedArtifactIdentifier != null) {
                Process process = processManager.handleProcessFromSyscall(eventData);
                AbstractEdge edge = null;
                Boolean wasOpenedForRead = closedArtifactIdentifier.wasOpenedForRead();
                if (wasOpenedForRead == null) {
                    // Not drawing an edge because didn't seen an open or was a 'bound' fd
                } else {
                    Artifact artifact = putArtifactFromSyscall(eventData, closedArtifactIdentifier);
                    if (wasOpenedForRead) {
                        edge = new Used(process, artifact);
                    } else {
                        edge = new WasGeneratedBy(artifact, process);
                    }
                }
                if (edge != null) {
                    putEdge(edge, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
                }
                //after everything done increment epoch is udp socket
                if (isUdp(closedArtifactIdentifier)) {
                    artifactManager.artifactCreated(closedArtifactIdentifier);
                }
            } else {
                log(Level.INFO, "No FD with number '" + fd + "' for pid '" + pid + "'", null, time, eventId,
                        syscall);
            }
        }

        //there is an option to either handle epochs 1) when artifact opened/created or 2) when artifacts deleted/closed.
        //handling epoch at opened/created in all cases
    }

    private boolean isUdp(ArtifactIdentifier identifier) {
        if (identifier != null && identifier.getClass().equals(NetworkSocketIdentifier.class)) {
            if (PROTOCOL_NAME_UDP.equals(((NetworkSocketIdentifier) identifier).getProtocol())) {
                return true;
            }
        }
        return false;
    }

    private void handleTruncate(Map<String, String> eventData, SYSCALL syscall) {
        // write() receives the following message(s):
        // - SYSCALL
        // - EOE
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String size = eventData.get(AuditEventReader.ARG1);

        ArtifactIdentifier artifactIdentifier = null;
        String permissions = null;

        if (syscall == SYSCALL.TRUNCATE) {
            PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_NORMAL);
            if (pathRecord == null) {
                log(Level.INFO, "Missing PATH record", null, time, eventId, syscall);
                return;
            }
            String path = pathRecord.getPath();
            path = constructAbsolutePath(path, eventData.get(AuditEventReader.CWD), pid);
            if (path == null) {
                log(Level.INFO, "Missing PATH or CWD record", null, time, eventId, syscall);
                return;
            }
            artifactIdentifier = getArtifactIdentifierFromPathMode(path, pathRecord.getPathType(), time, eventId,
                    syscall);
            permissions = pathRecord.getPermissions();
            if (artifactIdentifier != null) {
                artifactManager.artifactVersioned(artifactIdentifier);
                artifactManager.artifactPermissioned(artifactIdentifier, permissions);
            }
        } else if (syscall == SYSCALL.FTRUNCATE) {
            String fd = eventData.get(AuditEventReader.ARG0);
            artifactIdentifier = processManager.getFd(pid, fd);
            if (artifactIdentifier == null) {
                artifactIdentifier = addUnknownFd(pid, fd);
            }
            artifactManager.artifactVersioned(artifactIdentifier);
        }

        if (artifactIdentifier != null) {
            Process process = processManager.handleProcessFromSyscall(eventData);
            Artifact vertex = putArtifactFromSyscall(eventData, artifactIdentifier);
            WasGeneratedBy wgb = new WasGeneratedBy(vertex, process);
            if (size != null) {
                wgb.addAnnotation(OPMConstants.EDGE_SIZE, size);
            }
            putEdge(wgb, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
        } else {
            log(Level.INFO, "Failed to find artifact identifier from the event data", null, time, eventId, syscall);
        }
    }

    private void handleDup(Map<String, String> eventData, SYSCALL syscall) {
        // dup(), dup2(), and dup3() receive the following message(s):
        // - SYSCALL
        // - EOE
        String pid = eventData.get(AuditEventReader.PID);

        String fd = eventData.get(AuditEventReader.ARG0);
        String newFD = eventData.get(AuditEventReader.EXIT); //new fd returned in all: dup, dup2, dup3

        if (!fd.equals(newFD)) { //if both fds same then it succeeds in case of dup2 and it does nothing so do nothing here too
            ArtifactIdentifier artifactIdentifier = processManager.getFd(pid, fd);
            if (artifactIdentifier == null) {
                artifactIdentifier = addUnknownFd(pid, fd);
            }
            processManager.setFd(pid, newFD, artifactIdentifier);
        }
    }

    private void handleVmsplice(Map<String, String> eventData, SYSCALL syscall) {
        // vmsplice() receives the following messages:
        // - SYSCALL
        // - EOE
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);

        String fdOut = eventData.get(AuditEventReader.ARG0);
        String bytes = eventData.get(AuditEventReader.EXIT);

        if (!"0".equals(bytes)) {
            ArtifactIdentifier fdOutIdentifier = processManager.getFd(pid, fdOut);
            fdOutIdentifier = fdOutIdentifier == null ? addUnknownFd(pid, fdOut) : fdOutIdentifier;

            Process process = processManager.handleProcessFromSyscall(eventData);
            artifactManager.artifactVersioned(fdOutIdentifier);
            Artifact fdOutArtifact = putArtifactFromSyscall(eventData, fdOutIdentifier);

            WasGeneratedBy processToWrittenArtifact = new WasGeneratedBy(fdOutArtifact, process);
            processToWrittenArtifact.addAnnotation(OPMConstants.EDGE_SIZE, bytes);
            putEdge(processToWrittenArtifact, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
        }
    }

    private void putTeeSplice(Map<String, String> eventData, SYSCALL syscall, String time, String eventId,
            String fdIn, String fdOut, String pid, String bytes) {
        ArtifactIdentifier fdInIdentifier = processManager.getFd(pid, fdIn);
        ArtifactIdentifier fdOutIdentifier = processManager.getFd(pid, fdOut);

        // Use unknown if missing fds
        fdInIdentifier = fdInIdentifier == null ? addUnknownFd(pid, fdIn) : fdInIdentifier;
        fdOutIdentifier = fdOutIdentifier == null ? addUnknownFd(pid, fdOut) : fdOutIdentifier;

        Process process = processManager.handleProcessFromSyscall(eventData);
        Artifact fdInArtifact = putArtifactFromSyscall(eventData, fdInIdentifier);
        artifactManager.artifactVersioned(fdOutIdentifier);
        Artifact fdOutArtifact = putArtifactFromSyscall(eventData, fdOutIdentifier);

        Used processToReadArtifact = new Used(process, fdInArtifact);
        processToReadArtifact.addAnnotation(OPMConstants.EDGE_SIZE, bytes);
        putEdge(processToReadArtifact, getOperation(syscall, SYSCALL.READ), time, eventId, AUDIT_SYSCALL_SOURCE);

        WasGeneratedBy processToWrittenArtifact = new WasGeneratedBy(fdOutArtifact, process);
        processToWrittenArtifact.addAnnotation(OPMConstants.EDGE_SIZE, bytes);
        putEdge(processToWrittenArtifact, getOperation(syscall, SYSCALL.WRITE), time, eventId,
                AUDIT_SYSCALL_SOURCE);

        WasDerivedFrom writtenToReadArtifact = new WasDerivedFrom(fdOutArtifact, fdInArtifact);
        writtenToReadArtifact.addAnnotation(OPMConstants.EDGE_PID, pid);
        putEdge(writtenToReadArtifact, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
    }

    private void handleTeeSplice(Map<String, String> eventData, SYSCALL syscall) {
        // tee(), and splice() receive the following messages:
        // - SYSCALL
        // - EOE
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);

        String fdIn = eventData.get(AuditEventReader.ARG0), fdOut = null;
        String bytes = eventData.get(AuditEventReader.EXIT);

        if (syscall == SYSCALL.TEE) {
            fdOut = eventData.get(AuditEventReader.ARG1);
        } else if (syscall == SYSCALL.SPLICE) {
            fdOut = eventData.get(AuditEventReader.ARG2);
        } else {
            log(Level.WARNING, "Unexpected syscall: " + syscall, null, time, eventId, syscall);
            return;
        }

        // If fd out set and bytes transferred is not zero
        if (fdOut != null && !"0".equals(bytes)) {
            putTeeSplice(eventData, syscall, time, eventId, fdIn, fdOut, pid, bytes);
        }
    }

    private void handleInitModule(Map<String, String> eventData, SYSCALL syscall) {
        // init_module(), and finit_module receive the following messages:
        // - SYSCALL
        // - PATH [OPTIONAL] why? not the path of the kernel module
        // - EOE
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);

        ArtifactIdentifier moduleIdentifier = null;
        if (syscall == SYSCALL.INIT_MODULE) {
            String memoryAddress = new BigInteger(eventData.get(AuditEventReader.ARG0)).toString(16); //convert to hexadecimal
            String memorySize = new BigInteger(eventData.get(AuditEventReader.ARG1)).toString(16); //convert to hexadecimal
            String tgid = processManager.getMemoryTgid(pid);
            moduleIdentifier = new MemoryIdentifier(tgid, memoryAddress, memorySize);
        } else if (syscall == SYSCALL.FINIT_MODULE) {
            String fd = eventData.get(AuditEventReader.ARG0);
            moduleIdentifier = processManager.getFd(pid, fd);
            moduleIdentifier = moduleIdentifier == null ? addUnknownFd(pid, fd) : moduleIdentifier;
        } else {
            log(Level.WARNING, "Unexpected syscall in (f)init_module handler", null, time, eventId, syscall);
        }

        if (moduleIdentifier != null) {
            Process process = processManager.handleProcessFromSyscall(eventData);
            Artifact module = putArtifactFromSyscall(eventData, moduleIdentifier);
            Used loadedModule = new Used(process, module);
            putEdge(loadedModule, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
        }
    }

    private void handleSetuidAndSetgid(Map<String, String> eventData, SYSCALL syscall) {
        // setuid(), setreuid(), setresuid(), setfsuid(), 
        // setgid(), setregid(), setresgid(), and setfsgid() receive the following message(s):
        // - SYSCALL
        // - EOE

        processManager.handleSetuidSetgid(eventData, syscall);
    }

    /**
     * Removes special path symbols '..' and '.'
     * 
     * @param path file system path
     * @return file system path or null if not a valid path
     */
    private String removeSpecialPathSymbols(String path) {
        if (path == null) {
            return null;
        }
        String finalPath = "";
        path = path.trim();
        if (path.isEmpty()) {
            return null;
        } else {
            String[] parts = path.split(File.separator);
            for (int a = parts.length - 1; a > -1; a--) {
                if (parts[a].equals("..")) {
                    a--;
                    continue;
                } else if (parts[a].equals(".")) {
                    continue;
                } else if (parts[a].trim().isEmpty()) {
                    /*
                     * Cases: 
                     * 1) Start of path (/path/to/something)
                     * 2) End of path (path/to/something/)
                     * 3) Double path separator (/path//to////something) 
                     */
                    // Continue
                } else {
                    finalPath = parts[a] + File.separator + finalPath;
                }
            }
            // Adding the slash in the end if the given path had a slash in the end
            if (!path.endsWith(File.separator) && finalPath.endsWith(File.separator)) {
                finalPath = finalPath.substring(0, finalPath.length() - 1);
            }
            // Adding the slash in the beginning if the given path had a slash in the beginning
            if (path.startsWith(File.separator) && !finalPath.startsWith(File.separator)) {
                finalPath = File.separator + finalPath;
            }
            return finalPath;
        }
    }

    /**
     * Resolves symbolic links in case reading from an audit log
     * 
     * Add other cases later, so far can only think of /proc/self
     * 
     * @param path path to resolve
     * @param pid process id
     * @return resolved path or null if invalid arguments
     */
    private String resolvePathStatically(String path, String pid) {
        if (path == null) {
            return null;
        }
        if (path.startsWith("/proc/self")) {
            if (pid == null) {
                return path;
            } else {
                StringBuilder string = new StringBuilder();
                string.append(path);
                string.delete(6, 10); // index of self in /proc/self is 6 and ends at 10
                string.insert(6, pid); // replacing with pid
                return string.toString();
            }
        } else { // No symbolic link to replace
            return path;
        }
    }

    /**
     * Constructs path by concatenating the two paths, then removes symbols such as '.' and 
     * '..' and then finally resolves symbolic links like /proc/self.
     * 
     * Null returned if unable to construct absolute path
     * 
     * @param path path relative to parentPath
     * @param parentPath parent path of the path
     * @return constructed path or null
     */
    private String constructAbsolutePath(String path, String parentPath, String pid) {
        path = concatenatePaths(path, parentPath);
        if (path != null) {
            path = removeSpecialPathSymbols(path);
            if (path != null) {
                path = resolvePathStatically(path, pid);
                return path;
            }
        }
        return null;
    }

    /**
     * Concatenates given paths using the following logic:
     * 
     * If path is absolute then return path
     * If path is not absolute then concatenates parentPath with path and returns that
     * 
     * @param path path relative to parentPath (can be absolute)
     * @param parentPath parent path for the given path
     * @return concatenated path or null
     */
    private String concatenatePaths(String path, String parentPath) {
        if (path != null) {
            path = path.trim();
            if (path.isEmpty()) {
                return null;
            } else {
                if (path.startsWith(File.separator)) { //is absolute
                    return path;
                } else {

                    if (parentPath != null) {
                        parentPath = parentPath.trim();
                        if (parentPath.isEmpty() || !parentPath.startsWith(File.separator)) {
                            return null;
                        } else {
                            return parentPath + File.separator + path;
                        }
                    }
                }
            }
        }
        return null;
    }

    private void handleRename(Map<String, String> eventData, SYSCALL syscall) {
        // rename(), renameat(), and renameat2() receive the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH 0
        // - PATH 1
        // - PATH 2 with nametype DELETE
        // - PATH 3 with nametype DELETE or CREATE
        // - [OPTIONAL] PATH 4 with nametype CREATE
        // - EOE
        // Resolving paths relative to CWD if not absolute

        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String cwd = eventData.get(AuditEventReader.CWD);
        String oldFilePath = eventData.get(AuditEventReader.PATH_PREFIX + "2");
        String oldFilePathModeStr = eventData.get(AuditEventReader.MODE_PREFIX + "2");
        //if file renamed to already existed then path4 else path3. Both are same so just getting whichever exists
        String newFilePath = eventData.get(AuditEventReader.PATH_PREFIX + "4") == null
                ? eventData.get(AuditEventReader.PATH_PREFIX + "3")
                : eventData.get(AuditEventReader.PATH_PREFIX + "4");
        String newFilePathModeStr = eventData.get(AuditEventReader.MODE_PREFIX + "4") == null
                ? eventData.get(AuditEventReader.MODE_PREFIX + "3")
                : eventData.get(AuditEventReader.MODE_PREFIX + "4");

        if (syscall == SYSCALL.RENAME) {
            oldFilePath = constructAbsolutePath(oldFilePath, cwd, pid);
            newFilePath = constructAbsolutePath(newFilePath, cwd, pid);
        } else if (syscall == SYSCALL.RENAMEAT) {
            oldFilePath = constructPathSpecial(oldFilePath, eventData.get(AuditEventReader.ARG0), cwd, pid, time,
                    eventId, syscall);
            newFilePath = constructPathSpecial(newFilePath, eventData.get(AuditEventReader.ARG2), cwd, pid, time,
                    eventId, syscall);
        } else {
            log(Level.WARNING, "Unexpected syscall '" + syscall + "' in RENAME handler", null, time, eventId,
                    syscall);
            return;
        }

        if (oldFilePath == null || newFilePath == null) {
            log(Level.INFO, "Failed to create path(s)", null, time, eventId, syscall);
            return;
        }

        handleSpecialSyscalls(eventData, syscall, oldFilePath, newFilePath, oldFilePathModeStr, newFilePathModeStr);
    }

    private void handleMknodat(Map<String, String> eventData) {
        //mknodat() receives the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH of the created file with nametype=CREATE
        // - EOE

        //first argument is the fd of the directory to create file in. if the directory fd is AT_FDCWD then use cwd

        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        PathRecord pathRecord = getPathWithCreateOrNormalNametype(eventData);

        if (pathRecord == null) {
            log(Level.INFO, "Missing PATH record", null, time, eventId, SYSCALL.MKNODAT);
            return;
        }

        String path = pathRecord.getPath();

        // If not absolute then only run the following logic according to the manpage
        if (!path.startsWith(File.separator)) {
            String fd = eventData.get(AuditEventReader.ARG0);
            Long fdLong = CommonFunctions.parseLong(fd, null);

            ArtifactIdentifier artifactIdentifier = null;

            if (fdLong != AT_FDCWD) {
                artifactIdentifier = processManager.getFd(pid, fd);
                if (artifactIdentifier == null) {
                    log(Level.INFO, "No FD '" + fd + "' for pid '" + pid + "'", null, time, eventId,
                            SYSCALL.MKNODAT);
                    return;
                } else if (artifactIdentifier instanceof PathIdentifier) {
                    String directoryPath = ((PathIdentifier) artifactIdentifier).getPath();
                    //update cwd to directoryPath and call handleMknod. the file created path is always relative in this syscall
                    eventData.put(AuditEventReader.CWD, directoryPath);
                } else {
                    log(Level.INFO, "FD '" + fd + "' for pid '" + pid + "' is of type '"
                            + artifactIdentifier.getClass() + "' but only file allowed", null, time, eventId,
                            SYSCALL.MKNODAT);
                    return;
                }
            }
        }

        //replace the second argument (which is mode in mknod) with the third (which is mode in mknodat)
        eventData.put(AuditEventReader.ARG1, eventData.get(AuditEventReader.ARG2));
        handleMknod(eventData, SYSCALL.MKNODAT);
    }

    private void handleMknod(Map<String, String> eventData, SYSCALL syscall) {
        //mknod() receives the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH of the parent with nametype=PARENT
        // - PATH of the created file with nametype=CREATE
        // - EOE

        String modeString = eventData.get(AuditEventReader.ARG1);
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);

        PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_CREATE);

        if (pathRecord == null) {
            log(Level.INFO, "PATH record missing", null, time, eventId, syscall);
            return;
        }

        String path = pathRecord.getPath();
        path = constructAbsolutePath(path, eventData.get(AuditEventReader.CWD),
                eventData.get(AuditEventReader.PID));

        if (path == null) {
            log(Level.INFO, "Missing PATH or CWD record", null, time, eventId, syscall);
            return;
        }

        ArtifactIdentifier artifactIdentifier = getArtifactIdentifierFromPathMode(path, pathRecord.getPathType(),
                time, eventId, syscall);

        if (artifactIdentifier == null) {
            log(Level.INFO, "Unsupported mode for mknod '" + modeString + "'", null, time, eventId, syscall);
            return;
        }

        if (artifactIdentifier != null) {
            artifactManager.artifactCreated(artifactIdentifier);
        }
    }

    private void handleLinkSymlink(Map<String, String> eventData, SYSCALL syscall) {
        // link(), symlink(), linkat(), and symlinkat() receive the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH 0 is path of <src> relative to <cwd>
        // - PATH 1 is directory of <dst>
        // - PATH 2 is path of <dst> relative to <cwd>
        // - EOE

        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String cwd = eventData.get(AuditEventReader.CWD);
        String srcPath = eventData.get(AuditEventReader.PATH_PREFIX + "0");
        String srcPathModeStr = eventData.get(AuditEventReader.MODE_PREFIX + "0");
        String dstPath = eventData.get(AuditEventReader.PATH_PREFIX + "2");
        String dstPathModeStr = eventData.get(AuditEventReader.MODE_PREFIX + "2");
        String time = eventData.get(AuditEventReader.TIME);

        if (syscall == SYSCALL.LINK || syscall == SYSCALL.SYMLINK) {
            srcPath = constructAbsolutePath(srcPath, cwd, pid);
            dstPath = constructAbsolutePath(dstPath, cwd, pid);
        } else if (syscall == SYSCALL.LINKAT) {
            srcPath = constructPathSpecial(srcPath, eventData.get(AuditEventReader.ARG0), cwd, pid, time, eventId,
                    syscall);
            dstPath = constructPathSpecial(dstPath, eventData.get(AuditEventReader.ARG2), cwd, pid, time, eventId,
                    syscall);
        } else if (syscall == SYSCALL.SYMLINKAT) {
            srcPath = constructAbsolutePath(srcPath, cwd, pid);
            dstPath = constructPathSpecial(dstPath, eventData.get(AuditEventReader.ARG1), cwd, pid, time, eventId,
                    syscall);
        } else {
            log(Level.WARNING, "Unexpected syscall '" + syscall + "' in LINK SYMLINK handler", null, time, eventId,
                    syscall);
            return;
        }

        if (srcPath == null || dstPath == null) {
            log(Level.INFO, "Failed to create path(s)", null, time, eventId, syscall);
            return;
        }

        handleSpecialSyscalls(eventData, syscall, srcPath, dstPath, srcPathModeStr, dstPathModeStr);
    }

    /**
     * Creates OPM vertices and edges for link, symlink, linkat, symlinkat, rename, renameat syscalls.
     * 
     * Steps:
     * 1) Gets the valid source artifact type (can be either file, named pipe, unix socket)
     * 2) Creates a valid destination artifact type with the same type as the source artifact type
     * 2.a) Marks new epoch for the destination file
     * 3) Adds the process vertex, artifact vertices, and edges (Process to srcArtifact [Used], Process to dstArtifact [WasGeneratedBy], dstArtifact to oldArtifact [WasDerivedFrom])
     * to reporter's internal buffer.
     * 
     * @param eventData event data as gotten from the audit log
     * @param syscall syscall being handled
     * @param srcPath path of the file being linked
     * @param dstPath path of the link
     */
    private void handleSpecialSyscalls(Map<String, String> eventData, SYSCALL syscall, String srcPath,
            String dstPath, String srcPathMode, String dstPathMode) {

        if (eventData == null || syscall == null || srcPath == null || dstPath == null) {
            logger.log(Level.INFO, "Missing arguments. srcPath:{0}, dstPath:{1}, syscall:{2}, eventData:{3}",
                    new Object[] { srcPath, dstPath, syscall, eventData });
            return;
        }

        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String time = eventData.get(AuditEventReader.TIME);
        String pid = eventData.get(AuditEventReader.PID);

        if (eventId == null || time == null || pid == null) {
            log(Level.INFO, "Missing keys in event data. pid:" + pid, null, time, eventId, syscall);
            return;
        }

        ArtifactIdentifier srcArtifactIdentifier = getArtifactIdentifierFromPathMode(srcPath,
                PathRecord.parsePathType(srcPathMode), time, eventId, syscall);
        ArtifactIdentifier dstArtifactIdentifier = getArtifactIdentifierFromPathMode(dstPath,
                PathRecord.parsePathType(dstPathMode), time, eventId, syscall);

        if (srcArtifactIdentifier == null || dstArtifactIdentifier == null) {
            logger.log(Level.WARNING, "Missing path objects. Failed to " + "process syscall {0} for event id {1}",
                    new Object[] { syscall, eventId });
            return;
        }

        Process process = processManager.handleProcessFromSyscall(eventData);

        //destination is new so mark epoch
        artifactManager.artifactCreated(dstArtifactIdentifier);

        artifactManager.artifactPermissioned(srcArtifactIdentifier, PathRecord.parsePermissions(srcPathMode));
        Artifact srcVertex = putArtifactFromSyscall(eventData, srcArtifactIdentifier);
        Used used = new Used(process, srcVertex);
        putEdge(used, getOperation(syscall, SYSCALL.READ), time, eventId, AUDIT_SYSCALL_SOURCE);

        artifactManager.artifactPermissioned(dstArtifactIdentifier, PathRecord.parsePermissions(dstPathMode));
        Artifact dstVertex = putArtifactFromSyscall(eventData, dstArtifactIdentifier);
        WasGeneratedBy wgb = new WasGeneratedBy(dstVertex, process);
        putEdge(wgb, getOperation(syscall, SYSCALL.WRITE), time, eventId, AUDIT_SYSCALL_SOURCE);

        WasDerivedFrom wdf = new WasDerivedFrom(dstVertex, srcVertex);
        wdf.addAnnotation(OPMConstants.EDGE_PID, pid);
        putEdge(wdf, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
    }

    /**
     * Returns the corresponding artifact identifier type based on the pathModeString value
     * 
     * Returns null if an unexpected mode value found
     * 
     * @param path file system path
     * @param pathType mode value for the path in the audit log
     * @return ArtifactIdentifier subclass or null
     */
    private ArtifactIdentifier getArtifactIdentifierFromPathMode(String path, int pathType, String time,
            String eventId, SYSCALL syscall) {
        int type = pathType & S_IFMT;
        switch (type) {
        case S_IFREG:
            return new FileIdentifier(path);
        case S_IFDIR:
            return new DirectoryIdentifier(path);
        case S_IFCHR:
            return new CharacterDeviceIdentifier(path);
        case S_IFBLK:
            return new BlockDeviceIdentifier(path);
        case S_IFLNK:
            return new LinkIdentifier(path);
        case S_IFIFO:
            return new NamedPipeIdentifier(path);
        case S_IFSOCK:
            return new UnixSocketIdentifier(path);
        default:
            log(Level.INFO, "Unknown file type: " + pathType + ". Defaulted to 'File'", null, time, eventId,
                    syscall);
            return new FileIdentifier(path);
        }
    }

    private void handleChmod(Map<String, String> eventData, SYSCALL syscall) {
        // chmod(), fchmod(), and fchmodat() receive the following message(s):
        // - SYSCALL
        // - CWD
        // - PATH
        // - EOE
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String time = eventData.get(AuditEventReader.TIME);
        String pid = eventData.get(AuditEventReader.PID);
        String modeArgument = null;
        // if syscall is chmod, then path is <path0> relative to <cwd>
        // if syscall is fchmod, look up file descriptor which is <a0>
        // if syscall is fchmodat, loop up the directory fd and build a path using the path in the audit log
        ArtifactIdentifier artifactIdentifier = null;
        if (syscall == SYSCALL.CHMOD) {
            PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_NORMAL);
            if (pathRecord == null) {
                log(Level.INFO, "Missing PATH record", null, time, eventId, syscall);
                return;
            }
            String path = pathRecord.getPath();
            path = constructAbsolutePath(path, eventData.get(AuditEventReader.CWD), pid);
            if (path == null) {
                log(Level.INFO, "Missing PATH or CWD records", null, time, eventId, syscall);
                return;
            }
            artifactIdentifier = getArtifactIdentifierFromPathMode(path, pathRecord.getPathType(), time, eventId,
                    syscall);
            modeArgument = eventData.get(AuditEventReader.ARG1);
        } else if (syscall == SYSCALL.FCHMOD) {
            String fd = eventData.get(AuditEventReader.ARG0);
            artifactIdentifier = processManager.getFd(pid, fd);
            if (artifactIdentifier == null) {
                artifactIdentifier = addUnknownFd(pid, fd);
            }
            modeArgument = eventData.get(AuditEventReader.ARG1);
        } else if (syscall == SYSCALL.FCHMODAT) {
            PathRecord pathRecord = getFirstPathWithNametype(eventData, AuditEventReader.NAMETYPE_NORMAL);
            if (pathRecord == null) {
                log(Level.INFO, "Missing PATH record", null, time, eventId, syscall);
                return;
            }
            String path = pathRecord.getPath();
            path = constructPathSpecial(path, eventData.get(AuditEventReader.ARG0),
                    eventData.get(AuditEventReader.CWD), pid, time, eventId, syscall);
            if (path == null) {
                log(Level.INFO, "Failed to create path", null, time, eventId, syscall);
                return;
            }
            artifactIdentifier = getArtifactIdentifierFromPathMode(path, pathRecord.getPathType(), time, eventId,
                    syscall);
            modeArgument = eventData.get(AuditEventReader.ARG2);
        } else {
            log(Level.INFO, "Unexpected syscall '" + syscall + "' in CHMOD handler", null, time, eventId, syscall);
            return;
        }

        if (artifactIdentifier == null) {
            logger.log(Level.WARNING,
                    "Failed to process syscall=" + syscall + " because of missing artifact identifier");
            return;
        }

        String mode = new BigInteger(modeArgument).toString(8);
        String permissions = PathRecord.parsePermissions(mode);

        artifactManager.artifactVersioned(artifactIdentifier);
        artifactManager.artifactPermissioned(artifactIdentifier, permissions);

        Process process = processManager.handleProcessFromSyscall(eventData);
        Artifact vertex = putArtifactFromSyscall(eventData, artifactIdentifier);
        WasGeneratedBy wgb = new WasGeneratedBy(vertex, process);
        wgb.addAnnotation(OPMConstants.EDGE_MODE, mode);
        putEdge(wgb, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);
    }

    private void handleSocketPair(Map<String, String> eventData, SYSCALL syscall) {
        // socketpair() receives the following message(s):
        // - SYSCALL
        // - FD_PAIR
        // - EOE
        String pid = eventData.get(AuditEventReader.PID);
        String fd0 = eventData.get(AuditEventReader.FD0);
        String fd1 = eventData.get(AuditEventReader.FD1);
        String domainString = eventData.get(AuditEventReader.ARG0);
        String sockTypeString = eventData.get(AuditEventReader.ARG1);
        String fdTgid = processManager.getFdTgid(pid);

        int domain = CommonFunctions.parseInt(domainString, null); // Let exception be thrown
        int sockType = CommonFunctions.parseInt(sockTypeString, null);

        String protocol = getProtocolNameBySockType(sockType);

        ArtifactIdentifier fdIdentifier = null;

        if (domain == AF_INET || domain == AF_INET6 || domain == PF_INET || domain == PF_INET6) {
            fdIdentifier = new UnnamedNetworkSocketPairIdentifier(fdTgid, fd0, fd1, protocol);
        } else if (domain == AF_LOCAL || domain == AF_UNIX || domain == PF_LOCAL || domain == PF_UNIX) {
            fdIdentifier = new UnnamedUnixSocketPairIdentifier(fdTgid, fd0, fd1);
        } else {
            // Unsupported domain
        }

        if (fdIdentifier != null) {
            processManager.setFd(pid, fd0, fdIdentifier, false);
            processManager.setFd(pid, fd1, fdIdentifier, false);

            artifactManager.artifactCreated(fdIdentifier);
        }
    }

    private void handlePipe(Map<String, String> eventData, SYSCALL syscall) {
        // pipe() receives the following message(s):
        // - SYSCALL
        // - FD_PAIR
        // - EOE
        String pid = eventData.get(AuditEventReader.PID);
        String fdTgid = processManager.getFdTgid(pid);
        String fd0 = eventData.get(AuditEventReader.FD0);
        String fd1 = eventData.get(AuditEventReader.FD1);
        ArtifactIdentifier readPipeIdentifier = new UnnamedPipeIdentifier(fdTgid, fd0, fd1);
        ArtifactIdentifier writePipeIdentifier = new UnnamedPipeIdentifier(fdTgid, fd0, fd1);
        processManager.setFd(pid, fd0, readPipeIdentifier, true);
        processManager.setFd(pid, fd1, writePipeIdentifier, false);

        // Since both (read, and write) pipe identifiers are the same, only need to mark epoch on one.
        artifactManager.artifactCreated(readPipeIdentifier);
    }

    public static Integer getProtocolNumber(String protocolName) {
        if (PROTOCOL_NAME_UDP.equals(protocolName)) {
            return 17;
        } else if (PROTOCOL_NAME_TCP.equals(protocolName)) {
            return 6;
        } else {
            return null;
        }
    }

    private static String getProtocolName(Integer protocolNumber) {
        if (protocolNumber != null) {
            if (protocolNumber == 17) {
                return PROTOCOL_NAME_UDP;
            } else if (protocolNumber == 6) {
                return PROTOCOL_NAME_TCP;
            }
        }
        return null;
    }

    private void handleNetfilterPacketEvent(Map<String, String> eventData) {
        //      Refer to the following link for protocol numbers
        //       http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String protocolNumberString = eventData.get(AuditEventReader.PROTO);
        String hook = eventData.get(AuditEventReader.HOOK);//hook=1 (input), hook=3 (output)

        String localAddress = null, localPort = null, remoteAddress = null, remotePort = null;

        if (AuditEventReader.HOOK_INPUT.equals(hook)) {
            localAddress = eventData.get(AuditEventReader.DADDR);
            localPort = eventData.get(AuditEventReader.DPORT);
            remoteAddress = eventData.get(AuditEventReader.SADDR);
            remotePort = eventData.get(AuditEventReader.SPORT);
        } else if (AuditEventReader.HOOK_OUTPUT.equals(hook)) {
            localAddress = eventData.get(AuditEventReader.SADDR);
            localPort = eventData.get(AuditEventReader.SPORT);
            remoteAddress = eventData.get(AuditEventReader.DADDR);
            remotePort = eventData.get(AuditEventReader.DPORT);
        } else {
            logger.log(Level.INFO, "Unexpected hook value: " + hook);
            return;
        }

        Integer protocolNumber = CommonFunctions.parseInt(protocolNumberString, null);
        String protocolName = getProtocolName(protocolNumber);
        protocolName = protocolName == null ? "" : protocolName; // put empty if null

        // Update this area if network annotations change. TODO in future
        // epoch and version added from the syscall artifact
        Map<String, String> annotationsFromNetfilter = new HashMap<String, String>();
        annotationsFromNetfilter.put(OPMConstants.ARTIFACT_LOCAL_ADDRESS, localAddress);
        annotationsFromNetfilter.put(OPMConstants.ARTIFACT_LOCAL_PORT, localPort);
        annotationsFromNetfilter.put(OPMConstants.ARTIFACT_REMOTE_ADDRESS, remoteAddress);
        annotationsFromNetfilter.put(OPMConstants.ARTIFACT_REMOTE_PORT, remotePort);
        annotationsFromNetfilter.put(OPMConstants.ARTIFACT_PROTOCOL, protocolName);
        annotationsFromNetfilter.put(OPMConstants.ARTIFACT_SUBTYPE, OPMConstants.SUBTYPE_NETWORK_SOCKET);
        annotationsFromNetfilter.put(OPMConstants.SOURCE, OPMConstants.SOURCE_AUDIT_NETFILTER);

        Map<String, String> annotationsFromSyscall = getNetworkAnnotationsSeenInList(networkAnnotationsFromSyscalls,
                remoteAddress, remotePort);
        if (annotationsFromSyscall != null) { //found
            String localPortFromSyscall = annotationsFromSyscall.get(OPMConstants.ARTIFACT_LOCAL_PORT);
            if (localPortFromSyscall != null && !localPortFromSyscall.trim().isEmpty()) {
                if (!localPortFromSyscall.equals(localPort)) {
                    // different connection
                    annotationsFromNetfilter.put(OPMConstants.EDGE_TIME, time);
                    annotationsFromNetfilter.put(OPMConstants.EDGE_EVENT_ID, eventId);
                    networkAnnotationsFromNetfilter.add(annotationsFromNetfilter);
                    return;
                }
            }
            // logic for deduplication deviates from the current one
            // standardize this too and handling of netfilter in syscall functions. TODO
            String epoch = annotationsFromSyscall.get(OPMConstants.ARTIFACT_EPOCH);
            String version = annotationsFromSyscall.get(OPMConstants.ARTIFACT_VERSION);
            if (epoch != null) {
                annotationsFromNetfilter.put(OPMConstants.ARTIFACT_EPOCH, epoch);
            }
            if (version != null) {
                annotationsFromNetfilter.put(OPMConstants.ARTIFACT_VERSION, version);
            }

            Artifact artifactFromNetfilter = new Artifact();
            artifactFromNetfilter.addAnnotations(annotationsFromNetfilter);
            putVertex(artifactFromNetfilter); // add this only. syscall one already added.

            Artifact artifactFromSyscall = new Artifact();
            artifactFromSyscall.addAnnotations(annotationsFromSyscall);

            WasDerivedFrom syscallToNetfilter = new WasDerivedFrom(artifactFromNetfilter, artifactFromSyscall);
            putEdge(syscallToNetfilter, getOperation(SYSCALL.UPDATE), time, eventId,
                    OPMConstants.SOURCE_AUDIT_NETFILTER);

            // Found a match, and have consumed this. So, remove from the list.
            networkAnnotationsFromSyscalls.remove(annotationsFromSyscall);

            matchedNetfilterSyscall++;

        } else {
            annotationsFromNetfilter.put(OPMConstants.EDGE_EVENT_ID, eventId);
            annotationsFromNetfilter.put(OPMConstants.EDGE_TIME, time);
            networkAnnotationsFromNetfilter.add(annotationsFromNetfilter);
        }
    }

    private NetworkSocketIdentifier getNetworkIdentifier(SYSCALL syscall, String time, String eventId, String pid,
            String fd) {
        ArtifactIdentifier identifier = processManager.getFd(pid, fd);
        if (identifier != null) {
            if (identifier.getClass().equals(NetworkSocketIdentifier.class)) {
                return ((NetworkSocketIdentifier) identifier);
            } else {
                log(Level.INFO, "Expected network identifier but found: " + identifier.getClass(), null, time,
                        eventId, syscall);
                return null;
            }
        } else {
            return null;
        }
    }

    private String getProtocol(SYSCALL syscall, String time, String eventId, String pid, String fd) {
        NetworkSocketIdentifier identifier = getNetworkIdentifier(syscall, time, eventId, pid, fd);
        if (identifier != null) {
            return identifier.getProtocol();
        } else {
            return null;
        }
    }

    private AddressPort getLocalAddressPort(SYSCALL syscall, String time, String eventId, String pid, String fd) {
        NetworkSocketIdentifier identifier = getNetworkIdentifier(syscall, time, eventId, pid, fd);
        if (identifier != null) {
            NetworkSocketIdentifier networkIdentifier = ((NetworkSocketIdentifier) identifier);
            return new AddressPort(networkIdentifier.getLocalHost(), networkIdentifier.getLocalPort());
        } else {
            return null;
        }
    }

    private String getProtocolNameBySockType(Integer sockType) {
        if (sockType != null) {
            if ((sockType & SOCK_SEQPACKET) == SOCK_SEQPACKET) { // check first because seqpacket matches stream too
                return PROTOCOL_NAME_TCP;
            } else if ((sockType & SOCK_STREAM) == SOCK_STREAM) {
                return PROTOCOL_NAME_TCP;
            } else if ((sockType & SOCK_DGRAM) == SOCK_DGRAM) {
                return PROTOCOL_NAME_UDP;
            }
        }
        return null;
    }

    private void handleSocket(Map<String, String> eventData, SYSCALL syscall) {
        // socket() receives the following message(s):
        // - SYSCALL
        // - EOE
        String sockFd = eventData.get(AuditEventReader.EXIT);
        Integer socketType = CommonFunctions.parseInt(eventData.get(AuditEventReader.ARG1), null);
        String protocolName = getProtocolNameBySockType(socketType);

        if (protocolName != null) {
            String pid = eventData.get(AuditEventReader.PID);

            NetworkSocketIdentifier identifierForProtocol = new NetworkSocketIdentifier(null, null, null, null,
                    protocolName);
            processManager.setFd(pid, sockFd, identifierForProtocol, null); // no close edge
        }
    }

    private void putBind(String pid, String fd, ArtifactIdentifier identifier) {
        if (identifier != null) {
            // no need to add to descriptors because we will have the address from other syscalls? TODO
            processManager.setFd(pid, fd, identifier, null);
            if (identifier instanceof UnixSocketIdentifier) {
                artifactManager.artifactCreated(identifier);
            }
        }
    }

    private void logInvalidSaddr(String saddr, String time, String eventId, SYSCALL syscall) {
        if (!"0100".equals(saddr)) { // if not empty path
            log(Level.INFO, "Failed to parse saddr: " + saddr, null, time, eventId, syscall);
        }
    }

    // needed for marking epoch for unix socket
    private void handleBindKernelModule(Map<String, String> eventData, String time, String eventId, SYSCALL syscall,
            String pid, String exit, String sockFd, int sockType, String localSaddr, String remoteSaddr) {
        boolean isNetwork = isNetworkSaddr(localSaddr) || isNetworkSaddr(remoteSaddr);
        if (!isNetwork) {
            // is unix
            ArtifactIdentifier identifier = parseUnixSaddr(localSaddr); // local or remote. any is fine.
            if (identifier != null) {
                putBind(pid, sockFd, identifier);
            } else {
                logInvalidSaddr(remoteSaddr, time, eventId, syscall);
            }
        } else {
            // nothing needed in case of network because we get all info from other syscalls if kernel module
        }
    }

    private void handleBind(Map<String, String> eventData, SYSCALL syscall) {
        // bind() receives the following message(s):
        // - SYSCALL
        // - SADDR
        // - EOE
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String saddr = eventData.get(AuditEventReader.SADDR);
        String sockFd = eventData.get(AuditEventReader.ARG0);
        String pid = eventData.get(AuditEventReader.PID);

        if (!isNetlinkSaddr(saddr)) { // not handling netlink
            ArtifactIdentifier identifier = null;
            if (isNetworkSaddr(saddr)) {
                AddressPort addressPort = parseNetworkSaddr(saddr);
                if (addressPort != null) {
                    String protocolName = getProtocol(syscall, time, eventId, pid, sockFd);
                    identifier = new NetworkSocketIdentifier(addressPort.address, addressPort.port, null, null,
                            protocolName);
                }
            } else if (isUnixSaddr(saddr)) {
                identifier = parseUnixSaddr(saddr);
            }

            if (identifier == null) {
                logInvalidSaddr(saddr, time, eventId, syscall);
            } else {
                putBind(pid, sockFd, identifier);
            }
        }
    }

    private NetworkSocketIdentifier constructNetworkIdentifier(SYSCALL syscall, String time, String eventId,
            String localSaddr, String remoteSaddr, Integer sockType) {
        String protocolName = getProtocolNameBySockType(sockType);
        AddressPort local = parseNetworkSaddr(localSaddr);
        AddressPort remote = parseNetworkSaddr(remoteSaddr);
        if (local == null && remote == null) {
            log(Level.INFO, "Local and remote saddr both null", null, time, eventId, syscall);
        } else {
            String localAddress = null, localPort = null, remoteAddress = null, remotePort = null;
            if (local != null) {
                localAddress = local.address;
                localPort = local.port;
            }
            if (remote != null) {
                remoteAddress = remote.address;
                remotePort = remote.port;
            }
            return new NetworkSocketIdentifier(localAddress, localPort, remoteAddress, remotePort, protocolName);
        }
        return null;
    }

    private void putConnect(SYSCALL syscall, String time, String eventId, String pid, String fd,
            ArtifactIdentifier fdIdentifier, Map<String, String> eventData) {
        if (fdIdentifier != null) {
            if (fdIdentifier instanceof NetworkSocketIdentifier) {
                artifactManager.artifactCreated(fdIdentifier);
            }
            processManager.setFd(pid, fd, fdIdentifier, false);

            Process process = processManager.handleProcessFromSyscall(eventData);
            artifactManager.artifactVersioned(fdIdentifier);
            Artifact artifact = putArtifactFromSyscall(eventData, fdIdentifier);
            WasGeneratedBy wgb = new WasGeneratedBy(artifact, process);
            putEdge(wgb, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);

            if (REFINE_NET) {
                putWasDerivedFromEdgeFromNetworkArtifacts(artifact);
            }
        }
    }

    private void handleConnectKernelModule(Map<String, String> eventData, String time, String eventId,
            SYSCALL syscall, String pid, String exit, String sockFd, int sockType, String localSaddr,
            String remoteSaddr) {
        // if not network then unix. Only that being handled in kernel module
        boolean isNetwork = isNetworkSaddr(localSaddr) || isNetworkSaddr(remoteSaddr);

        ArtifactIdentifier identifier = null;

        if (isNetwork) {
            identifier = constructNetworkIdentifier(syscall, time, eventId, localSaddr, remoteSaddr, sockType);
        } else { // is unix socket
            identifier = parseUnixSaddr(remoteSaddr); // address in remote unlike accept
            if (identifier == null) {
                logInvalidSaddr(localSaddr, time, eventId, syscall);
            }
        }
        if (identifier != null) {
            putConnect(syscall, time, eventId, pid, sockFd, identifier, eventData);
        }
    }

    private void handleConnect(Map<String, String> eventData, SYSCALL syscall) {
        //connect() receives the following message(s):
        // - SYSCALL
        // - SADDR
        // - EOE   
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String time = eventData.get(AuditEventReader.TIME);
        String pid = eventData.get(AuditEventReader.PID);
        String saddr = eventData.get(AuditEventReader.SADDR);
        String sockFd = eventData.get(AuditEventReader.ARG0);

        Integer exit = CommonFunctions.parseInt(eventData.get(AuditEventReader.EXIT), null);
        if (exit == null) {
            log(Level.WARNING, "Failed to parse exit value: " + eventData.get(AuditEventReader.EXIT), null, time,
                    eventId, syscall);
            return;
        } else { // not null
            // only handling if success is 0 or success is EINPROGRESS
            if (exit != 0 // no success
                    && exit != EINPROGRESS) { //in progress with possible failure in the future. see manpage.
                return;
            }
        }

        if (!isNetlinkSaddr(saddr)) { // not handling netlink saddr
            ArtifactIdentifier identifier = null;
            if (isNetworkSaddr(saddr)) {
                AddressPort addressPort = parseNetworkSaddr(saddr);
                if (addressPort != null) {
                    String protocolName = getProtocol(syscall, time, eventId, pid, sockFd);
                    identifier = new NetworkSocketIdentifier(null, null, addressPort.address, addressPort.port,
                            protocolName);

                }
            } else if (isUnixSaddr(saddr)) {
                identifier = parseUnixSaddr(saddr);
            }

            if (identifier == null) {
                logInvalidSaddr(saddr, time, eventId, syscall);
            } else {
                putConnect(syscall, time, eventId, pid, sockFd, identifier, eventData);
            }
        }
    }

    private void putAccept(SYSCALL syscall, String time, String eventId, String pid, String fd,
            ArtifactIdentifier fdIdentifier, Map<String, String> eventData) {
        // eventData must contain all information need to create a process vertex
        if (fdIdentifier != null) {
            if (fdIdentifier instanceof NetworkSocketIdentifier) {
                artifactManager.artifactCreated(fdIdentifier);
            }

            processManager.setFd(pid, fd, fdIdentifier, false);

            Process process = processManager.handleProcessFromSyscall(eventData);
            Artifact socket = putArtifactFromSyscall(eventData, fdIdentifier);
            Used used = new Used(process, socket);
            putEdge(used, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);

            if (REFINE_NET) {
                putWasDerivedFromEdgeFromNetworkArtifacts(socket);
            }
        }
    }

    private void handleAcceptKernelModule(Map<String, String> eventData, String time, String eventId,
            SYSCALL syscall, String pid, String fd, String sockFd, int sockType, String localSaddr,
            String remoteSaddr) {
        // if not network then unix. Only that being handled in kernel module
        boolean isNetwork = isNetworkSaddr(localSaddr) || isNetworkSaddr(remoteSaddr);

        ArtifactIdentifier identifier = null;
        if (isNetwork) {
            identifier = constructNetworkIdentifier(syscall, time, eventId, localSaddr, remoteSaddr, sockType);
        } else { // is unix socket
            identifier = parseUnixSaddr(localSaddr);
            if (identifier == null) {
                logInvalidSaddr(localSaddr, time, eventId, syscall);
            }
        }
        if (identifier != null) {
            putAccept(syscall, time, eventId, pid, fd, identifier, eventData);
        }
    }

    private void handleAccept(Map<String, String> eventData, SYSCALL syscall) {
        //accept() & accept4() receive the following message(s):
        // - SYSCALL
        // - SADDR
        // - EOE
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String time = eventData.get(AuditEventReader.TIME);
        String pid = eventData.get(AuditEventReader.PID);
        String sockFd = eventData.get(AuditEventReader.ARG0); //the fd on which the connection was accepted, not the fd of the connection
        String fd = eventData.get(AuditEventReader.EXIT); //fd of the connection
        String saddr = eventData.get(AuditEventReader.SADDR);

        if (!isNetlinkSaddr(saddr)) { // not handling netlink saddr
            ArtifactIdentifier identifier = null;
            ArtifactIdentifier boundIdentifier = processManager.getFd(pid, sockFd); // sockFd
            if (isNetworkSaddr(saddr)) {
                AddressPort addressPort = parseNetworkSaddr(saddr);
                if (addressPort != null) {
                    String localAddress = null, localPort = null,
                            protocol = getProtocol(syscall, time, eventId, pid, sockFd);
                    AddressPort localAddressPort = getLocalAddressPort(syscall, time, eventId, pid, sockFd);
                    if (localAddressPort != null) {
                        localAddress = localAddressPort.address;
                        localPort = localAddressPort.port;
                    }
                    identifier = new NetworkSocketIdentifier(localAddress, localPort, addressPort.address,
                            addressPort.port, protocol);
                }
            } else if (isUnixSaddr(saddr)) {
                // The unix saddr in accept is empty. So, use the bound one.
                if (boundIdentifier != null) {
                    if (boundIdentifier.getClass().equals(UnixSocketIdentifier.class)) {
                        // Use a new one because the wasOpenedForRead is updated otherwise it would
                        // be updated in the bound identifier too.
                        identifier = new UnixSocketIdentifier(((UnixSocketIdentifier) boundIdentifier).getPath());
                    } else {
                        log(Level.INFO, "Expected unix identifier but found: " + boundIdentifier.getClass(), null,
                                time, eventId, syscall);
                    }
                }
            }

            if (identifier == null) {
                logInvalidSaddr(saddr, time, eventId, syscall);
            } else {
                putAccept(syscall, time, eventId, pid, fd, identifier, eventData);
            }
        }
    }

    private void putWasDerivedFromEdgeFromNetworkArtifacts(Artifact syscallArtifact) {
        if (syscallArtifact.getAnnotation(OPMConstants.ARTIFACT_SUBTYPE)
                .equals(OPMConstants.SUBTYPE_NETWORK_SOCKET)) {
            String remoteAddress = syscallArtifact.getAnnotation(OPMConstants.ARTIFACT_REMOTE_ADDRESS);
            String remotePort = syscallArtifact.getAnnotation(OPMConstants.ARTIFACT_REMOTE_PORT);
            Map<String, String> netfilterAnnotations = getNetworkAnnotationsSeenInList(
                    networkAnnotationsFromNetfilter, remoteAddress, remotePort);
            if (netfilterAnnotations != null) {
                String localPortFromSyscall = syscallArtifact.getAnnotation(OPMConstants.ARTIFACT_LOCAL_PORT);
                if (localPortFromSyscall != null && !localPortFromSyscall.trim().isEmpty()) {
                    if (!localPortFromSyscall.equals(netfilterAnnotations.get(OPMConstants.ARTIFACT_LOCAL_PORT))) {
                        // different connection
                        // basically further pruning
                        networkAnnotationsFromSyscalls.add(syscallArtifact.getAnnotations());
                        return;
                    }
                }

                // remove the annotations map from netfilter because we are going to consume that.
                // removing that here because the map is going to be updated below.
                networkAnnotationsFromNetfilter.remove(netfilterAnnotations);

                // logic for deduplication deviates from the current one
                // standardize this too and handling of netfilter in syscall functions. TODO
                String epoch = syscallArtifact.getAnnotation(OPMConstants.ARTIFACT_EPOCH);
                String version = syscallArtifact.getAnnotation(OPMConstants.ARTIFACT_VERSION);
                if (epoch != null) {
                    netfilterAnnotations.put(OPMConstants.ARTIFACT_EPOCH, epoch);
                }
                if (version != null) {
                    netfilterAnnotations.put(OPMConstants.ARTIFACT_VERSION, version);
                }

                String netfilterTime = netfilterAnnotations.remove(OPMConstants.EDGE_TIME); //remove
                String netfilterEventId = netfilterAnnotations.remove(OPMConstants.EDGE_EVENT_ID); //remove

                Artifact netfilterArtifact = new Artifact();
                netfilterArtifact.addAnnotations(netfilterAnnotations);
                putVertex(netfilterArtifact);

                WasDerivedFrom edge = new WasDerivedFrom(netfilterArtifact, syscallArtifact);
                putEdge(edge, getOperation(SYSCALL.UPDATE), netfilterTime, netfilterEventId,
                        OPMConstants.SOURCE_AUDIT_NETFILTER);

                matchedSyscallNetfilter++;
            } else {
                networkAnnotationsFromSyscalls.add(syscallArtifact.getAnnotations());
            }
        }
    }

    private ArtifactIdentifier getNetworkIdentifierFromFdAndOrSaddr(SYSCALL syscall, String time, String eventId,
            String pid, String fd, String saddr) {
        ArtifactIdentifier identifier = null;
        if (saddr != null) {
            if (!isNetlinkSaddr(saddr)) { // not handling netlink saddr
                if (isNetworkSaddr(saddr)) {
                    AddressPort addressPort = parseNetworkSaddr(saddr);
                    if (addressPort != null) {
                        String localAddress = null, localPort = null;
                        //   Protocol has to be UDP since SOCK_DGRAM and (AF_INET or AF_INET6) 
                        //   protocol = getProtocol(syscall, time, eventId, pid, fd);
                        AddressPort localAddressPort = getLocalAddressPort(syscall, time, eventId, pid, fd);
                        if (localAddressPort != null) {
                            localAddress = localAddressPort.address;
                            localPort = localAddressPort.port;
                        }
                        // Protocol can only be UDP because saddr only present when SOCK_DGRAM
                        // and family is AF_INET or AF_INET6.
                        identifier = new NetworkSocketIdentifier(localAddress, localPort, addressPort.address,
                                addressPort.port, PROTOCOL_NAME_UDP);
                    }
                } else if (isUnixSaddr(saddr)) {
                    identifier = parseUnixSaddr(saddr);
                    // just use this
                }
            }
        } else {
            // use fd
            identifier = processManager.getFd(pid, fd);
        }

        // Don't update fd in descriptors even if updated identifier
        // Not updating because if fd used again then saddr must be present
        return identifier;
    }

    private void handleNetworkIOKernelModule(Map<String, String> eventData, String time, String eventId,
            SYSCALL syscall, String pid, String bytes, String sockFd, int sockType, String localSaddr,
            String remoteSaddr, boolean isRecv) {
        boolean isNetwork = isNetworkSaddr(localSaddr) || isNetworkSaddr(remoteSaddr);
        ArtifactIdentifier identifier = null;
        if (isNetwork) {
            NetworkSocketIdentifier recordIdentifier = constructNetworkIdentifier(syscall, time, eventId,
                    localSaddr, remoteSaddr, sockType);
            ArtifactIdentifier fdIdentifier = processManager.getFd(pid, sockFd);
            if (fdIdentifier instanceof NetworkSocketIdentifier) {
                NetworkSocketIdentifier fdNetworkIdentifier = (NetworkSocketIdentifier) fdIdentifier;
                if (CommonFunctions.isNullOrEmpty(fdNetworkIdentifier.getRemoteHost())) {
                    // Connection based IO
                    identifier = recordIdentifier;
                } else {
                    // Non-connection based IO
                    identifier = fdNetworkIdentifier;
                }
            } else {
                identifier = recordIdentifier;
            }
        } else { // is unix socket
            identifier = parseUnixSaddr(localSaddr);
            if (identifier == null) {
                logInvalidSaddr(localSaddr, time, eventId, syscall);
            }
        }
        putIO(eventData, time, eventId, syscall, pid, sockFd, identifier, bytes, null, isRecv);
    }

    private void putIO(Map<String, String> eventData, String time, String eventId, SYSCALL syscall, String pid,
            String fd, ArtifactIdentifier identifier, String bytesTransferred, String offset, boolean incoming) {

        boolean isNetworkUdp = false;
        if (identifier instanceof NetworkSocketIdentifier) {
            if (!USE_SOCK_SEND_RCV) {
                return;
            }
            isNetworkUdp = isUdp(identifier);
        } else { // all else are local i.e. file io include unix
            if (!USE_READ_WRITE) {
                return;
            }
            if (identifier instanceof UnixSocketIdentifier
                    || identifier instanceof UnnamedUnixSocketPairIdentifier) {
                if (!globals.unixSockets) {
                    return;
                }
            }
        }

        if (identifier == null) {
            identifier = addUnknownFd(pid, fd);
        }

        if (isNetworkUdp) {
            // Since saddr present that means that it is SOCK_DGRAM.
            // Epoch for all SOCK_DGRAM
            artifactManager.artifactCreated(identifier);
        }

        Process process = processManager.handleProcessFromSyscall(eventData);
        Artifact artifact = null;
        AbstractEdge edge = null;
        if (incoming) {
            artifact = putArtifactFromSyscall(eventData, identifier);
            edge = new Used(process, artifact);
        } else {
            artifactManager.artifactVersioned(identifier);
            artifact = putArtifactFromSyscall(eventData, identifier);
            edge = new WasGeneratedBy(artifact, process);
        }
        edge.addAnnotation(OPMConstants.EDGE_SIZE, bytesTransferred);
        if (offset != null) {
            edge.addAnnotation(OPMConstants.EDGE_OFFSET, offset);
        }
        putEdge(edge, getOperation(syscall), time, eventId, AUDIT_SYSCALL_SOURCE);

        // UDP
        if (isNetworkUdp && REFINE_NET) {
            putWasDerivedFromEdgeFromNetworkArtifacts(artifact);
        }
    }

    /**
     * Outputs formatted messages in the format-> [Event ID:###, SYSCALL:... MSG Exception]
     *  
     * @param level Level of the log message
     * @param msg Message to print
     * @param exception Exception (if any)
     * @param time time of the audit event
     * @param eventId id of the audit event
     * @param syscall system call of the audit event
     */
    public void log(Level level, String msg, Exception exception, String time, String eventId, SYSCALL syscall) {
        String msgPrefix = "";
        if (eventId != null && syscall != null) {
            msgPrefix = "[Time:EventID=" + time + ":" + eventId + ", SYSCALL=" + syscall + "] ";
        } else if (eventId != null && syscall == null) {
            msgPrefix = "[Time:EventID=" + time + ":" + eventId + "] ";
        } else if (eventId == null && syscall != null) {
            msgPrefix = "[SYSCALL=" + syscall + "] ";
        }
        if (exception == null) {
            logger.log(level, msgPrefix + msg);
        } else {
            logger.log(level, msgPrefix + msg, exception);
        }
    }

    private Artifact putArtifactFromSyscall(Map<String, String> eventData, ArtifactIdentifier identifier) {
        String time = eventData.get(AuditEventReader.TIME);
        String eventId = eventData.get(AuditEventReader.EVENT_ID);
        String pid = eventData.get(AuditEventReader.PID);
        String source = AUDIT_SYSCALL_SOURCE;
        String operation = getOperation(SYSCALL.UPDATE);
        return artifactManager.putArtifact(time, eventId, operation, pid, source, identifier);
    }

    /**
     * Standardized method to be called for putting an edge into the reporter's buffer.
     * Adds the arguments to the edge with proper annotations. If any argument null then 
     * that annotation isn't put to the edge.
     * 
     * @param edge edge to add annotations to and to put to reporter's buffer
     * @param operation operation as gotten from {@link #getOperation(SYSCALL) getOperation}
     * @param time time of the audit log which generated this edge
     * @param eventId event id in the audit log which generated this edge
     * @param source source of the edge
     */
    public void putEdge(AbstractEdge edge, String operation, String time, String eventId, String source) {
        if (edge != null && edge.getChildVertex() != null && edge.getParentVertex() != null) {
            if (!globals.unixSockets && (isUnixSocketArtifact(edge.getChildVertex())
                    || isUnixSocketArtifact(edge.getParentVertex()))) {
                return;
            }
            if (time != null) {
                edge.addAnnotation(OPMConstants.EDGE_TIME, time);
            }
            if (eventId != null) {
                edge.addAnnotation(OPMConstants.EDGE_EVENT_ID, eventId);
            }
            if (source != null) {
                edge.addAnnotation(OPMConstants.SOURCE, source);
            }
            if (operation != null) {
                edge.addAnnotation(OPMConstants.EDGE_OPERATION, operation);
            }
            putEdge(edge);
        } else {
            log(Level.WARNING,
                    "Failed to put edge. edge = " + edge + ", sourceVertex = "
                            + (edge != null ? edge.getChildVertex() : null) + ", " + "destination vertex = "
                            + (edge != null ? edge.getParentVertex() : null) + ", operation = " + operation + ", "
                            + "time = " + time + ", eventId = " + eventId + ", source = " + source,
                    null, time, eventId, SYSCALL.valueOf(operation.toUpperCase()));
        }
    }

    private boolean isUnixSocketArtifact(AbstractVertex vertex) {
        return vertex != null
                && (OPMConstants.SUBTYPE_UNIX_SOCKET.equals(vertex.getAnnotation(OPMConstants.ARTIFACT_SUBTYPE))
                        || OPMConstants.SUBTYPE_UNNAMED_UNIX_SOCKET_PAIR
                                .equals(vertex.getAnnotation(OPMConstants.ARTIFACT_SUBTYPE)));
    }

    /**
     * To be used where an absolute path needs to be created in system calls like renameat, openat, linkat, and etc.
     * 
     * Constructs an absolute path from given params using the following rules:
     * 
     * 1) If path absolute then return that path
     * 2) If path not absolute and fd == AT_FDCWD and cwd != null then concatenate cwd with path and return that
     * 3) If path not absolute and fd is a valid existing FILE descriptor then get the path from fd, concatenate 
     * this path from fd and the passed path and return that.
     * 
     * @param path path of the file as gotten in the audit log
     * @param fdString fd of the directory. Should be in decimal format and a valid number to be usable
     * @param cwd current working directory
     * @param pid process id
     * @param time time of the audit event
     * @param eventId event id as gotten in the audit log. Used for logging.
     * @param syscall system call from where this function is called. Used for logging.
     * @return
     */
    private String constructPathSpecial(String path, String fdString, String cwd, String pid, String time,
            String eventId, SYSCALL syscall) {
        if (path == null) {
            log(Level.INFO, "Missing PATH record", null, time, eventId, syscall);
            return null;
        } else if (path.startsWith(File.separator)) { //is absolute
            return constructAbsolutePath(path, cwd, pid); //just getting the path resolved if it has .. or .
        } else { //is not absolute
            if (fdString == null) {
                log(Level.INFO, "Missing FD", null, time, eventId, syscall);
                return null;
            } else {
                Long fd = CommonFunctions.parseLong(fdString, -1L);
                if (fd == AT_FDCWD) {
                    if (cwd == null) {
                        log(Level.INFO, "Missing CWD record", null, time, eventId, syscall);
                        return null;
                    } else {
                        path = constructAbsolutePath(path, cwd, pid);
                        return path;
                    }
                } else {
                    ArtifactIdentifier artifactIdentifier = processManager.getFd(pid, String.valueOf(fd));
                    if (artifactIdentifier == null) {
                        log(Level.INFO, "No FD with number '" + fd + "' for pid '" + pid + "'", null, time, eventId,
                                syscall);
                        return null;
                    } else if (!(artifactIdentifier instanceof PathIdentifier)) {
                        log(Level.INFO, "FD with number '" + fd + "' for pid '" + pid
                                + "' must be of type file but is '" + artifactIdentifier.getClass() + "'", null,
                                time, eventId, syscall);
                        return null;
                    } else {
                        path = constructAbsolutePath(path, ((PathIdentifier) artifactIdentifier).getPath(), pid);
                        if (path == null) {
                            log(Level.INFO,
                                    "Invalid path (" + ((PathIdentifier) artifactIdentifier).getPath()
                                            + ") for fd with number '" + fd + "' of pid '" + pid + "'",
                                    null, time, eventId, syscall);
                            return null;
                        } else {
                            return path;
                        }
                    }
                }
            }
        }
    }

    private AddressPort parseNetworkSaddr(String saddr) {
        // TODO the address = 0.0.0.0 and 127.0.0.1 issue! Rename or not?
        try {
            String address = null, port = null;
            if (isIPv4Saddr(saddr) && saddr.length() >= 17) {
                port = Integer.toString(Integer.parseInt(saddr.substring(4, 8), 16));
                int oct1 = Integer.parseInt(saddr.substring(8, 10), 16);
                int oct2 = Integer.parseInt(saddr.substring(10, 12), 16);
                int oct3 = Integer.parseInt(saddr.substring(12, 14), 16);
                int oct4 = Integer.parseInt(saddr.substring(14, 16), 16);
                address = String.format("%d.%d.%d.%d", oct1, oct2, oct3, oct4);
            } else if (isIPv6Saddr(saddr) && saddr.length() >= 49) {
                port = Integer.toString(Integer.parseInt(saddr.substring(4, 8), 16));
                String hextet1 = saddr.substring(16, 20);
                String hextet2 = saddr.substring(20, 24);
                String hextet3 = saddr.substring(24, 28);
                String hextet4 = saddr.substring(28, 32);
                String hextet5 = saddr.substring(32, 36);
                String hextet6 = saddr.substring(36, 40);
                String hextet7 = saddr.substring(40, 44);
                String hextet8 = saddr.substring(44, 48);
                address = String.format("%s:%s:%s:%s:%s:%s:%s:%s", hextet1, hextet2, hextet3, hextet4, hextet5,
                        hextet6, hextet7, hextet8);
            }
            if (address != null && port != null) {
                return new AddressPort(address, port);
            }
        } catch (Exception e) {
            // Logged by the caller
        }
        return null;
    }

    private UnixSocketIdentifier parseUnixSaddr(String saddr) {
        String path = "";
        int start = -1;
        //starting from 2 since first two characters are 01
        for (int a = 2; a <= saddr.length() - 2; a += 2) {
            if (saddr.substring(a, a + 2).equals("00")) { //null char
                //continue until non-null found
                continue;
            } else {
                //first non-null char i.e. we are going to start from here
                start = a;
                break;
            }
        }

        if (start != -1) { //found
            try {
                for (; start <= saddr.length() - 2; start += 2) {
                    char c = (char) (Integer.parseInt(saddr.substring(start, start + 2), 16));
                    if (c == 0) { //null char
                        break;
                    }
                    path += c;
                }
            } catch (Exception e) {
                return null;
            }
        }

        // TODO handle unnamed unix socket. Need a new identifier to contain the tgid like for memory identifier.
        // Unnamed unix socket created through socketpair.
        // TODO need to handle socketpair syscall for that too.
        if (path != null && !path.isEmpty()) {
            return new UnixSocketIdentifier(path);
        } else {
            return null;
        }
    }

    /**
     * Groups system call names by functionality and returns that name to simplify identification of the type of system call.
     * Grouping only done if {@link #SIMPLIFY SIMPLIFY} is true otherwise the system call name is returned simply.
     * 
     * @param syscall system call to get operation for
     * @return operation corresponding to the syscall
     */
    public String getOperation(SYSCALL primary) {
        return getOperation(primary, null);
    }

    private String getOperation(SYSCALL primary, SYSCALL secondary) {
        return OPMConstants.getOperation(primary, secondary, SIMPLIFY);
    }

    /**
     * Every path record in Audit log has a key 'nametype' which can be used to identify what the artifact
     * referenced in the path record was.
     * 
     * Possible options for nametype:
     * 1) PARENT -> parent directory of the path
     * 2) CREATE -> path was created
     * 3) NORMAL -> path was just used for read or write
     * 4) DELETE -> path was deleted
     * 5) UNKNOWN -> can't tell what was done with the path. So far seen that it only happens when a syscall fails.
     * 
     * Returns empty if none found
     *  
     * @param eventData eventData that contains the paths information in the format: path[Index], nametype[Index]. example, path0, nametype0 
     * @param nametypeValue one of the above-mentioned values. Case sensitive compare operation on nametypeValue
     * @return returns a list PathRecord objects sorted by their index in ascending order
     */
    private List<PathRecord> getPathsWithNametype(Map<String, String> eventData, String nametypeValue) {
        List<PathRecord> pathRecords = new ArrayList<PathRecord>();
        if (eventData != null && nametypeValue != null) {
            Long items = CommonFunctions.parseLong(eventData.get(AuditEventReader.ITEMS), 0L);
            for (int itemcount = 0; itemcount < items; itemcount++) {
                if (nametypeValue.equals(eventData.get(AuditEventReader.NAMETYPE_PREFIX + itemcount))) {
                    PathRecord pathRecord = new PathRecord(itemcount,
                            eventData.get(AuditEventReader.PATH_PREFIX + itemcount),
                            eventData.get(AuditEventReader.NAMETYPE_PREFIX + itemcount),
                            eventData.get(AuditEventReader.MODE_PREFIX + itemcount));
                    pathRecords.add(pathRecord);
                }
            }
        }
        Collections.sort(pathRecords);
        return pathRecords;
    }

    /**
     * Returns the first PathRecord object where nametypeValue matches i.e. one with the lowest index
     * 
     * Returns null if none found
     * 
     * @param eventData eventData that contains the paths information in the format: path[Index], nametype[Index]. example, path0, nametype0 
     * @param nametypeValue one of the above-mentioned values. Case sensitive compare operation on nametypeValue
     * @return returns the PathRecord object with the lowest index
     */
    private PathRecord getFirstPathWithNametype(Map<String, String> eventData, String nametypeValue) {
        List<PathRecord> pathRecords = getPathsWithNametype(eventData, nametypeValue);
        if (pathRecords == null || pathRecords.size() == 0) {
            return null;
        } else {
            return pathRecords.get(0);
        }
    }

}

class AddressPort {
    public final String address, port;

    public AddressPort(String address, String port) {
        this.address = address;
        this.port = port;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((address == null) ? 0 : address.hashCode());
        result = prime * result + ((port == null) ? 0 : port.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AddressPort other = (AddressPort) obj;
        if (address == null) {
            if (other.address != null)
                return false;
        } else if (!address.equals(other.address))
            return false;
        if (port == null) {
            if (other.port != null)
                return false;
        } else if (!port.equals(other.port))
            return false;
        return true;
    }
}

/**
 * A class to represent all of Path record data as received from Audit logs
 * 
 * Implements Comparable interface on the index field. And is immutable.
 * 
 */
class PathRecord implements Comparable<PathRecord> {

    /**
     * Value of the name field in the audit log
     */
    private String path;
    /**
     * Value of the item field in the audit log
     */
    private int index;
    /**
     * Value of the nametype field in the audit log
     */
    private String nametype;
    /**
     * Value of the mode field in the audit log
     */
    private String mode;
    /**
     * Extracted from the mode variable by parsing it with base-8
     */
    private int pathType = 0;
    /**
     * Extracted from the mode variable
     */
    private String permissions = null;

    public PathRecord(int index, String path, String nametype, String mode) {
        this.index = index;
        this.path = path;
        this.nametype = nametype;
        this.mode = mode;
        this.pathType = parsePathType(mode);
        this.permissions = parsePermissions(mode);
    }

    /**
     * Parses the string mode into an integer with base 8
     * 
     * @param mode base 8 representation of string
     * @return integer value of mode
     */
    public static int parsePathType(String mode) {
        try {
            return Integer.parseInt(mode, 8);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * Returns the last 4 characters in the mode string.
     * If the length of the mode string is less than 4 than pads the
     * remaining zeroes at the beginning of the return value.
     * If the mode argument is null then null returned.
     * @param mode mode string with last 4 characters as permissions
     * @return only the last 4 characters or null
     */
    public static String parsePermissions(String mode) {
        if (mode != null) {
            if (mode.length() >= 4) {
                return mode.substring(mode.length() - 4);
            } else {
                int difference = 4 - mode.length();
                for (int a = 0; a < difference; a++) {
                    mode = "0" + mode;
                }
                return mode;
            }
        }
        return null;
    }

    public String getPermissions() {
        return permissions;
    }

    public int getPathType() {
        return pathType;
    }

    public String getPath() {
        return path;
    }

    public String getNametype() {
        return nametype;
    }

    public int getIndex() {
        return index;
    }

    /**
     * Compares based on index. If the passed object is null then 1 returned always
     */
    @Override
    public int compareTo(PathRecord o) {
        if (o != null) {
            return this.index - o.index;
        }
        return 1;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + index;
        result = prime * result + ((mode == null) ? 0 : mode.hashCode());
        result = prime * result + ((nametype == null) ? 0 : nametype.hashCode());
        result = prime * result + ((path == null) ? 0 : path.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        PathRecord other = (PathRecord) obj;
        if (index != other.index)
            return false;
        if (mode == null) {
            if (other.mode != null)
                return false;
        } else if (!mode.equals(other.mode))
            return false;
        if (nametype == null) {
            if (other.nametype != null)
                return false;
        } else if (!nametype.equals(other.nametype))
            return false;
        if (path == null) {
            if (other.path != null)
                return false;
        } else if (!path.equals(other.path))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "PathRecord [path=" + path + ", index=" + index + ", nametype=" + nametype + ", mode=" + mode + "]";
    }
}