Endace

The EndaceProbe Analytics Platform provides 100% accurate, continuous packet capture on network links up to 100Gbps, with unparalleled depth of storage and retrieval performance. Coupled with the Endace InvestigationManager, this provides a central search and data-mining capability across a fabric of EndaceProbes deployed in a network. This integration uses Endace APIs to search, archive and download PCAP file from either a single EndaceProbe or many via the InvestigationManager and enables integration of full historical packet capture into security automation workflows.

Network Security · Endace

Details

IDEndace
ProviderEndace
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

The EndaceProbe Analytics Platform provides 100% accurate, continuous packet capture on network links up to 100Gbps, with unparalleled depth of storage and retrieval performance. Coupled with the Endace InvestigationManager, this provides a central search and data-mining capability across a fabric of EndaceProbes deployed in a network.

This integration uses Endace APIs to search, archive and download PCAP file from either a single EndaceProbe or many via the InvestigationManager and enables integration of full historical packet capture into security automation workflows.
This integration was integrated and tested with version 6.5.7 & 7.0.0 of Endace

Configure Endace in Cortex

Parameter Description Required
applianceurl EndaceProbe URL e.g. https://<fqdn/ip[:port]> True
credentials Username True
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False
hostname EndaceProbe System Hostname True

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

endace-create-search


Create a search task on EndaceProbe. Search is issued against all Rotation Files on EndaceProbe.

Base Command

endace-create-search

Input
Argument Name Description Required
start UTC StartTime in ISO 8601 format as in 2020-04-08T15:46:30 Optional
end UTC EndTime in ISO 8601 format as in 2020-04-08T15:46:30 Optional
ip directionless ip address. For valid search either a IP or Src Host or a Dest Host value is required. Optional
port directionless port. Optional
src_host_list List of comma delimited Source IP addresses to search with a maximum of 10 IP addresses per search. For valid search either a Src Host or a Dest Host value is required. Optional
dest_host_list List of comma delimited Destination IP addresses to search with a maximum of 10 IP addresses per search. For valid search either a Src Host or a Dest Host value is required. Optional
src_port_list List of comma delimited Source Port addresses to search with a maximum of 10 Port addresses per search. Optional
dest_port_list List of comma delimited Destination Port addresses to search with a maximum of 10 Port addresses per search. Optional
protocol IANA defined IP Protocol Name or Number. For example: either use TCP or tcp or 6 for tcp protocol Optional
timeframe Event timeframe to search. Select one of the values from 30seconds, 1minute, 5minutes, 10minutes, 30minutes, 1hour, 2hours, 5hours, 10hours, 12hours, 1day, 3days, 5days, 1week. Timeframe works as search for last n seconds if start and end time is not provided. For example, by specifying 1hour as the timeframe, analyst can schedule a search for last 3600s. If both start and end time is provided, timeframe value is ignored. If either start or end time is provided along with timeframe, the respective start or end time is calculated accordingly. Initial value of timeframe is 1hour. Optional
Context Output
Path Type Description
Endace.Search.Task.JobID String Search Job ID
Endace.Search.Task.Status String Status of Search Task
Endace.Search.Task.Error String Search Error
Command Example

!endace-create-search start="2020-04-15T14:48:12" ip="1.1.1.1" timeframe="1hour"

Context Example
{
    "Endace": {
        "Search": {
            "Task": {
                "Error": "NoError",
                "JobID": "c944a329-bf16-4e51-ac58-900f17fa1a52",
                "Status": "Started",
                "Task": "CreateSearchTask"
            }
        }
    }
}
Human Readable Output

EndaceResult

Task JobID Status Error
CreateSearchTask c944a329-bf16-4e51-ac58-900f17fa1a52 Started NoError

endace-get-search-status


Get search status from EndaceProbe. This command can be polled in a loop until response is received or polling timer is over.

Base Command

endace-get-search-status

Input
Argument Name Description Required
jobid This is the job ID returned by endace-create-search command Required
Context Output
Path Type Description
Endace.Search.Response.JobID String This is the job ID of search query which we polled to get search status
Endace.Search.Response.JobProgress String Progress of this search Job
Endace.Search.Response.DataSources String List of Data Sources where packets of interest were found.
Endace.Search.Response.TotalBytes String Total data matching this search across all Data Sources.
Endace.Search.Response.Status String Task status
Endace.Search.Response.Error String Search response error
Command Example

!endace-get-search-status jobid="c944a329-bf16-4e51-ac58-900f17fa1a52"

Context Example
{
    "Endace": {
        "Search": {
            "Response": {
                "DataSources": [endaceprobe-1:datasource1],
                "Error": "NoError",
                "JobID": "c944a329-bf16-4e51-ac58-900f17fa1a52",
                "JobProgress": "100",
                "Status": "complete",
                "Task": "GetSearchStatus",
                "TotalBytes": 5526100
            }
        }
    }
}
Human Readable Output

EndaceSearch

Task JobID Status Error JobProgress DataSources TotalBytes
GetSearchStatus c944a329-bf16-4e51-ac58-900f17fa1a52 complete NoError 100 endaceprobe-1:datasource1 5526100

endace-delete-search-task


Delete search task

Base Command

endace-delete-search-task

Input
Argument Name Description Required
jobid Job ID obtained from endace-create-search command Required
Context Output
Path Type Description
Endace.Search.Delete.JobID String JobID of the task that needs to be deleted
Endace.Search.Delete.Error String Error message
Endace.Search.Delete.Status String delete status, queryNotFound indicates that the search query has already expired before this operation, which is expected as EndaceProbe purges inactive tasks after api timer expire. Deleted indicates an active search query is now deleted.
Command Example

!endace-delete-search-task jobid="c944a329-bf16-4e51-ac58-900f17fa1a52"

Context Example
{
    "Endace": {
        "Search": {
            "Delete": {
                "Error": "NoError",
                "JobID": "c944a329-bf16-4e51-ac58-900f17fa1a52",
                "Status": "Deleted",
                "Task": "DeleteSearchTask"
            }
        }
    }
}
Human Readable Output

EndaceSearch

Task JobID Status Error
DeleteSearchTask c944a329-bf16-4e51-ac58-900f17fa1a52 Deleted NoError

endace-create-archive


Create an archive task to archive packets of interest on EndaceProbe. Archived packets can later be downloaded from EndaceProbe as a PCAP file. Archived Files never expire. Allowed chars are text, numbers, dash and underscore.

Base Command

endace-create-archive

Input
Argument Name Description Required
start UTC StartTime in ISO 8601 format as in 2020-04-08T15:46:30 Optional
end UTC EndTime in ISO 8601 format as in 2020-04-08T15:46:30 Optional
ip directionless ip address. Optional
port directionless port. For valid search either a Src Host or a Dest Host value is required. Optional
timeframe Event timeframe to search. Select one of the values from 30seconds, 1minute, 5minutes, 10minutes, 30minutes, 1hour, 2hours, 5hours, 10hours, 12hours, 1day, 3days, 5days, 1week. Timeframe works as search for last n seconds if start and end time is not provided. For example, by specifying 1hour as the timeframe, analyst can schedule a search for last 3600s. If both start and end time is provided, timeframe value is ignored. If either start or end time is provided along with timeframe, the respective start or end time is calculated accordingly. Initial value of timeframe is 1hour. Optional
src_host_list List of comma delimited Source IP addresses to search with a maximum of 10 IP addresses per search. For valid search either a Src Host or a Dest Host value is required. Optional
dest_host_list List of comma delimited Destination IP addresses to search with a maximum of 10 IP addresses per search. For valid search either a Src Host or a Dest Host value is required. Optional
src_port_list List of comma delimited Source Port addresses to search with a maximum of 10 Port addresses per search. Optional
dest_port_list List of comma delimited Destination Port addresses to search with a maximum of 10 Port addresses per search. Optional
protocol IANA defined IP Protocol Name or Number. For example: either use TCP or tcp or 6 for tcp protocol Optional
archive_filename Name of the archive file. For example, archive_filename could be an event ID. To keep archive filename unique, value of epoch seconds at the time of execution of the command is appended to this filename argument. For example - if the event id is eventid, then archive_filename is eventid-[epochtime]. Required
Context Output
Path Type Description
Endace.Archive.Task.FileName String Name of the archived File
Endace.Archive.Task.P2Vurl String Endace Pivot to Vision URL to archived packets that links to an Investigation Dashboard on EndaceProbe. This enables an analyst to utilize tools available on EndaceProbe for further drill down on packets of interests without needing to download a PCAP.
Endace.Archive.Task.Status String Status of archived task
Endace.Archive.Task.Error String Archive error
Endace.Archive.Task.JobID String Archive Task Job ID
Command Example

!endace-create-archive start="2020-04-15T14:48:12" archive_filename="event" ip="1.1.1.1" timeframe="1hour"

Context Example
{
    "Endace": {
        "Archive": {
            "Task": {
                "End": 1586965692,
                "Error": "NoError",
                "FileName": "event-1586976954",
                "JobID": "495f1899-6f27-4ed9-85c9-2af19a4e55d8",
                "P2Vurl": "[Endace PivotToVision URL](https://endaceprobe-1/vision2/pivotintovision/?datasources=tag:rotation-file&title=event-1586976954&start=1586962092000&end=1586965692000&tools=trafficOverTime_by_app%2Cconversations_by_ipaddress&ip=1.1.1.1)",
                "Start": 1586962092,
                "Status": "Started",
                "Task": "CreateArchiveTask"
            }
        }
    }
}
Human Readable Output

EndaceResult

Task FileName P2Vurl Status Error JobID
CreateArchiveTask event-1586976954 Endace PivotToVision URL Started NoError 495f1899-6f27-4ed9-85c9-2af19a4e55d8

endace-get-archive-status


get status of archived task

Base Command

endace-get-archive-status

Input
Argument Name Description Required
archive_filename Get status of this archived file Required
Context Output
Path Type Description
Endace.Archive.Response.FileName String Archived File Name
Endace.Archive.Response.FileSize String Archived File Size
Endace.Archive.Response.Status String Status of the archive process
Endace.Archive.Response.Error String Archive response error
Command Example

!endace-get-archive-status archive_filename="event-1586976861"

Context Example
{
    "Endace": {
        "Archive": {
            "Response": {
                "Error": "NoError",
                "FileName": "event-1586976861",
                "FileSize": "6.29MB",
                "Status": "Finished",
                "Task": "GetArchiveStatus"
            }
        }
    }
}
Human Readable Output

EndaceResult

Task FileName Status Error FileSize
GetArchiveStatus event-1586976861 Finished NoError 6.29MB

endace-delete-archive-task


delete archive task

Base Command

endace-delete-archive-task

Input
Argument Name Description Required
jobid Job ID of archive task Optional
Context Output
Path Type Description
Endace.Archive.Delete.Status String Status of delete task
Endace.Archive.Delete.Error String Delete task error
Endace.Archive.Delete.JobID String Delete Task ID
Command Example

!endace-delete-archive-task jobid="83fec7a8-daec-42fb-9b5a-e742145e85e8"

Context Example
{
    "Endace": {
        "Archive": {
            "Delete": {
                "Error": "NoError",
                "JobID": "83fec7a8-daec-42fb-9b5a-e742145e85e8",
                "Status": "Deleted",
                "Task": "DeleteArchiveTask"
            }
        }
    }
}
Human Readable Output

EndaceResult

Task JobID Status Error
DeleteArchiveTask 83fec7a8-daec-42fb-9b5a-e742145e85e8 Deleted NoError

endace-download-pcap


Download a copy of the PCAP file from EndaceProbe if PCAP file size is within the threshold value defined by filesizelimit.

Base Command

endace-download-pcap

Input
Argument Name Description Required
filename Name of the file (without extension) to download from EndaceProbe. Text, numbers, underscore or dash is supported. Required
filesizelimit User defined upper size limit on file download (in MegaBytes). A PCAP File with size less than or equal to this limit can be downloaded from EndaceProbe. Minimum size is 1 (MB). Default Upper Limit is 50 (MB). Required
Context Output
Path Type Description
Endace.Download.PCAP.FileName String Name of the File to download from EndaceProbe
Endace.Download.PCAP.FileSize String File size in MegaBytes
Endace.Download.PCAP.FileType String The file downloaded from EndaceProbe is either a Rotation File or Archive.
Endace.Download.PCAP.FileURL String URL to PCAP file on EndaceProbe.
Endace.Download.PCAP.FileUser String Username of the person who has permission to download this PCAP from EndaceProbe.
Endace.Download.PCAP.Status String Download status of the PCAP file.
Endace.Download.PCAP.Error String Error occured during downloading of this file
Command Example

!endace-download-pcap filename="event-1586976861" filesizelimit="50"

Context Example
{
    "Endace": {
        "Download": {
            "PCAP": {
                "Error": "NoError",
                "FileName": "event-1586976861.pcap",
                "FileSize": "6.29MB",
                "FileType": "archive_file",
                "FileURL": "[Endace PCAP URL](https://endaceprobe-1/vision2/data/files/b20e43e6-2cf7-1af2-3665-01016cb2daba/stream?format=pcap)",
                "FileUser": "admin",
                "Status": "DownloadFinished",
                "Task": "DownloadPCAP"
            }
        }
    },
    "InfoFile": {
        "EntryID": "4450@59d61022-f169-427a-8767-77ab234fa692",
        "Extension": "pcap",
        "Info": "pcap",
        "Name": "event-1586976861.pcap",
        "Size": 5722924,
        "Type": "tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 16384)"
    }
}
Human Readable Output

EndaceResult

Task FileName Status Error FileSize FileType FileUser FileURL
DownloadPCAP event-1586976861.pcap DownloadFinished NoError 6.29MB archive_file admin Endace PCAP URL

endace-delete-archived-file


Delete an archived file from EndaceProbe.

Base Command

endace-delete-archived-file

Input
Argument Name Description Required
archived_filename Base name of the archived file to be deleted on EndaceProbe. Filename must be without any extension. Refer to Endace.Archive.Response.FileName field Required
Context Output
Path Type Description
Endace.ArchivedFile.Delete.FileName String Filename of the deleted archived file
Endace.ArchivedFile.Delete.Status String Archived File Delete task status
Endace.ArchivedFile.Delete.Error String Delete Error
Command Example

!endace-delete-archived-file archived_filename="event-1586976861"

Context Example
{
    "Endace": {
        "ArchivedFile": {
            "Delete": {
                "Error": "NoError",
                "FileName": "event-1586976861",
                "Status": "FileDeleted",
                "Task": "DeleteArchivedFile"
            }
        }
    }
}
Human Readable Output

EndaceResult

Task FileName Status Error
DeleteArchivedFile event-1586976861 FileDeleted NoError

Configuration parameters

  • applianceurl — EndaceProbe URL (e.g. https://<fqdn/ip>[:port]) (required)
  • credentials — Username (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • hostname — EndaceProbe System Hostname (required)

Commands (8)

  • endace-create-archive

    Create an archive task to archive packets of interest on EndaceProbe. Archived packets can later be downloaded from EndaceProbe as a PCAP file. Archived Files never expire. Allowed chars are text, numbers, dash and underscore.

  • endace-create-search

    Create a search task on EndaceProbe. Search is issued against all Rotation Files on EndaceProbe.

  • endace-delete-archive-task

    delete archive task

  • endace-delete-archived-file

    Delete an archived file from EndaceProbe.

  • endace-delete-search-task

    Delete search task

  • endace-download-pcap

    Download a copy of the PCAP file from EndaceProbe if PCAP file size is within the threshold value defined by filesizelimit.

  • endace-get-archive-status

    get status of archived task

  • endace-get-search-status

    Get search status from EndaceProbe. This command can be polled in a loop until response is received or polling timer is over.

import calendar
import json
import re
import time

import requests
import urllib3
from CommonServerPython import *

from CommonServerUserPython import *


class EndaceVisionAPIAdapter:
    """Adapter for EndaceWebSession which allows
    a simpler interface to the private Vision API.

    Implements some of the request methods, so the interface
    in most cases should be the same.

    Ensures CSRF tokens are sent correctly in requests.
    """

    API_BASE = "/vision2/data"

    def __init__(self, endace_session):
        self.endace_session = endace_session

    def request(self, method, path, **kwargs):
        headers = {}
        if method == "POST":
            csrf_cookie = self.endace_session.requests.cookies.get("vision2_csrf_cookie")
            if csrf_cookie:
                headers = {"XSRF-csrf-token": str(csrf_cookie)}
        try:
            r = self.endace_session.requests.request(
                method, self.endace_session.page(f"{self.API_BASE}/{path}"), headers=headers, **kwargs
            )
            r.raise_for_status()
        except requests.exceptions.HTTPError as err:
            return err.response.status_code
        else:
            return r

    def get(self, path, **kwargs):
        return self.request("GET", path, **kwargs)

    def post(self, path, **kwargs):
        return self.request("POST", path, **kwargs)

    def put(self, path, **kwargs):
        return self.request("PUT", path, **kwargs)

    def delete(self, path, **kwargs):
        return self.request("DELETE", path, **kwargs)


class EndaceWebSession:
    LOGIN_PAGE = "/admin/launch?script=rh&template=login"
    LOGIN_ACTION = "/admin/launch?script=rh&template=login&action=login"
    LOGOUT_PAGE = "/admin/launch?script=rh&template=logout&action=logout"

    def __init__(self, app_url=None, username=None, password=None, cert_verify=False):
        self.app_url = app_url
        self.username = username
        self.password = password
        self.requests = None
        self.verify = cert_verify

    def __enter__(self):
        self.requests = self._create_login_session()
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        self.close()
        _ = exception_type
        _ = exception_value
        _ = traceback

    def close(self):
        self.logout()
        if self.requests:
            self.requests.close()
            self.requests = None

    def page(self, path="/"):
        return f"{self.app_url}{path}"

    def logout(self):
        if self.requests:
            logout = self.requests.get(self.page(self.LOGOUT_PAGE))
            if logout.status_code == 200:
                return True
            else:
                raise Exception(f"logout to {self.app_url} failed")
        else:
            return False

    def _create_login_session(self):
        """
        Creates a requests.Session object with correct cookies
        setup for an OSm session.
        """
        sess = requests.Session()
        r = sess.get(self.page(self.LOGIN_PAGE), verify=self.verify)
        if r.status_code == 200:
            csrf_token = EndaceWebSession.find_csrf_token_login(r.content)
            if csrf_token is None:
                raise Exception("Could not find CSRF token")
            # Submit login form
            login_result = sess.post(
                self.page(self.LOGIN_ACTION),
                data={
                    "_csrf": csrf_token,
                    "d_user_id": "user_id",
                    "t_user_id": "string",
                    "c_user_id": "string",
                    "e_user_id": "true",
                    "f_user_id": str(self.username),
                    "f_password": str(self.password),
                    "Login": "Login",
                },
                headers={"Content-type": "application/x-www-form-urlencoded"},
            )
            if login_result.status_code == 200 and len(sess.cookies) > 0:
                return sess
            else:
                raise Exception("Login failed")
        else:
            raise Exception("Login failed")

    @staticmethod
    def find_csrf_token_login(page_content=""):
        return EndaceWebSession.fetch_csrf_token(str(page_content), r"(class=\"csrf-token\").*?\/>")

    @staticmethod
    def fetch_csrf_token(input_text, pattern):
        """
        Fetch CSRF Token from the given input html tag by applying the pattern passed
        :param input_text:
        :param pattern:
        :return:
        """

        m = re.search(pattern=pattern, string=input_text)
        if not m:
            return None

        match_input_tag = m.group(0)

        if match_input_tag:
            for replace_tag in ["value", "content"]:
                m = re.search(pattern=replace_tag + '=".*"', string=match_input_tag)
                if m:
                    csrf_tag = str(m.group(0))
                    csrf_tag = csrf_tag.replace(replace_tag + "=", "")
                    csrf_tag = csrf_tag.replace('"', "")
                    return csrf_tag.strip()
        return None


class EndaceVisionData:
    def __init__(self, args=None):
        self.args = args

    def build_search_data(self):
        investigation_data = {
            "type": "TrafficBreakdownNG",
            "breakdown_type": "datasource",
            "datasource_guids": ["tag:rotation-file"],
            "start_time": int(self.args["start"]) * 1000,
            "end_time": int(self.args["end"]) * 1000,
            "filter": self.get_filters(),
            "order_by": "bytes",
            "required_aggregates": ["bytes"],
            "order_direction": "desc",
            "other_required": True,
            "points": 10,
            "auto_pivot": True,
            "disable_uvision": False,
            "disable_mvision": False,
        }
        return investigation_data

    def build_archive_data(self):
        archive_data = {
            "filename": self.args["archive_filename"],
            "deduplication": False,
            "bidirection": False,
            "datasources": self.get_datasources(),
            "timerange": self.get_timerange(),
            "filters": self.get_filters(),
            "individualSessionData": False,
        }

        return archive_data

    def get_datasources(self):
        datasources = {
            "key": "tag:rotation-file",
            "datasource": {
                "id": "tag:rotation-file",
                "type": "tag",
                "name": "rotation-file",
                "probeName": "tag",
                "displayName": "tag:rotation-file",
                "status": {"inUse": False, "readers": [], "writers": []},
                "vision": True,
                "mplsLevel1": 1,
                "mplsLevel2": "BOTTOM",
                "metadataTimerange": {
                    "visionObjectType": "timerange",
                    "visionObjectValue": {
                        "start": {"seconds": self.args["start"], "nanoseconds": 0},
                        "end": {"seconds": self.args["end"], "nanoseconds": 0},
                    },
                },
                "packetTimerange": {
                    "visionObjectType": "timerange",
                    "visionObjectValue": {
                        "start": {"seconds": self.args["start"], "nanoseconds": 0},
                        "end": {"seconds": self.args["end"], "nanoseconds": 0},
                    },
                },
                "datasourceIds": self.args["ids"],
            },
            "missing": False,
        }

        return [datasources]

    def get_timerange(self):
        timerange = {
            "visionObjectType": "timerange",
            "visionObjectValue": {
                "start": {"seconds": int(self.args["start"]), "nanoseconds": 0},
                "end": {"seconds": int(self.args["end"]), "nanoseconds": 0},
            },
        }
        return timerange

    def get_filters(self):
        filters = {
            "visionObjectType": "filter",
            "visionObjectValue": {
                "active": "basic",
                "basic": {"visionObjectType": "basicFilter", "visionObjectValue": {"filters": self.get_basicfilters()}},
            },
        }

        return filters

    def get_basicfilters(self):
        allfilterslist = []
        visionobjecttype = "basicFilterDirectionlessIp"
        for filtertype in self.args["filterby"]:
            filterlist = []
            if filtertype == 0:
                visionobjecttype = "basicFilterDirectionlessIp"
                filterlist.append(self.args["ip"])
            if filtertype == 1:
                visionobjecttype = "basicFilterSourceIp"
                for sip in self.args["src_host_list"]:
                    filterlist.append(sip)
            if filtertype == 2:
                visionobjecttype = "basicFilterDestinationIp"
                for dip in self.args["dest_host_list"]:
                    filterlist.append(dip)
            if filtertype == 3:
                visionobjecttype = "basicFilterSourcePort"
                for sport in self.args["src_port_list"]:
                    filterlist.append(sport)
            if filtertype == 4:
                visionobjecttype = "basicFilterDestinationPort"
                for dport in self.args["dest_port_list"]:
                    filterlist.append(dport)
            if filtertype == 5:
                visionobjecttype = "basicFilterIpProtocol"
                filterlist.append(self.args["protocol"])
            if filtertype == 6:
                visionobjecttype = "basicFilterDirectionlessPort"
                filterlist.append(self.args["port"])
            if filtertype == 7:
                visionobjecttype = "basicFilterVlan"
                for vlan in self.args.vlan1list:
                    filterlist.append(vlan)

            visionfilter = {
                "visionObjectType": visionobjecttype,
                "visionObjectValue": {"version": 1, "include": True, "value": filterlist},
            }

            allfilterslist.append(visionfilter)

        return allfilterslist


class EndaceApp:
    delta_time = 120
    wait_time = 5

    def __init__(self, *args):
        self.args = {}

        self.applianceurl = args[0]
        self.username = args[1]
        self.password = args[2]
        self.cert_verify = args[3]
        self.hostname = args[4]

    @staticmethod
    def endace_get_input_arguments(args=None):
        timeframe_converter = {
            "30seconds": 30,
            "1minute": 60,
            "5minutes": 300,
            "10minutes": 600,
            "30minutes": 1800,
            "1hour": 3600,
            "2hours": 7200,
            "5hours": 18000,
            "10hours": 36000,
            "12hours": 43200,
            "1day": 86400,
            "3days": 259200,
            "5days": 432000,
            "1week": 604800,
        }

        function_args = {}

        #  timestamps
        function_args["start"] = args.get("start")
        if args.get("start"):
            #   converting ISO time to epoch time
            function_args["start"] = date_to_timestamp(args.get("start")) / 1000

        function_args["end"] = args.get("end")
        if args.get("end"):
            #   converting ISO time to epoch time
            function_args["end"] = date_to_timestamp(args.get("end")) / 1000

        function_args["timeframe"] = timeframe_converter.get(args.get("timeframe"))

        #   filter params
        #   args.get("directionless IP")
        function_args["ip"] = args.get("ip")
        #   args.get("directionless PORT")
        function_args["port"] = args.get("port")
        #   args.get("src_host_list")
        function_args["src_host_list"] = list(set(argToList(args.get("src_host_list"))))[:10]
        #   args.get("dest_host_list")
        function_args["dest_host_list"] = list(set(argToList(args.get("dest_host_list"))))[:10]
        #   args.get("src_port_list")
        function_args["src_port_list"] = list(set(argToList(args.get("src_port_list"))))[:10]
        #   args.get("dest_port_list")
        function_args["dest_port_list"] = list(set(argToList(args.get("dest_port_list"))))[:10]

        function_args["protocol"] = args.get("protocol")

        #   Doing a sanity check on input function arguments
        #   adding a limit to number of filter items to be passed to 10 max
        if (
            len(function_args["src_host_list"])
            + len(function_args["dest_host_list"])
            + len(function_args["src_port_list"])
            + len(function_args["dest_port_list"])
        ) > 10:
            raise ValueError("Wrong number of filters items - Limit search filters to 10 items")

        if not function_args["ip"] and not function_args["src_host_list"] and not function_args["dest_host_list"]:
            raise ValueError("Wrong or missing value - Src and Dest IP arguments")

        if not function_args["start"] and not function_args["end"] and not function_args["timeframe"]:
            raise ValueError("Wrong arguments - StartTime, EndTime or TimeFrame is invalid ")
        elif (not function_args["start"] or not function_args["end"]) and not function_args["timeframe"]:
            raise ValueError("Wrong arguments - either StartTime or EndTime or Timeframe is invalid ")

        if function_args["start"] and function_args["end"] and function_args["start"] == function_args["end"]:
            raise ValueError("Wrong arguments - value of StartTime and EndTime argument - both are same")

        #   Logical options for search time:
        #   1) start and stop time is not provided: will work like search Last n seconds, n = timeframe
        #   2) only either start or stop time is provided: Search will be either +/- of timeframe of the given time
        #   3) Both start and stop time is provided: Search using start and stop time, ignore timeframe

        if not function_args["start"] and not function_args["end"]:
            function_args["end"] = int(calendar.timegm(time.gmtime()) - 10)
            function_args["start"] = int(function_args["end"]) - int(function_args["timeframe"])
        elif function_args["start"] and not function_args["end"]:
            if int(function_args["start"]) > (calendar.timegm(time.gmtime()) - 10):
                raise ValueError(f'Wrong argument - value of StartTime - {args.get("start")} UTC cannot be in future')
            function_args["end"] = int(function_args["start"]) + int(function_args["timeframe"])
            if int(function_args["end"]) > (calendar.timegm(time.gmtime()) - 10):
                raise ValueError(
                    "Wrong argument - value of EndTime - adjust timeframe argument such that EndTime is not in future"
                )
        elif not function_args["start"] and function_args["end"]:
            if int(function_args["end"]) > (calendar.timegm(time.gmtime()) - 10):
                raise ValueError(f'Wrong argument - value of EndTime - {args.get("end")} UTC cannot be in future')
            function_args["start"] = int(function_args["end"]) - int(function_args["timeframe"])

        #   search time boundary check
        if function_args["end"] < function_args["start"]:
            raise ValueError("Wrong argument - value of EndTime - cannot be before StartTime")
        if int(function_args["start"]) > (calendar.timegm(time.gmtime()) - 10):
            raise ValueError(f'Wrong argument - value of StartTime - {args.get("start")} UTC cannot be in future')
        if int(function_args["end"]) > (calendar.timegm(time.gmtime()) - 10):
            raise ValueError(f'Wrong argument - value of EndTime - {args.get("end")} UTC cannot be in future')

        return function_args

    @staticmethod
    def handle_error_notifications(eperror):
        error_dict = {
            "common.notAuthorized": "Authorization issue due to incorrect RBAC roles on EndaceProbe",
            "duration.invalidInput": "Fix Invalid search starttime, endtime or timeframe",
            "timestamp.invalidInput": "Fix Invalid search starttime, endtime or timeframe",
            "query.serviceLayerError": "One of the search parameters have invalid syntax",
            "filter.invalidFilterFormat": "One of the search parameters have invalid syntax",
            "download.emptyDatasource": "Empty Packet Datasource, this happens when packet data has "
            "rotated out but metadata is still available due to incorrect "
            "datastore sizing configuration. Contact support@endace.com for "
            "any technical assistance on optimal datasource sizing",
            "FileNotFound": "File not found on EndaceProbe",
            "SearchTimeOut": "Search query has timed out. Improve search by narrowing search filter "
            "items - IP addresses, Port or Timeframe. If problem persists "
            "contact support@endace.com to review EndaceProbe configuration",
        }

        raise Exception(
            error_dict.get(eperror, f"try again. contact support@endace.com and report {eperror} if problem persists")
        )

    #  search
    def create_search_task(self, args=None):
        """create a search task on Endace Probe
        Args: dict
        Returns:
            Dictionary context data in response to the command execution
        """
        input_args_dict = args
        input_args_dict.update({"filterby": []})

        result = {"Task": "CreateSearchTask", "Status": "Started", "Error": "NoError", "JobID": ""}

        #   Filter order
        if input_args_dict["ip"]:
            input_args_dict["filterby"].append(0)
        if input_args_dict["src_host_list"]:
            input_args_dict["filterby"].append(1)
        if input_args_dict["dest_host_list"]:
            input_args_dict["filterby"].append(2)
        if input_args_dict["src_port_list"]:
            input_args_dict["filterby"].append(3)
        if input_args_dict["dest_port_list"]:
            input_args_dict["filterby"].append(4)
        if input_args_dict["protocol"]:
            input_args_dict["filterby"].append(5)
        if input_args_dict["port"]:
            input_args_dict["filterby"].append(6)

        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            rd = api.get(path)
            if rd.status_code == 200:
                path = "queries/"
                evid = EndaceVisionData(input_args_dict)
                rp = api.post(path, json=evid.build_search_data())
                if rp.status_code == 200:
                    try:
                        response = rp.json()
                    except json.decoder.JSONDecodeError:
                        raise Exception(f"JsonDecodeError - path {path}")
                    else:
                        meta = response.get("meta", {})
                        payload = response.get("payload")
                        if meta:
                            meta_error = meta.get("error")
                            if meta_error is not None:
                                if meta_error is not False:
                                    result["Status"] = "Failed"
                                    result["Error"] = str(meta_error)
                                else:
                                    if payload is not None:
                                        result["JobID"] = payload
                                    else:
                                        result["Status"] = "Failed"
                                        result["Error"] = f"ServerError - empty payload data from {path}"
                        else:
                            result["Status"] = "Failed"
                            result["Error"] = f"ServerError - empty meta data from {path}"
                else:
                    result["Status"] = "Failed"
                    result["Error"] = f"HTTP {rp.status_code} to /{path}"
            else:
                result["Status"] = "Failed"
                result["Error"] = f"HTTP {rd.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result

    def get_search_status(self, args=None):
        """get status of the search task on Endace Probe
        Args: string - Job ID
        Returns:
            Dictionary context data in response to the command execution
        """
        result = {
            "Task": "GetSearchStatus",
            "Status": "complete",
            "Error": "NoError",
            "JobProgress": "0",
            "DataSources": [],
            "TotalBytes": 0,
            "JobID": args,
        }

        matching_data = 0
        keys = []
        values = []
        id_to_key_dict = {}
        app_dict = {}

        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            rd = api.get(path)
            if rd.status_code == 200:
                path = "queries/" + args
                progress_status = True
                query_time = calendar.timegm(time.gmtime())
                while progress_status:
                    #  progress loop
                    #  exit when whichever occurrs before, search timeout or search progress = 100% or search returns an
                    #   unknown value
                    current_time = calendar.timegm(time.gmtime())
                    if current_time - query_time > self.delta_time:
                        progress_status = False
                        result["Status"] = "InProgress"
                        result["Error"] = "SearchTimeOut"
                    else:
                        rj = api.get(path)
                        if rj.status_code == 200:
                            #  Check metadata for no error.
                            try:
                                response = rj.json()
                            except json.decoder.JSONDecodeError:
                                raise Exception(f"JsonDecodeError - path {path}")
                            else:
                                meta = response.get("meta", {})
                                if meta:
                                    meta_error = meta.get("error")
                                    if meta_error is not None:
                                        if meta_error is not False:
                                            progress_status = False
                                            result["Status"] = "complete"
                                            result["Error"] = str(meta_error)
                                        else:
                                            #  check payload for no error
                                            payload = response.get("payload")
                                            if payload is not None:
                                                progress = payload.get("progress")
                                                if progress is not None:
                                                    result["JobProgress"] = str(progress)
                                                    #  check if the Search Job has finished.
                                                    #  if so, return a data dict back to Demisto
                                                    #  if No, Wait and loop in to run another status check,
                                                    #   until "self.delta_time" has elapsed
                                                    payload_data = payload.get("data")
                                                    if payload_data is not None and int(progress) == 100:
                                                        progress_status = False
                                                        for data_map_dict in payload_data:
                                                            id_to_key_dict[data_map_dict["id"]] = data_map_dict["name"]

                                                        for top_key in payload["top_keys"]:
                                                            keys.append(id_to_key_dict[top_key])

                                                        #   Calculate Total matching MBytes
                                                        for top_value in payload["top_values"]:
                                                            matching_data = matching_data + int(top_value)
                                                            values.append(str(top_value))

                                                        result["TotalBytes"] = int(matching_data)

                                                        for index in range(len(keys)):
                                                            app_dict[keys[index]] = values[index] + " Bytes"

                                                        result["Status"] = str(payload["state"])
                                                        result["DataSources"] = keys
                                            else:
                                                progress_status = False
                                                result["Status"] = "Failed"
                                                result["Error"] = f"ServerError - empty payload data from {path}"
                                else:
                                    progress_status = False
                                    result["Status"] = "Failed"
                                    result["Error"] = f"ServerError - empty meta data from {path}"
                        else:
                            progress_status = False
                            result["Status"] = rj.status_code
                            result["Error"] = f"ServerError - HTTP {rj.status_code} to /{path}"
                    #   wait time before next run
                    time.sleep(self.wait_time)
            else:
                result["Status"] = "Failed"
                result["Error"] = f"ServerError - HTTP {rd.status_code} to /{path}"

        if result["Status"] != "complete":
            self.handle_error_notifications(result["Error"])
        return result

    def delete_search_task(self, args=None):
        """delete the search task on Endace Probe
        Args: string - Job ID
        Returns:
            dict with delete status
        """
        result = {"Task": "DeleteSearchTask", "Status": "Deleted", "Error": "NoError", "JobID": args}

        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            rd = api.get(path)
            if rd.status_code == 200:
                path = "queries/" + args
                dr = api.delete(path)
                if dr.status_code == 200:
                    try:
                        response = dr.json()
                    except json.decoder.JSONDecodeError:
                        raise Exception(f"JsonDecodeError - path {path}")
                    else:
                        meta = response.get("meta", {})
                        if meta:
                            meta_error = meta.get("error")
                            if meta_error is not None and meta_error is not False:
                                result["Status"] = "complete"
                                result["Error"] = str(meta_error)
                        else:
                            result["Status"] = "Failed"
                            result["Error"] = f"ServerError - empty meta data from {path}"
            else:
                result["Status"] = "Failed"
                result["Error"] = f"ServerError - HTTP {rd.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result

    #  archive
    def create_archive_task(self, args=None):
        """create an archive task on Endace Probe
        Args: dict
        Returns:
            Dictionary context data in response to the command execution
        """
        input_args_dict = args
        input_args_dict.update({"archive_filename": (args.get("archive_filename") + "-" + str(calendar.timegm(time.gmtime())))})
        input_args_dict.update({"filterby": []})
        input_args_dict.update({"ids": ""})

        result = {
            "Task": "CreateArchiveTask",
            "Status": "Started",
            "Error": "NoError",
            "JobID": "",
            "Start": args.get("start"),
            "End": args.get("end"),
            "P2Vurl": "",
            "FileName": args["archive_filename"],
        }

        datasource = self.hostname + ":" + input_args_dict["archive_filename"]

        start_time_in_ms = str(int(input_args_dict["start"]) * 1000)
        end_time_in_ms = str(int(input_args_dict["end"]) * 1000)

        p2v_url = (
            f'{self.applianceurl}/vision2/pivotintovision/?datasources={datasource}'
            f'&title={result["FileName"]}&start={start_time_in_ms}&end={end_time_in_ms}'
            f'&tools=trafficOverTime_by_app%2Cconversations_by_ipaddress'
        )

        #   Endace Filter order
        if input_args_dict["ip"]:
            input_args_dict["filterby"].append(0)
            p2v_url = p2v_url + "&ip=" + input_args_dict["ip"]
        if input_args_dict["src_host_list"]:
            input_args_dict["filterby"].append(1)
            src_ip = ""
            for ip in input_args_dict["src_host_list"]:
                src_ip = src_ip + "," + ip
            src_ip = src_ip[1:]
            p2v_url = p2v_url + "&sip=" + src_ip
        if input_args_dict["dest_host_list"]:
            input_args_dict["filterby"].append(2)
            dest_ip = ""
            for ip in input_args_dict["dest_host_list"]:
                dest_ip = dest_ip + "," + ip
            dest_ip = dest_ip[1:]
            p2v_url = p2v_url + "&dip=" + dest_ip
        if input_args_dict["src_port_list"]:
            input_args_dict["filterby"].append(3)
            port = ""
            for sport in input_args_dict["src_port_list"]:
                port = port + "," + sport
            port = port[1:]
            p2v_url = p2v_url + "&sport=" + port
        if input_args_dict["dest_port_list"]:
            input_args_dict["filterby"].append(4)
            port = ""
            for dport in input_args_dict["dest_port_list"]:
                port = port + "," + dport
            port = port[1:]
            p2v_url = p2v_url + "&dport=" + port
        if input_args_dict["protocol"]:
            input_args_dict["filterby"].append(5)
        if input_args_dict["port"]:
            input_args_dict["filterby"].append(6)
            p2v_url = p2v_url + "&port=" + input_args_dict["port"]

        evid = EndaceVisionData(input_args_dict)
        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            #  Extract list of rotationfiles datasources and exclude previously archived files
            rotfile_ids = []

            api = EndaceVisionAPIAdapter(sess)
            path = "datasources"
            rd = api.get(path)
            try:
                response = rd.json()
            except json.decoder.JSONDecodeError:
                raise Exception(f"JsonDecodeError - path {path}")
            else:
                if rd.status_code == 200:
                    payload = response.get("payload")
                    for rotfile in payload:
                        if rotfile["type"] == "rotation_file_v2":
                            rotfile_ids.append(rotfile["id"])

                    input_args_dict["ids"] = rotfile_ids

                    path = "archive/"
                    rp = api.post(path, json=evid.build_archive_data())
                    if rp.status_code == 200:
                        try:
                            response = rp.json()
                        except json.decoder.JSONDecodeError:
                            raise Exception(f"JsonDecodeError - path {path}")
                        else:
                            meta = response.get("meta", {})
                            payload = response.get("payload")
                            if meta:
                                meta_error = meta.get("error")
                                if meta_error is not None:
                                    if meta_error is not False:
                                        result["Status"] = "Failed"
                                        result["Error"] = str(meta_error)
                                    else:
                                        if payload is not None:
                                            result["JobID"] = payload
                                            result["P2Vurl"] = f"[Endace PivotToVision URL]({p2v_url})"
                                        else:
                                            result["Status"] = "Failed"
                                            result["Error"] = f"ServerError - empty payload data from {path}"
                            else:
                                result["Status"] = "Failed"
                                result["Error"] = f"ServerError - empty meta data from {path}"
                    else:
                        result["Status"] = "Failed"
                        result["Error"] = f"HTTP {rd.status_code} to /{path}"
                else:
                    result["Status"] = "Failed"
                    result["Error"] = f"HTTP {rd.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result

    def get_archive_status(self, args=None):
        """get status of the archive task on Endace Probe
        Args: string - Archived File Name
        Returns:
            Dictionary context data in response to the command execution
        """
        result = {
            "Task": "GetArchiveStatus",
            "Error": "NoError",
            "Status": "InProgress",
            "FileName": args["archive_filename"],
            "FileSize": 0,
        }

        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            progress_status = True
            query_time = calendar.timegm(time.gmtime())

            while progress_status:
                #   wait time before next run
                time.sleep(self.wait_time)
                current_time = calendar.timegm(time.gmtime())
                if current_time - query_time > self.delta_time:
                    progress_status = False
                    result["Status"] = "InProgress"

                rf = api.get(path)
                if rf.status_code == 200:
                    try:
                        response = rf.json()
                    except json.decoder.JSONDecodeError:
                        raise Exception(f"JsonDecodeError - path {path}")
                    else:
                        meta = response.get("meta", {})
                        payload = response.get("payload")
                        if meta:
                            meta_error = meta["error"]
                            if meta_error is not None:
                                if meta_error is not False:
                                    progress_status = False
                                    result["Status"] = "InProgress"
                                    result["Error"] = str(meta_error)
                                else:
                                    #   progress loop
                                    #   exit at timeout or archive finished
                                    #  archive_payload = payload
                                    for file in payload:
                                        if args["archive_filename"] == file["name"]:
                                            result["FileName"] = file["name"]
                                            if not file["status"]["inUse"]:
                                                #  archive finished
                                                progress_status = False
                                                result["FileSize"] = file["usage"]
                                                result["Status"] = "Finished"
                                            else:
                                                result["Status"] = "InProgress"
                                            break

                        else:
                            progress_status = False
                            result["Status"] = "Failed"
                            result["Error"] = f"ServerError - empty meta data from {path}"
                else:
                    progress_status = False
                    result["Status"] = rf.status_code
                    result["Error"] = f"ServerError - HTTP {rf.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result

    def delete_archive_task(self, args=None):
        """delete the search task on Endace Probe
        Args: string - Job ID
        Returns: dict with delete status
        """
        result = {"Task": "DeleteArchiveTask", "Error": "NoError", "Status": "Deleted", "JobID": args}

        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            rd = api.get(path)
            if rd.status_code == 200:
                path = "queries/" + args
                dr = api.delete(path)
                if dr.status_code == 200:
                    try:
                        response = dr.json()
                    except json.decoder.JSONDecodeError:
                        raise Exception(f"JsonDecodeError - path {path}")
                    else:
                        meta = response.get("meta", {})
                        if meta:
                            meta_error = meta.get("error")
                            if meta_error is not None and meta_error is not False:
                                result["Status"] = "complete"
                                result["Error"] = str(meta_error)
                        else:
                            result["Status"] = "Failed"
                            result["Error"] = f"ServerError - empty meta data from {path}"
            else:
                result["Error"] = rd.status_code
                result["Error"] = f"ServerError - HTTP {rd.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result

    def delete_archived_file(self, args=None):
        """Delete archived file on Endace Probe
        Args: string - Archived File Name
        Returns:
            Dictionary context data in response to the command execution
        """
        result = {
            "Task": "DeleteArchivedFile",
            "Error": "NoError",
            "Status": "FileNotFound",
            "FileName": args["archived_filename"],
        }

        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            rf = api.get(path)
            if rf.status_code == 200:
                try:
                    response = rf.json()
                except json.decoder.JSONDecodeError:
                    raise Exception(f"JsonDecodeError - path {path}")
                else:
                    meta = response.get("meta", {})
                    payload = response.get("payload")
                    if meta:
                        meta_error = meta["error"]
                        if meta_error is not None:
                            if meta_error is not False:
                                result["Status"] = "FileNotFound"
                                result["Error"] = str(meta_error)
                            else:
                                #   Delete archived File
                                for file in payload:
                                    if result["FileName"] == file["name"] and len(file["id"]) and file["type"] == "archive_file":
                                        #   File available to delete
                                        archived_file_path = f'files?_={calendar.timegm(time.gmtime())!s}000&files={file["id"]}'
                                        df = api.delete(archived_file_path)
                                        try:
                                            response = df.json()
                                        except json.decoder.JSONDecodeError:
                                            raise
                                        else:
                                            meta = response.get("meta", {})
                                            if df.status_code == 200:
                                                if meta["error"] is None:
                                                    result["Status"] = "FileNotFound"
                                                    result["Error"] = meta["error"]
                                                else:
                                                    result["Status"] = "FileDeleted"
                                            else:
                                                result["Error"] = f"ServerError - HTTP {rf.status_code} to /{path}"

                    else:
                        result["Status"] = "Failed"
                        result["Error"] = f"ServerError - empty meta data from {path}"
            else:
                result["Status"] = "Failed"
                result["Error"] = f"ServerError - HTTP {rf.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result

    #  download
    def download_pcap(self, args=None):
        """download a PCAP file from EndaceProbe
        Args:
            dict - FileName and FileSize
        Returns:
            Dictionary context data in response to the command execution
        """
        result = {
            "Task": "DownloadPCAP",
            "Error": "NoError",
            "Status": "FileNotFound",
            "FileName": args["filename"],
            "FileSize": 0,
            "FileType": "UnKnown",
            "FileURL": "UnKnown",
            "FileUser": "UnKnown",
        }
        with EndaceWebSession(
            app_url=self.applianceurl, username=self.username, password=self.password, cert_verify=self.cert_verify
        ) as sess:
            api = EndaceVisionAPIAdapter(sess)
            path = "files"
            rf = api.get(path)
            if rf.status_code == 200:
                try:
                    response = rf.json()
                except json.decoder.JSONDecodeError:
                    raise Exception(f"JsonDecodeError - path {path}")
                else:
                    meta = response.get("meta", {})
                    payload = response.get("payload")
                    if meta:
                        meta_error = meta["error"]
                        if meta_error is not None:
                            if meta_error is not False:
                                result["Status"] = "FileNotFound"
                                result["Error"] = str(meta_error)
                            else:
                                #   Download PCAP File
                                for file in payload:
                                    if result["FileName"] == file["name"] and len(file["id"]):
                                        file_numerical_part = float(re.findall(r"[\d\.]+", file["usage"])[0])

                                        if "KB" in file["usage"]:
                                            filesize = file_numerical_part * 0.001
                                        elif "GB" in file["usage"]:
                                            filesize = file_numerical_part * 1000
                                        elif "TB" in file["usage"]:
                                            filesize = file_numerical_part * 1000000
                                        else:
                                            filesize = file_numerical_part * 1

                                        if filesize <= int(args["filesizelimit"]):
                                            result["FileName"] = file["name"] + ".pcap"
                                            if not file["status"]["inUse"]:
                                                #   File available to download
                                                pcapfile_url_path = f"files/{file['id']}/stream?format=pcap"
                                                d = api.get(pcapfile_url_path)
                                                if d.status_code == 200:
                                                    demisto.results(
                                                        fileResult(
                                                            f'{result["FileName"]}',
                                                            d.content,
                                                            file_type=entryTypes["entryInfoFile"],
                                                        )
                                                    )

                                                    result["FileURL"] = (
                                                        f"[Endace PCAP URL]"
                                                        f"({self.applianceurl}/vision2/data/"
                                                        f"{pcapfile_url_path})"
                                                    )

                                                    result["FileSize"] = file["usage"]
                                                    result["Status"] = "DownloadFinished"
                                                    result["FileType"] = file["type"]
                                                    result["FileUser"] = file["user"]
                                                else:
                                                    result["Status"] = "FileNotFound"
                                                    result["Error"] = f"ServerError - HTTP {rf.status_code} to /{path}"
                                            else:
                                                result["Status"] = "FileInUse"
                                        else:
                                            result["Status"] = "FileExceedsSizeLimit"
                    else:
                        result["Status"] = "Failed"
                        result["Error"] = f"ServerError - empty meta data from {path}"
            else:
                result["Status"] = "Failed"
                result["Error"] = f"ServerError - HTTP {rf.status_code} to /{path}"

        if result["Status"] == "Failed":
            self.handle_error_notifications(result["Error"])
        return result


""" COMMAND FUNCTION """
#  Search Commands


def endace_create_search_command(app, args):
    """create search command function
    Function Params:
        app: Endace App Client
        args:
            start time: in ISO 8601 format
            end time: in ISO 8601 format
            ip: comma separated ips to search for
            port: comma separated ports to search for
            src ips: comma separated source ips to search for. Src IPs will be OR'd
            dest ips: comma separated destination ips to search for. Destination IPs will be OR'd
            src ports: comma separated source ports to search for. Src Ports will be OR'd
            dest ports: comma separated destination ports to search for. Destination ports will be OR'd
            protocol: TCP or UDP protocol value
            search window: number in second
    Returns: The Search JOB ID.
    Raises:
        ValueError: If input argument is in wrong format.

    """
    if len(args.values()):
        function_args = app.endace_get_input_arguments(args)

        #  calling search task function of app instance
        result = app.create_search_task(function_args)

        #  create entry context to return to Demisto
        output = {"Endace.Search.Task(val.JobID == obj.JobID)": result}
        table_header = ["Task", "JobID", "Status", "Error"]
        readable_output = tableToMarkdown("EndaceResult", [result], headers=table_header, removeNull=False)
        raw_response = result

        return readable_output, output, raw_response
    else:
        raise ValueError("No arguments were provided to search by, at least one must be provided")


def endace_delete_search_task_command(app, args):
    """
    Delete search Job on Endace Probe
    Function Params:
    app: Endace App Client
    args: string - Search JOB id
    Returns:
        Null
    Raises:
         ValueError: If input argument is in wrong format
         exception: If delete command fails
    """

    jobid = args.get("jobid")
    if len(re.findall(r"([0-9a-fA-F]+)", jobid)) == 5:
        #   calling search status function of app instance
        result = app.delete_search_task(jobid)

        #   create entry context to return to Demisto
        output = {"Endace.Search.Delete(val.JobID == obj.JobID)": result}
        table_header = ["Task", "JobID", "Status", "Error"]
        readable_output = tableToMarkdown("EndaceResult", result, headers=table_header, removeNull=False)
        raw_response = result
        return readable_output, output, raw_response
    else:
        raise ValueError("Incorrect JOB ID provided")


def endace_get_search_status_command(app, args):
    """Poll search task on Endace Probe by given Job ID
    Function Params:
    app: Endace App Client
    args: Search JobID
    Returns:
          Dictionary context data in response to the command execution
    Raises:
         ValueError: If input argument is in wrong format.
    """

    jobid = args.get("jobid")
    if len(re.findall(r"([0-9a-fA-F]+)", jobid)) == 5:
        #   calling search status function of app instance
        result = app.get_search_status(jobid)

        #   create entry context to return to Demisto
        output = {"Endace.Search.Response(val.JobID == obj.JobID)": result}
        table_header = ["Task", "JobID", "Status", "Error", "JobProgress", "DataSources", "TotalBytes"]
        readable_output = tableToMarkdown("EndaceResult", result, headers=table_header, removeNull=False)
        raw_response = result

        return readable_output, output, raw_response

    else:
        raise ValueError("Wrong JOB ID provided")


#  Archive Commands


def endace_create_archive_command(app, args):
    """create archive command function
    Function Params:
        app: Endace App Client
        args:
            start time: in ISO 8601 format
            end time: in ISO 8601 format
            ip: comma separated ips to search for
            port: comma separated ports to search for
            src ips: comma separated source ips to search for. Src IPs will be OR'd
            dest ips: comma separated destination ips to search for. Destination IPs will be OR'd
            src ports: comma separated source ports to search for. Src Ports will be OR'd
            dest ports: comma separated destination ports to search for. Destination ports will be OR'd
            protocol: TCP or UDP protocol value
            search window: number representing seconds
            archive_filename: string representing archive filename
    Returns: The Archive JOB ID.
    Raises:
        ValueError: If input argument is in wrong format.

    """
    if len(args.values()):
        #   archive file name
        if re.fullmatch(r"[\w0-9_-]+", args.get("archive_filename")) is None:
            raise ValueError("Wrong format of archive_filename. text, numbers, underscore or dash is supported")

        function_args = app.endace_get_input_arguments(args)
        function_args["archive_filename"] = args.get("archive_filename")

        #  calling archive task function of app instance
        result = app.create_archive_task(function_args)

        #  create entry context to return to Demisto
        output = {"Endace.Archive.Task(val.JobID == obj.JobID)": result}
        table_header = ["Task", "FileName", "P2Vurl", "Status", "Error", "JobID"]
        readable_output = tableToMarkdown("EndaceResult", [result], headers=table_header, removeNull=False)
        raw_response = result
        return readable_output, output, raw_response
    else:
        raise ValueError(
            "No arguments were provided to search by, at least one Filter item, "
            "either start/end time or timeframe is required "
        )


def endace_delete_archive_task_command(app, args):
    """
    Delete archive Job on Endace Probe
    Function Params:
    app: Endace App Client
    args: Archive JOB id - string
    Returns:
        Null
    Raises:
         ValueError: If input argument is in wrong format
         exception: If delete command fails
    """
    jobid = args.get("jobid")
    if re.fullmatch(r"[0-9a-zA-Z\-]+", jobid) is not None:
        #   calling delete archive task function of app instance
        result = app.delete_archive_task(jobid)

        #   create entry context to return to Demisto
        output = {"Endace.Archive.Delete(val.JobID == obj.JobID)": result}
        table_header = ["Task", "JobID", "Status", "Error"]
        readable_output = tableToMarkdown("EndaceResult", result, headers=table_header, removeNull=False)
        raw_response = result
        return readable_output, output, raw_response
    else:
        raise ValueError("Incorrect JOB ID provided")


def endace_get_archive_status_command(app, args):
    """Poll archive task on Endace Probe by given Job ID
    Function Params:
    app: Endace App Client
    args: archive JobID - string
    Returns:
          Dictionary context data in response to the command execution
    Raises:
         ValueError: If input argument is in wrong format.
    """
    if len(args.values()):
        function_args = {}
        #   archive file name
        if re.fullmatch(r"[\w0-9_-]+", args.get("archive_filename")) is None:
            raise ValueError("Wrong format of archive_filename. text, numbers, underscore or dash is supported")
        function_args["archive_filename"] = args.get("archive_filename")

        #  calling app instance
        result = app.get_archive_status(function_args)

        #   create entry context to return to Demisto
        output = {"Endace.Archive.Response(val.FileName == obj.FileName)": result}
        table_header = ["Task", "FileName", "Status", "Error", "FileSize"]
        readable_output = tableToMarkdown("EndaceResult", result, headers=table_header, removeNull=False)
        raw_response = result
        return readable_output, output, raw_response
    else:
        raise ValueError("Archived FileName must be provided")


def endace_delete_archived_file_command(app, args):
    """Delete archived file on Endace Probe
    Function Params:
    app: Endace App Client
    args: string , archive filename
    Returns:
        Dictionary context data in response to the command execution
    Raises:
        ValueError: If input argument is in wrong format.
    """

    if len(args.values()):
        function_arg = {}
        #   archive file name
        function_arg["archived_filename"] = args.get("archived_filename")

        #   archive file name
        if re.fullmatch(r"[\w0-9_-]+", args.get("archived_filename")) is None:
            raise ValueError("Wrong format of archived_filename. text, numbers, underscore or dash is supported")

        #   calling archive file delete task function of app instance
        result = app.delete_archived_file(function_arg)

        #   create entry context to return to Demisto
        output = {"Endace.ArchivedFile.Delete(val.FileName == obj.FileName)": result}
        table_header = ["Task", "FileName", "Status", "Error"]
        readable_output = tableToMarkdown("EndaceResult", result, headers=table_header, removeNull=False)
        raw_response = result
        return readable_output, output, raw_response
    else:
        raise ValueError("Archived FileName must be provided")


#  Download Command
def endace_download_pcap_command(app, args):
    """download pcap file function
    Function Params:
     app: Endace App Client
     args: dict of
            filename: Name of the file to download
            filesize: limit download to max filesize.
    Returns: Dictionary context data in response to the command execution
    Raises:
        ValueError: If input argument is in wrong format.
    """
    if len(args.values()):
        function_args = {"filename": args.get("filename"), "filesizelimit": args.get("filesizelimit")}

        #   Doing a sanity check on input function arguments
        try:
            int(function_args["filesizelimit"])
        except ValueError:
            raise ValueError("Filesize Limit value is incorrect, must be an integer 1  or greater")
        else:
            if int(function_args["filesizelimit"]) < 1:
                raise ValueError("Filesize Limit value is incorrect, must be an integer 1  or greater")

            #   calling download PCAP function of app instance
            result = app.download_pcap(function_args)

            #   create entry context to return to Demisto

            output = {"Endace.Download.PCAP(val.FileName == obj.FileName)": result}
            table_header = ["Task", "FileName", "Status", "Error", "FileSize", "FileType", "FileUser", "FileURL"]
            readable_output = tableToMarkdown("EndaceResult", [result], headers=table_header, removeNull=False)
            raw_response = result
            return readable_output, output, raw_response
    else:
        raise ValueError("FileName must be provided")


#  Test Command
def endace_test_command(appurl, username, password, insecure):
    if re.match(r"https://[\w\-\.]+\.\w+", appurl) is None:
        raise ValueError("Wrong Appliance URL. Make sure URL is in format of https://<fqdn/ip[:port]>")
    with EndaceWebSession(app_url=appurl, username=username, password=password, cert_verify=insecure):
        return "ok"


#  Main Function
def main():
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    """ COMMANDS + REQUESTS FUNCTIONS """
    command = demisto.command()
    applianceurl = demisto.params().get("applianceurl")
    username = demisto.params().get("credentials").get("identifier")
    password = demisto.params().get("credentials").get("password")
    insecure = not demisto.params().get("insecure", False)
    hostname = demisto.params().get("hostname")

    LOG(f"Command being called is {demisto.command()}")

    try:
        handle_proxy()
        if command == "test-module":
            """
               Returning 'ok' indicates that the the user can login to EndaceProbe successfully with his credentials.
               Returns:
                   'ok' if test passed, anything else will fail the test
            """
            demisto.results(endace_test_command(applianceurl, username, password, insecure))
        else:
            app = EndaceApp(applianceurl, username, password, insecure, hostname)
            """ Command Modules """
            if command == "endace-create-search":
                return_outputs(*endace_create_search_command(app, demisto.args()))
            elif command == "endace-get-search-status":
                return_outputs(*endace_get_search_status_command(app, demisto.args()))
            elif command == "endace-delete-search-task":
                return_outputs(*endace_delete_search_task_command(app, demisto.args()))
            elif command == "endace-create-archive":
                return_outputs(*endace_create_archive_command(app, demisto.args()))
            elif command == "endace-get-archive-status":
                return_outputs(*endace_get_archive_status_command(app, demisto.args()))
            elif command == "endace-delete-archive-task":
                return_outputs(*endace_delete_archive_task_command(app, demisto.args()))
            elif command == "endace-delete-archived-file":
                return_outputs(*endace_delete_archived_file_command(app, demisto.args()))
            elif command == "endace-download-pcap":
                return_outputs(*endace_download_pcap_command(app, demisto.args()))

    except Exception as err:
        err_msg = f"Error in Endace integration: {err}"
        return_error(err_msg, error=err)


#   python2 uses __builtin__ python3 uses builtins
if __name__ == "__builtin__" or __name__ == "builtins":
    main()