class_docstr: Load threat intelligence data from DynamoDB and perform IOC detection
class_docstr_tok: ["Load","threat","intelligence","data","from","DynamoDB","and","perform","IOC","detection"]
docstr: Instance method to query DynamoDB table
  
  Args:
      values (list): A list of string which contains IOC values
  
  Returns:
      A tuple(list, dict)
      list: A list of dict returned from dynamodb
          table query, in the format of
              [
                  {'sub_type': 'c2_domain', 'ioc_value': 'evil.com'},
                  {'sub_type': 'mal_ip', 'ioc_value': '1.1.1.2'},
              ]
      dict: A dict containing unprocesed keys.
docstr_tok: ["Instance","method","to","query","DynamoDB","table","Args",":","values","(","list",")",":","A","list","of","string","which","contains","IOC","values","Returns",":","A","tuple","(","list",",","dict",")","list",":","A","list","of","dict","returned","from","dynamodb","table","query",",","in","the","format","of","[","{","'sub_type","'",":","'c2_domain","'",",","'ioc_value","'",":","'evil.com","'","}",",","{","'sub_type","'",":","'mal_ip","'",",","'ioc_value","'",":","'","1.1.1.2","'","}",",","]","dict",":","A","dict","containing","unprocesed","keys","."]
code: def _query(self, values):
      """Instance method to query DynamoDB table
  
      Args:
          values (list): A list of string which contains IOC values
  
      Returns:
          A tuple(list, dict)
          list: A list of dict returned from dynamodb
              table query, in the format of
                  [
                      {'sub_type': 'c2_domain', 'ioc_value': 'evil.com'},
                      {'sub_type': 'mal_ip', 'ioc_value': '1.1.1.2'},
                  ]
          dict: A dict containing unprocesed keys.
      """
  
      @backoff.on_predicate(
          backoff.fibo,
          lambda resp: bool(
              resp["UnprocessedKeys"]
          ),  # retry if this is true
          max_tries=2,  # only retry unprocessed key 2 times max
          on_backoff=backoff_handler(),
          on_success=success_handler(),
          on_giveup=giveup_handler(),
      )
      @backoff.on_exception(
          backoff.expo,
          self.EXCEPTIONS_TO_BACKOFF,
          max_tries=self.BACKOFF_MAX_RETRIES,
          giveup=self._exceptions_to_giveup,
          on_backoff=backoff_handler(),
          on_success=success_handler(),
          on_giveup=giveup_handler(),
      )
      def _run_query(query_values, results):
          query_keys = [
              {self.PRIMARY_KEY: {"S": ioc}}
              for ioc in query_values
              if ioc
          ]
  
          response = self._dynamodb.batch_get_item(
              RequestItems={
                  self._table: {
                      "Keys": query_keys,
                      "ProjectionExpression": self.PROJECTION_EXPRESSION,
                  }
              }
          )
  
          results.extend(
              self._deserialize(response["Responses"].get(self._table))
          )
  
          # Log this as an error for now so it can be picked up in logs
          if response["UnprocessedKeys"]:
              LOGGER.error(
                  "Retrying unprocessed keys in response: %s",
                  response["UnprocessedKeys"],
              )
              # Strip out the successful keys so only the unprocesed ones are retried.
              # This changes the list in place, so the called function sees the updated list
              self._remove_processed_keys(
                  query_values,
                  response["UnprocessedKeys"][self._table]["Keys"],
              )
  
          return response
  
      results = []
  
      _run_query(values, results)
  
      return results
  
imports: from collections import defaultdict
  from os import environ
  import backoff
  import boto3
  from boto3.dynamodb.types import TypeDeserializer
  from botocore.exceptions import ClientError, ParamValidationError
  from netaddr import IPNetwork
  from streamalert.shared.backoff_handlers import backoff_handler, success_handler, giveup_handler
  from streamalert.shared.logger import get_logger
  from streamalert.shared.normalize import Normalizer
  from streamalert.shared.utils import in_network, valid_ip
label_desc: Use image node_id 8 for calling the ThreatIntel obj's underlying member method code with example usage: obj._query(values) and returns: results, response