module OUI

Organizationally Unique Identifier

Constants

BLANK_LINE
COUNTRY_OVERRIDES
DEBUGGING_DEFAULT
ERASE_LINE
EXPECTED_DUPLICATES
FIRST_LINE_INDEX
HEX_BEGINNING_REGEX
IMPORT_LOCAL_TXT_FILE_DEFAULT

import data/oui.txt instead of fetching remotely

IN_MEMORY_ONLY_DEFAULT

use in-memory instead of persistent file

LINE_LENGTH
LOCAL_DB_DEFAULT
LOCAL_MANUAL_FILE
LOCAL_TXT_FILE
MISSING_COUNTRIES
REMOTE_TXT_URI
ROOT
TABLE

Public Instance Methods

clear_table() click to toggle source
# File lib/oui.rb, line 138
def clear_table
  debug 'clear_table'
  table.delete_sql
end
close_db() click to toggle source

Release backend resources

# File lib/oui.rb, line 128
def close_db
  semaphore.synchronize do
    debug 'Closing database'
    if @@db 
      @@db.disconnect
      @@db = nil
    end
  end
end
debugging() click to toggle source
# File lib/oui.rb, line 59
def debugging
  @@debugging
end
debugging=(v) click to toggle source
# File lib/oui.rb, line 63
def debugging=(v)
  @@debugging = (v.nil?) ? DEBUGGING_DEFAULT : v
end
find(oui) click to toggle source

@param oui [String,Integer] hex or numeric OUI to find @return [Hash,nil]

# File lib/oui.rb, line 94
def find(oui)
  semaphore.synchronize do
    update_db unless table? && table.count > 0
    r = table.where(id: self.to_i(oui)).first
    r.delete :create_table if r # not sure why this is here, but nuking it
    r
  end
end
import_local_txt_file() click to toggle source
# File lib/oui.rb, line 70
def import_local_txt_file
  @@import_local_txt_file
end
import_local_txt_file=(v) click to toggle source
# File lib/oui.rb, line 74
def import_local_txt_file=(v)
  @@import_local_txt_file = (v.nil?) ? IMPORT_LOCAL_TXT_FILE_DEFAULT : v
end
in_memory_only() click to toggle source
# File lib/oui.rb, line 81
def in_memory_only
  @@in_memory_only
end
in_memory_only=(v) click to toggle source
# File lib/oui.rb, line 85
def in_memory_only=(v)
  if v != @@in_memory_only
    @@in_memory_only = (v.nil?) ? IN_MEMORY_ONLY_DEFAULT : v
    close_db
  end
end
local_db() click to toggle source
# File lib/oui.rb, line 48
def local_db
  @@local_db
end
local_db=(v) click to toggle source
# File lib/oui.rb, line 52
def local_db=(v)
  @@local_db = v || LOCAL_DB_DEFAULT
end
to_i(oui) click to toggle source

Converts an OUI string to an integer of equal value @param oui [String,Integer] MAC OUI in hexadecimal formats

hhhh.hh, hh:hh:hh, hh-hh-hh or hhhhhh

@return [Integer] numeric representation of oui

# File lib/oui.rb, line 107
def to_i(oui)
  return oui if oui.is_a? Integer
  oui = oui.strip.gsub(/[:\- .]/, '')
  if oui =~ /([[:xdigit:]]{6})/
    $1.to_i(16)
  end
end
to_s(id, sep = '-') click to toggle source

Convert an id to OUI @param oui [String,nil] string to place between pairs of hex digits, nil for none @return [String] hexadecimal format of id

# File lib/oui.rb, line 118
def to_s(id, sep = '-')
  return id if id.is_a? String
  unless id >= 0x000000 && id <= 0xFFFFFF
    raise ArgumentError, "#{id} is not a valid 24-bit OUI"
  end
  format('%06x', id).scan(/../).join(sep)
end
update_db(local = nil, db_file = nil) click to toggle source

Update database from fetched URL @param [Boolean] whether to connect to the network or not @return [Integer] number of unique records loaded

# File lib/oui.rb, line 146
  def update_db(local = nil, db_file = nil)
    semaphore.synchronize do
      debug "update_db(local = #{local}, db_file = #{db_file})"
      close_db
      old_import_local_txt_file = self.import_local_txt_file
      self.import_local_txt_file = local
      old_local_db = self.local_db
      self.local_db = db_file
      ## Sequel
      debug '--- close db ---'
      debug '--- close db ---'
      debug '--- drop table ---'
      drop_table
#      debug '--- drop table ---'
#      debug '--- create table ---'
      create_table
#      debug '--- create table ---'
      db.transaction do
        debug '--- clear table ---'
        clear_table
        debug '--- clear table ---'
        debug '--- install manual ---'
        install_manual
        debug '--- install manual ---'
        debug '--- install updates ---'
        install_updates
        debug '--- install updates ---'
      end
      debug '--- close db ---'
      close_db
      debug '--- close db ---'

      self.local_db = old_local_db
      self.import_local_txt_file = old_import_local_txt_file
      ## AR
      # self.transaction do
      #   self.delete_all
      #   self.install_manual
      #   self.install_updates
      # end
      debug "update_db(local = #{local}, db_file = #{db_file}) finish"
    end
  end

Private Instance Methods

added() click to toggle source

Has a particular id been added yet?

# File lib/oui.rb, line 346
def added
  @@added ||= {}
end
connect_db() click to toggle source
# File lib/oui.rb, line 203
def connect_db
  if in_memory_only
    debug 'Connecting to in-memory database'
    if RUBY_PLATFORM == 'java'
      Sequel.connect('jdbc:sqlite::memory:')
    else 
      Sequel.sqlite # in-memory sqlite database
    end
  else
    connect_file_db local_db
  end
end
connect_file_db(f) click to toggle source
# File lib/oui.rb, line 192
def connect_file_db(f)
  FileUtils.mkdir_p(File.dirname(f))
  if RUBY_PLATFORM == 'java'
    u = 'jdbc:sqlite:'+f
  else
    u = 'sqlite:'+f
  end
  debug "Connecting to db file #{u}"
  Sequel.connect(u)
end
create_from_line_group(g) click to toggle source

@param g [Array<String>]

# File lib/oui.rb, line 300
def create_from_line_group(g)
  n = g.length
  raise ArgumentError, "Parse error lines: #{n} '#{g}'" unless (2..6).include? n
  id = parse_id(g)
  create_unless_present(id: id, organization: parse_org(g),
                        address1: parse_address1(g),
                        address2: parse_address2(g, id),
                        address3: parse_address3(g),
                        country: parse_country(g, id))
end
create_table() click to toggle source
# File lib/oui.rb, line 233
  def create_table
#    debug 'create_table'
    db.create_table TABLE do
      primary_key :id
      String :organization, null: false
      String :address1
      String :address2
      String :address3
      String :country
      index :id
    end
  end
create_unless_present(opts) click to toggle source
# File lib/oui.rb, line 366
def create_unless_present(opts)
  id = opts[:id]
  if added[id]
    unless expected_duplicate? id
      debug "OUI unexpected duplicate #{opts}"
    end
  else
    table.insert(opts)
    # self.create! opts
    added[id] = true
  end
end
db() click to toggle source
# File lib/oui.rb, line 216
def db
  @@db ||= connect_db
end
debug(*args) click to toggle source
# File lib/oui.rb, line 354
def debug(*args)
  $stderr.puts(*args) if debug?
end
debug?() click to toggle source
# File lib/oui.rb, line 350
def debug?
  $DEBUG || debugging || ENV['DEBUG_OUI']
end
debug_print(*args) click to toggle source
# File lib/oui.rb, line 358
def debug_print(*args)
  $stderr.print(*args) if debug?
end
drop_table() click to toggle source
# File lib/oui.rb, line 228
def drop_table
  debug 'drop_table'
  db.drop_table(TABLE) if table? 
end
expected_duplicate?(id) click to toggle source

Expected duplicates are 00-01-C8 (2x) and 08-00-30 (3x)

# File lib/oui.rb, line 341
def expected_duplicate?(id)
  EXPECTED_DUPLICATES.include? id
end
fetch() click to toggle source
# File lib/oui.rb, line 311
def fetch
  uri = oui_uri
  $stderr.puts "Fetching #{uri}"
  open(uri).read
end
install_manual() click to toggle source
# File lib/oui.rb, line 317
def install_manual
  debug 'install_manual'
  JSON.load(File.read(LOCAL_MANUAL_FILE)).each do |g|
    # convert keys to symbols
    g = g.map { |k, v| [k.to_sym, v] }
    g = Hash[g]
    # convert OUI octets to integers
    g[:id] = self.to_i(g[:id])
    create_unless_present(g)
  end
rescue Errno::ENOENT
end
install_updates() click to toggle source
# File lib/oui.rb, line 330
def install_updates
  debug 'install_updates'
  lines = fetch.split("\n").map { |x| x.sub(/\r$/, '') } 
  parse_lines_into_groups(lines).each_with_index do |group, idx|
    create_from_line_group(group)
    debug "#{ERASE_LINE}Created records #{idx}" if idx % 1000 == 0
  end.count
  debug "#{ERASE_LINE}#{BLANK_LINE}"
end
oui_uri() click to toggle source
# File lib/oui.rb, line 379
def oui_uri
  if import_local_txt_file
    debug 'oui_uri = local'
    LOCAL_TXT_FILE
  else
    debug 'oui_uri = remote'
    REMOTE_TXT_URI
  end
end
parse_address1(g) click to toggle source
# File lib/oui.rb, line 280
def parse_address1(g)
  g[2] if g.length >= 4
end
parse_address2(g, id) click to toggle source
# File lib/oui.rb, line 284
def parse_address2(g, id)
  g[3] if g.length >= 5 || MISSING_COUNTRIES.include?(id)
end
parse_address3(g) click to toggle source
# File lib/oui.rb, line 288
def parse_address3(g)
  g[4] if g.length == 6
end
parse_country(g, id) click to toggle source

@param g [Array<String>] @param id [Integer]

# File lib/oui.rb, line 294
def parse_country(g, id)
  c = COUNTRY_OVERRIDES[id] || g[-1]
  c if c !~ /\A\h/
end
parse_id(g) click to toggle source

@param g [Array<String>]

# File lib/oui.rb, line 276
def parse_id(g)
  g[1].split(' ')[0].to_i(16)
end
parse_lines_into_groups(lines) click to toggle source

@param lines [Array<String>] @return [Array<Array<String>>]

# File lib/oui.rb, line 248
def parse_lines_into_groups(lines)
  grps, curgrp = [], []
  header = true
  lines.each do |line|
    if header
      if line =~ HEX_BEGINNING_REGEX
        header = false
      else
        next
      end
    end
    if !curgrp.empty? && line =~ HEX_BEGINNING_REGEX
      grps << curgrp
      curgrp = []
    end
    line.strip!
    next if line.empty?
    curgrp << line
  end
  grps << curgrp # add last group and return
end
parse_org(g) click to toggle source

@param g [Array<String>]

# File lib/oui.rb, line 271
def parse_org(g)
  g[0].split("\t").last
end
semaphore() click to toggle source
# File lib/oui.rb, line 362
def semaphore
  @@semaphore ||= Monitor.new
end
table() click to toggle source
# File lib/oui.rb, line 224
def table
  db[TABLE]
end
table?() click to toggle source
# File lib/oui.rb, line 220
def table?
  db.tables.include? TABLE
end