unclesp1d3r/CipherSwarm

View on GitHub
app/models/hashcat_status.rb

Summary

Maintainability
A
0 mins
Test Coverage
C
70%
# frozen_string_literal: true

# SPDX-FileCopyrightText:  2024 UncleSp1d3r
# SPDX-License-Identifier: MPL-2.0

# == Schema Information
#
# Table name: hashcat_statuses
#
#  id                                                       :bigint           not null, primary key
#  estimated_stop(The estimated time of completion)         :datetime
#  original_line(The original line from the hashcat output) :text
#  progress(The progress in percentage)                     :bigint           is an Array
#  recovered_hashes(The number of recovered hashes)         :bigint           is an Array
#  recovered_salts(The number of recovered salts)           :bigint           is an Array
#  rejected(The number of rejected hashes)                  :bigint
#  restore_point(The restore point)                         :bigint
#  session(The session name)                                :string           not null
#  status(The status code)                                  :integer          not null
#  target(The target file)                                  :string           not null
#  time(The time of the status)                             :datetime         not null
#  time_start(The time the task started)                    :datetime         not null
#  created_at                                               :datetime         not null
#  updated_at                                               :datetime         not null
#  task_id                                                  :bigint           not null, indexed
#
# Indexes
#
#  index_hashcat_statuses_on_task_id  (task_id)
#
# Foreign Keys
#
#  fk_rails_...  (task_id => tasks.id) ON DELETE => cascade
#
require "date"
include ActiveSupport::NumberHelper

# The HashcatStatus model represents the status of a Hashcat task.
#
# It derives from the Hashcat status output and is used to track the progress of the cracking process.
#
# Associations:
# - belongs_to :task
# - has_many :device_statuses
# - has_one :hashcat_guess
#
# Validations:
# - validates_associated :device_statuses
# - validates_associated :hashcat_guess
# - validates :time, presence: true
# - validates :status, presence: true
# - validates :session, presence: true, length: { maximum: 255 }
# - validates :target, presence: true, length: { maximum: 255 }
# - validates :time_start, presence: true
#
# Nested Attributes:
# - accepts_nested_attributes_for :device_statuses, allow_destroy: true
# - accepts_nested_attributes_for :hashcat_guess, allow_destroy: true
#
# Scopes:
# - latest: Returns the latest HashcatStatus based on time.
# - older_than(time): Returns HashcatStatus records older than the given time.
#
# Delegations:
# - guess_base_count: Delegates to :hashcat_guess
# - guess_base_offset: Delegates to :hashcat_guess
#
# Enums:
# - status: Defines various status values for the Hashcat task.
#
# Instance Methods:
# - estimated_time: Returns the estimated time until the process stops in words.
# - serializable_hash(options = {}): Customizes the serialization to include associated records.
# - status_text: Returns the capitalized string representation of the status.
class HashcatStatus < ApplicationRecord
  belongs_to :task, touch: true
  has_many :device_statuses, dependent: :destroy, autosave: true
  has_one :hashcat_guess, dependent: :destroy, autosave: true
  validates_associated :device_statuses
  validates_associated :hashcat_guess
  validates :time, presence: true
  validates :status, presence: true
  validates :session, presence: true, length: { maximum: 255 }
  validates :target, presence: true, length: { maximum: 255 }
  validates :time_start, presence: true

  accepts_nested_attributes_for :device_statuses, allow_destroy: true
  accepts_nested_attributes_for :hashcat_guess, allow_destroy: true

  scope :latest, -> { order(time: :desc).first }
  scope :older_than, ->(time) { where(time: ...time) }

  delegate :guess_base_count, to: :hashcat_guess
  delegate :guess_base_offset, to: :hashcat_guess

  enum :status, {
    initializing: 0, # Hashcat is initializing, which is the process of setting up the environment.
    autotuning: 1, # Hashcat is autotuning, which is a process to determine the optimal settings for the current hardware.
    self_testing: 2, # Hashcat is self-testing, which is a process to ensure that the hardware is functioning correctly.
    running: 3, # Hashcat is running, which is the process of cracking the hashes.
    paused: 4, # Hashcat is paused, which means that the process has been temporarily stopped.
    exhausted: 5, # Hashcat is exhausted, which means that the process has run out of guesses.
    cracked: 6, # Hashcat has cracked the hashes.
    aborted: 7, # Hashcat has aborted the process.
    quit: 8, # Hashcat has quit the process.
    bypassed: 9, # Hashcat has bypassed the process, which means that the process has been skipped.
    aborted_session_checkpoint: 10, # Hashcat has aborted the process at a session checkpoint.
    aborted_runtime_limit: 11, # Hashcat has aborted the process due to a runtime limit.
    error: 13, # Hashcat has encountered an error.
    aborted_finish: 14, # Hashcat has aborted the process at the finish, which means that the process has been stopped.
    autodetecting: 16 # Hashcat is autodetecting, which is the process of automatically detecting the hash type.
  }

  # Returns the current iteration of the Hashcat task.
  # The iteration is based on the guess base offset.
  #
  # @return [Integer] the current iteration, or 0 if no offset is present
  def current_iteration
    (guess_base_offset.presence || 0)
  end

  # Calculates the total speed of all devices.
  # If there are no device statuses, it returns 0.
  #
  # @return [Integer] the total speed of all devices
  def device_speed
    device_statuses.blank? ? 0 : device_statuses.sum(&:speed)
  end

  # Returns the estimated time remaining for the process to complete.
  # The time is calculated based on the estimated stop time and is
  # presented in a human-readable format (e.g., "about 5 minutes").
  #
  # @return [String] the estimated time remaining in words
  def estimated_time
    time_ago_in_words(estimated_stop)
  end

  # Calculates the progress percentage of the Hashcat task.
  # The progress is calculated based on the progress array, which contains
  # the current progress and the total progress.
  #
  # @return [Float, Integer] the progress percentage rounded to two decimal places
  def progress_percentage
    return 0.0 unless progress.present? && progress.is_a?(Array) && progress[1].positive? && progress[0].positive?

    progress_value = (progress[0].to_f / progress[1].to_f) * 100
    progress_value.round(2)
  end

  # Returns a formatted string representing the progress of the Hashcat task.
  # The string includes the progress percentage, iteration information, device speed, and recovered hashes.
  #
  # @return [String] the formatted progress text
  def progress_text
    progress_percentage_text = number_to_percentage(progress_percentage, precision: 2)
    is_multiple_iterations = guess_base_count.present? && guess_base_offset.present? && guess_base_count > 1

    formatted_progress_text = is_multiple_iterations ?
                                format("%s for iteration %d of %d", progress_percentage_text, current_iteration, guess_base_count)
                                : progress_percentage_text

    formatted_speed_text = number_to_human(device_speed, units: { unit: "H/s", thousand: "KH/s", million: "MH/s", billion: "GH/s" })
    formatted_hashes_text = format("%d of %d", recovered_hashes[0], recovered_hashes[1])

    "#{formatted_progress_text} at #{formatted_speed_text} (#{formatted_hashes_text})"
  end

  # Generates a serializable hash representation of the object.
  #
  # @param options [Hash] Options for customizing the serialization.
  # @option options [Array<Symbol>] :include Additional associations to include in the serialization.
  #
  # @return [Hash] The serialized hash representation of the object.
  def serializable_hash(options = {})
    options ||= {}
    if options[:include]
      options[:include].concat %i[device_statuses hashcat_guess]
    else
      options[:include] = %i[device_statuses hashcat_guess]
    end
    super(options)
  end

  # Returns the status as a capitalized string.
  #
  # @return [String] the capitalized status text
  def status_text
    status.to_s.capitalize
  end

  # Returns the total number of iterations for the Hashcat task.
  # The total iterations are based on the guess base count.
  #
  # @return [Integer] the total number of iterations, or 0 if no count is present
  def total_iterations
    guess_base_count.presence || 0
  end
end