def sensor_request_job():
    """Sensor API. Called when a sensor wants a new job"""
    # job request from Dalton Agent
    redis = get_redis()

    try:
        SENSOR_UID = request.args["SENSOR_UID"]
    except Exception:
        SENSOR_UID = "unknown"

    SENSOR_IP = request.remote_addr

    try:
        AGENT_VERSION = request.args["AGENT_VERSION"]
    except Exception:
        AGENT_VERSION = "unknown"

    try:
        SENSOR_ENGINE = request.args["SENSOR_ENGINE"]
    except Exception:
        SENSOR_ENGINE = "unknown"
    try:
        SENSOR_ENGINE_VERSION = request.args["SENSOR_ENGINE_VERSION"]
    except Exception:
        SENSOR_ENGINE_VERSION = "unknown"

    sensor_tech = f"{SENSOR_ENGINE}/{SENSOR_ENGINE_VERSION}"

    SENSOR_CONFIG = None
    if "SENSOR_CONFIG" in request.args.keys():
        try:
            SENSOR_CONFIG = request.args["SENSOR_CONFIG"]
        except Exception:
            SENSOR_CONFIG = None

    if SENSOR_CONFIG and len(SENSOR_CONFIG) > 0:
        sensor_tech += f"/{SENSOR_CONFIG}"

    # update check-in data; use md5 hash of SENSOR_UID.SENSOR_IP
    # note: sensor keys are expired by function clear_old_agents() which removes the sensor
    # when it has not checked in in <x> amount of time (expire time configurable via
    # 'agent_purge_time' parameter in dalton.conf).
    SENSOR_HASH = create_hash([SENSOR_UID, SENSOR_IP])
    redis.sadd("sensors", SENSOR_HASH)
    redis.set(f"{SENSOR_HASH}-uid", SENSOR_UID)
    redis.set(f"{SENSOR_HASH}-ip", SENSOR_IP)
    redis.set(f"{SENSOR_HASH}-time", datetime.datetime.now().strftime("%b %d %H:%M:%S"))
    redis.set(f"{SENSOR_HASH}-epoch", int(time.mktime(time.localtime())))
    redis.set(f"{SENSOR_HASH}-tech", sensor_tech)
    redis.set(f"{SENSOR_HASH}-agent_version", AGENT_VERSION)

    # grab a job! If it doesn't exist, return sleep.
    response = redis.lpop(sensor_tech)
    if response is None:
        return "sleep"
    else:
        respobj = json.loads(response)
        new_jobid = respobj["id"]
        logger.info(
            "Dalton Agent %s grabbed job %s for %s"
            % (SENSOR_UID, new_jobid, sensor_tech)
        )
        # there is a key for each sensor which is ("%s-current_job" % SENSOR_HASH) and has
        #  the value of the current job id it is running.  This value is set when a job is
        #  requested and set to "" when the results are posted.  A sensor can only run
        #  one job at a time so if there is an existing job when the sensor requests a new
        #  job then that means the sensor was interrupted while processing a job and could
        #  not communicate back with the controller.
        existing_job = redis.get("%s-current_job" % SENSOR_HASH)
        # logger.debug("Dalton in sensor_request_job(): job requested, sensor hash %s, new job: %s, existing job: %s" % (SENSOR_HASH, new_jobid, existing_job))
        if existing_job and existing_job != new_jobid:
            set_job_status(redis, existing_job, STAT_CODE_INTERRUPTED)
            set_job_status_msg(
                redis,
                existing_job,
                "Job %s was unexpectedly interrupted while running on the agent; please try submitting the job again."
                % existing_job,
            )
            # these shouldn't be populated but set them to expire just in case to prevent redis memory build up
            set_keys_timeout(redis, existing_job)
        redis.set("%s-current_job" % SENSOR_HASH, new_jobid)
        EXPIRE_VALUE = REDIS_EXPIRE
        if redis.get("%s-teapotjob" % new_jobid):
            EXPIRE_VALUE = TEAPOT_REDIS_EXPIRE
        redis.expire("%s-current_job" % SENSOR_HASH, EXPIRE_VALUE)
        redis.set("%s-start_time" % new_jobid, int(time.time()))
        redis.expire("%s-start_time" % new_jobid, EXPIRE_VALUE)
        set_job_status(redis, new_jobid, STAT_CODE_RUNNING)
        # if a user sees the "Running" message for more than a few dozen seconds (depending on
        #   the size of the pcap(s) and ruleset), then the job is hung on the agent or is going to
        #   timeout. Most likely the agent was killed or died during the job run.
        set_job_status_msg(redis, new_jobid, "Running...")

        # set expire times for keys that are stored on server until job is requested
        redis.expire("%s-submission_time" % new_jobid, EXPIRE_VALUE)
        redis.expire("%s-user" % new_jobid, EXPIRE_VALUE)
        redis.expire("%s-tech" % new_jobid, EXPIRE_VALUE)
        return response
Sensor API. Called when a sensor wants a new job
app/dalton.py