lib/presence/scanner.rb
require 'presence/errors'
module Presence
# Scans a network for connected clients, captures the MAC address, and
# dispatches related events to any registered listeners.
class Scanner
PAIR = '[0-9a-fA-F]{2}'
SEP = '\:'
MAC_REGEXP = Regexp.new("(#{6.times.map {PAIR}.join(SEP)})")
attr_accessor :listeners, :options
def initialize(options = nil)
options ||= {}
self.options = {
retries: 1,
ip_prefix: nil,
octet_range: (1..255)
}.merge(options)
self.listeners = []
@commands = Presence::Commands.new
end
def register_listener(l)
raise Presence::InvalidListener.new("Listener cannot be nil") if l.nil?
listeners << l
dispatch(:listener_registered, l, self)
end
def localhost_ip
unless @localhost_ip
@localhost_ip = @commands.local_ip
end
@localhost_ip
end
def ip_prefix
unless options[:ip_prefix]
options[:ip_prefix] = localhost_ip.split('.')[0,3].join('.')
end
options[:ip_prefix]
end
def octet_range
options[:octet_range]
end
def retries
options[:retries]
end
def scan
dispatch(:scan_started, ip_prefix, octet_range)
octet_range.each do |i|
scan_ip("#{ip_prefix}.#{i}")
end
dispatch(:scan_finished, ip_prefix, octet_range)
end
def scan_ip(ip)
cmd, result = @commands.arping(ip, retries)
dispatch(:ip_scanned, ip, cmd, result)
process_scan_result(ip, cmd, result)
end
def process_scan_result(ip, cmd, result)
if result =~ MAC_REGEXP
mac = $1
dispatch(:localhost_found, ip, mac) if ip == localhost_ip
dispatch(:mac_found, ip, mac)
else
dispatch(:mac_not_found, ip)
end
end
def dispatch(event, *args)
listeners.each do |l|
l.send(event, *args) if l.respond_to?(event)
end
end
def to_s
"<#{self.class} ip_prefix: '#{self.ip_prefix}' octet_range: (#{self.octet_range})>"
end
class << self
def check_env
commands = Commands.new
required_commands = %w{ifconfig arping}
required_commands.each do |cmd|
if commands.run("which #{cmd}").size == 0
raise Presence::InvalidEnvironment.new("Unsupported platform: #{cmd} not found.")
end
end
end
def scan_loop(&block)
scanner = self.new
yield(scanner) if block_given?
while(true)
begin
scanner.scan
rescue Interrupt
break
end
end
end
end
end
end