docs: def get_fingerprint_batch(input_list, results={}, default_port=443,
                            delay_time=0.5, max_threads=100):
      """
      This is a parallel version of the TLS fingerprint primitive.
  
      :param input_list: the input is a list of host:ports.
      :param default_port: default port to use when no port specified
      :param delay_time: delay before starting each thread
      :param max_threads: maximum number of concurrent threads
      :return:
      """
      threads = []
      thread_error = False
      thread_wait_timeout = 200
      ind = 1
      total_item_count = len(input_list)
      for row in input_list:
          if len(row.split(":")) == 2:
              host, port = row.split(":")
          elif len(row.split(":")) == 1:
              host = row
              port = default_port
          else:
              continue
  
          port = int(port)
          wait_time = 0
          while threading.active_count() > max_threads:
              time.sleep(1)
              wait_time += 1
              if wait_time > thread_wait_timeout:
                  thread_error = True
                  break
  
          if thread_error:
              results["error"] = "Threads took too long to finish."
              break
  
          # add just a little bit of delay before starting the thread
          # to avoid overwhelming the connection.
          time.sleep(delay_time)
  
          log_prefix = "%d/%d: " % (ind, total_item_count)
          thread = threading.Thread(target=get_fingerprint,
                                    args=(host, port,
                                          results, log_prefix))
          ind += 1
          thread.setDaemon(1)
  
          thread_open_success = False
          retries = 0
          while not thread_open_success and retries < MAX_THREAD_START_RETRY:
              try:
                  thread.start()
                  threads.append(thread)
                  thread_open_success = True
              except:
                  retries += 1
                  time.sleep(THREAD_START_DELAY)
                  logging.error("%sThread start failed for %s, retrying... (%d/%d)" % (log_prefix, host, retries, MAX_THREAD_START_RETRY))
  
          if retries == MAX_THREAD_START_RETRY:
              logging.error("%sCan't start a new thread for %s after %d retries." % (log_prefix, host, retries))
  
      for thread in threads:
          thread.join(thread_wait_timeout)
  
      return results