class PatronusFati::DataModels::AccessPoint

Constants

LOCAL_ATTRIBUTE_KEYS

Attributes

client_macs[RW]
last_dbm[RW]
local_attributes[RW]
ssids[RW]

Public Class Methods

current_expiration_threshold() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 10
def self.current_expiration_threshold
  Time.now.to_i - AP_EXPIRATION
end
new(bssid) click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 112
def initialize(bssid)
  super
  self.local_attributes = { bssid: bssid }
  self.client_macs = []
end

Public Instance Methods

active_ssids() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 14
def active_ssids
  return unless ssids
  # If there is any active SSIDs return them
  active = ssids.select { |_, v| v.active? }
  return active unless active.empty?

  # If there are no active SSIDs try and find the most recently seen SSID
  # and report that as still active. Still return an empty set if there
  # are no SSIDs.
  most_recent = ssids.sort_by { |_, v| v.presence.last_visible || 0 }.last
  most_recent ? Hash[[most_recent]] : {}
end
add_client(mac) click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 27
def add_client(mac)
  client_macs << mac unless client_macs.include?(mac)
end
announce_changes() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 31
def announce_changes
  return unless dirty? && valid? && worth_syncing?

  if active?
    status = new? ? :new : :changed

    PatronusFati.event_handler.event(
      :access_point,
      status,
      full_state,
      diagnostic_data
    )
  else
    PatronusFati.event_handler.event(
      :access_point, :offline, {
        'bssid' => local_attributes[:bssid],
        'uptime' => presence.visible_time
      },
      diagnostic_data
    )

    # We need to reset the first seen so we get fresh duration
    # information
    presence.first_seen = nil

    client_macs.each do |mac|
      DataModels::Client[mac].remove_access_point(local_attributes[:bssid])
      DataModels::Connection["#{local_attributes[:bssid]}^#{mac}"].link_lost = true
    end
  end

  mark_synced
end
broadcasting_multiple?() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 65
def broadcasting_multiple?
  return false unless ssids
  return false if active_ssids.count == 1

  presences = active_ssids.values.map(&:presence)
  # This check becomes very expensive at larger numbers, if we get too
  # high just short circuit and assume that yes there are simultaneous
  # SSIDs being transmitted. This is likely a sign of a malicious device.
  return true if presences.length >= 100

  current_presence_bits = presences.map { |p| p.current_presence.bits }
  return true if PatronusFati::BitHelper.largest_bit_overlap(current_presence_bits) >= SIMULTANEOUS_SSID_THRESHOLD

  last_presence_bits = presences.map { |p| p.last_presence.bits }
  return true if PatronusFati::BitHelper.largest_bit_overlap(last_presence_bits) >= SIMULTANEOUS_SSID_THRESHOLD

  false
end
cleanup_ssids() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 84
def cleanup_ssids
  return if ssids.nil? || ssids.select { |_, v| v.presence.dead? }.empty?

  # When an AP is offline we don't care about announcing that it's SSIDs
  # have expired, but we do want to remove them.
  set_sync_flag(:dirtyChildren) if active?

  ssids.reject! { |_, v| v.presence.dead? }
end
diagnostic_data() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 94
def diagnostic_data
  dd = super
  dd.merge!(ssids: Hash[ssids.map { |k, s| [k, s.diagnostic_data] }]) if ssids
  dd[:last_dbm] = last_dbm if last_dbm
  dd
end
full_state() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 101
def full_state
  state = local_attributes.merge({
    active: active?,
    broadcasting_multiple: broadcasting_multiple?,
    connected_clients: client_macs,
    vendor: vendor
  })
  state[:ssids] = active_ssids.values.map(&:full_state) if ssids
  state
end
mark_synced() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 118
def mark_synced
  super
  ssids.each { |_, v| v.mark_synced } if ssids
end
remove_client(mac) click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 123
def remove_client(mac)
  client_macs.delete(mac)
end
track_ssid(ssid_data) click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 127
def track_ssid(ssid_data)
  self.ssids ||= {}

  ssid_key = ssid_data[:cloaked] ?
    Digest::SHA256.hexdigest(ssid_data[:crypt_set].join) :
    ssid_data[:essid]

  ssids[ssid_key] ||= DataModels::Ssid.new(ssid_data[:essid])

  ssid = ssids[ssid_key]
  ssid.presence.mark_visible
  ssid.update(ssid_data)

  set_sync_flag(:dirtyChildren) if ssid.dirty?
end
update(attrs) click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 143
def update(attrs)
  attrs.each do |k, v|
    next unless LOCAL_ATTRIBUTE_KEYS.include?(k)
    next if v.nil? || local_attributes[k] == v
    # Disregard channel band updates for a specific BSSID
    next if k == :channel && local_attributes[k] && v &&
      ((local_attributes[k] <= 13 && v > 13) || (local_attributes[k] > 13 && v <= 13))

    set_sync_flag(:dirtyAttributes)
    local_attributes[k] = v
  end
end
valid?() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 156
def valid?
  !([:bssid, :channel, :type].map { |k| local_attributes[k].nil? }.any?) &&
    local_attributes[:channel] != 0
end
vendor() click to toggle source
# File lib/patronus_fati/data_models/access_point.rb, line 161
def vendor
  return unless local_attributes[:bssid]
  result = Louis.lookup(local_attributes[:bssid])
  result['long_vendor'] || result['short_vendor']
end
worth_syncing?() click to toggle source

This is a safety mechanism to check whether or not an access point is actually 'present'. This is intended to assist in cutting out the access points that are just on the edge of being visible to our sensors.

# File lib/patronus_fati/data_models/access_point.rb, line 170
def worth_syncing?
  client_macs.any? || sync_flag?(:syncedOnline) ||
    (presence && presence.visible_time && presence.visible_time > INTERVAL_DURATION)
end