oui-offline 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f0186bcd1fbaf8902a189bfa41da73427e0b7df7
4
+ data.tar.gz: b553fb418502ad5d9028eafdd24d308c64814b4e
5
+ SHA512:
6
+ metadata.gz: 687a5cf5e2e9bc2d6de4613be2d44444bab75cf4535bb7072997fed38fb1dac71fbd8970918457dbec2c6973dba32423e7d7193a54a8429e0393abaf042cf5d4
7
+ data.tar.gz: 09313a9cf06a123afa7e5bba35ea40968f8761dc0255ff8d1d51462edc0372b502bc6f75ae13601c94e71a5fa40efddc81a047afa2bf86c82c736862444ea878
checksums.yaml.gz.sig ADDED
Binary file
data.tar.gz.sig ADDED
Binary file
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # OUI (Organizationally Unique Identifiers)
2
+ ## Usage
3
+
4
+ >OUI.find 'AA-BB-CC'
5
+ => nil
6
+ > OUI.find '00:0c:85'
7
+ => {
8
+ :id => 3205,
9
+ :organization => "CISCO SYSTEMS, INC.",
10
+ :address1 => "170 W. TASMAN DRIVE",
11
+ :address2 => "M/S SJA-2",
12
+ :address3 => "SAN JOSE CA 95134-1706",
13
+ :country => "UNITED STATES"
14
+ }
15
+
16
+ ## Installation
17
+ ### Bundler Installation
18
+
19
+ ```ruby
20
+ gem 'oui', git: 'https://github.com/steakknife/oui.git'
21
+ ```
22
+
23
+ ### Manual Installation
24
+
25
+ cd ${TMP_DIR-/tmp}
26
+ git clone https://github.com/steakknife/oui
27
+ cd oui
28
+ gem build *.gemspec
29
+ gem install *.gem
30
+
31
+
32
+ ## Data
33
+
34
+ Database sourced from the public IEEE list, but it can be rebuilt anytime by running `bin/update_db` or `OUI.update_db`
35
+
36
+ ## License
37
+
38
+ MIT
data/bin/oui ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+ lib_dir = File.expand_path('../../lib', __FILE__)
3
+ $:.unshift lib_dir unless $:.include? lib_dir
4
+ require 'oui'
5
+
6
+ VERBOSE = ARGV.delete '-v'
7
+ case ARGV.shift
8
+ when 'lookup'
9
+ ARGV.map do |mac|
10
+ if r = OUI.find(mac)
11
+ puts (VERBOSE) ? r : r[:organization]
12
+ end
13
+ end
14
+
15
+ when 'update'
16
+ OUI.update_db
17
+
18
+ else
19
+ $stderr.puts <<-EOS
20
+ Usage: oui lookup oui+ # get corp name, oui in 24-bit oui in hex format
21
+ oui update # update oui db from ieee.org
22
+ EOS
23
+ exit 1
24
+ end
25
+
@@ -0,0 +1,7 @@
1
+ [
2
+ {"id": "00:D3:10", "organization": "Dell, Inc., for Dell Compellent Storage products"},
3
+ {"id": "0A:BB:CF", "organization": "Apple, Inc., Bluetooth PAN"},
4
+ {"id": "6A:5B:35", "organization": "Apple, Inc., Bridge and bond devices (virtual)"},
5
+ {"id": "CA:6F:1D", "organization": "Apple, Inc., iPhone USB"},
6
+ {"id": "D2:00:1A", "organization": "Apple, Inc., Thunderbolt"}
7
+ ]
data/db/oui.sqlite3 ADDED
Binary file
data/lib/oui.rb ADDED
@@ -0,0 +1,238 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+ require 'open-uri'
4
+ require 'sequel'
5
+
6
+ # Organizationally Unique Identifier
7
+ module OUI
8
+ extend self
9
+
10
+ private
11
+
12
+ TABLE = :ouis
13
+ # import data/oui.txt instead of fetching remotely
14
+ IMPORT_LOCAL_TXT_FILE = false
15
+ # use in-memory instead of persistent file
16
+ IN_MEMORY_ONLY = false
17
+ LOCAL_DB = File.expand_path('../../db/oui.sqlite3', __FILE__)
18
+ LOCAL_MANUAL_FILE = File.expand_path('../../data/oui-manual.json', __FILE__)
19
+ if IMPORT_LOCAL_TXT_FILE
20
+ OUI_URL = File.join('data', 'oui.txt')
21
+ else
22
+ OUI_URL = 'http://standards.ieee.org/develop/regauth/oui/oui.txt'
23
+ end
24
+ FIRST_LINE_INDEX = 7
25
+ EXPECTED_DUPLICATES = [0x0001C8, 0x080030]
26
+ LINE_LENGTH = 22
27
+
28
+ public
29
+
30
+ # @param oui [String,Integer] hex or numeric OUI to find
31
+ # @return [Hash,nil]
32
+ def find(oui)
33
+ update_db unless table? && table.count > 0
34
+ table.where(id: oui_to_i(oui)).first
35
+ end
36
+
37
+ # Converts an OUI string to an integer of equal value
38
+ # @param oui [String,Integer] MAC OUI in hexadecimal formats
39
+ # hhhh.hh, hh:hh:hh, hh-hh-hh or hhhhhh
40
+ # @return [Integer] numeric representation of oui
41
+ def oui_to_i(oui)
42
+ return oui if oui.is_a? Integer
43
+ oui = oui.strip.gsub(/[:\- .]/, '')
44
+ return unless oui =~ /[[:xdigit:]]{6}/
45
+ oui.to_i(16)
46
+ end
47
+
48
+ # Release backend resources
49
+ def close_db
50
+ @db = nil
51
+ end
52
+
53
+ # Update database from fetched URL
54
+ # @return [Integer] number of unique records loaded
55
+ def update_db
56
+ ## Sequel
57
+ drop_table
58
+ create_table
59
+ db.transaction do
60
+ table.delete_sql
61
+ install_manual
62
+ install_updates
63
+ end
64
+ ## AR
65
+ # self.transaction do
66
+ # self.delete_all
67
+ # self.install_manual
68
+ # self.install_updates
69
+ # end
70
+ end
71
+
72
+
73
+ private
74
+
75
+ HEX_BEGINNING_REGEX = /\A[[:space:]]{2}[[:xdigit:]]{2}-/
76
+ ERASE_LINE = "\b" * LINE_LENGTH
77
+ BLANK_LINE = ' ' * LINE_LENGTH
78
+
79
+ def connect_file_db(f)
80
+ FileUtils.mkdir_p(File.dirname(f))
81
+ Sequel.sqlite(f)
82
+ end
83
+
84
+ def connect_db
85
+ if IN_MEMORY_ONLY
86
+ Sequel.sqlite # in-memory sqlite database
87
+ else
88
+ $stderr.puts "Connecting to db file #{LOCAL_DB}"
89
+ connect_file_db LOCAL_DB
90
+ end
91
+ end
92
+
93
+ def db
94
+ @db ||= connect_db
95
+ end
96
+
97
+ def table?
98
+ db.tables.include? TABLE
99
+ end
100
+
101
+ def table
102
+ db[TABLE]
103
+ end
104
+
105
+ def drop_table
106
+ db.drop_table(TABLE) if table?
107
+ end
108
+
109
+ def create_table
110
+ db.create_table TABLE do
111
+ primary_key :id
112
+ String :organization, null: false
113
+ String :address1
114
+ String :address2
115
+ String :address3
116
+ String :country
117
+ index :id
118
+ end
119
+ end
120
+
121
+ # @param lines [Array<String>]
122
+ # @return [Array<Array<String>>]
123
+ def parse_lines_into_groups(lines)
124
+ grps, curgrp = [], []
125
+ lines[FIRST_LINE_INDEX..-1].each do |line|
126
+ if !curgrp.empty? && line =~ HEX_BEGINNING_REGEX
127
+ grps << curgrp
128
+ curgrp = []
129
+ end
130
+ line.strip!
131
+ next if line.empty?
132
+ curgrp << line
133
+ end
134
+ grps << curgrp # add last group and return
135
+ end
136
+
137
+ # @param g [Array<String>]
138
+ def parse_org(g)
139
+ g[0].split("\t").last
140
+ end
141
+
142
+ # @param g [Array<String>]
143
+ def parse_id(g)
144
+ g[1].split(' ')[0].to_i(16)
145
+ end
146
+
147
+ MISSING_COUNTRIES = [
148
+ 0x000052,
149
+ 0x002142,
150
+ 0x684CA8
151
+ ]
152
+
153
+ COUNTRY_OVERRIDES = {
154
+ 0x000052 => 'UNITED STATES',
155
+ 0x002142 => 'SERBIA',
156
+ 0x684CA8 => 'CHINA'
157
+ }
158
+
159
+ def parse_address1(g)
160
+ g[2] if g.length >= 4
161
+ end
162
+
163
+ def parse_address2(g, id)
164
+ g[3] if g.length >= 5 || MISSING_COUNTRIES.include?(id)
165
+ end
166
+
167
+ def parse_address3(g)
168
+ g[4] if g.length == 6
169
+ end
170
+
171
+ # @param g [Array<String>]
172
+ # @param id [Integer]
173
+ def parse_country(g, id)
174
+ c = COUNTRY_OVERRIDES[id] || g[-1]
175
+ c if c !~ /\A\h/
176
+ end
177
+
178
+ # @param g [Array<String>]
179
+ def create_from_line_group(g)
180
+ n = g.length
181
+ raise ArgumentError, "Parse error lines: #{n}" unless (2..6).include? n
182
+ id = parse_id(g)
183
+ create_unless_present(id: id, organization: parse_org(g),
184
+ address1: parse_address1(g),
185
+ address2: parse_address2(g, id),
186
+ address3: parse_address3(g),
187
+ country: parse_country(g, id))
188
+ end
189
+
190
+ def fetch
191
+ $stderr.puts "Fetching #{OUI_URL}"
192
+ open(OUI_URL).read
193
+ end
194
+
195
+ def install_manual
196
+ JSON.load(File.read(LOCAL_MANUAL_FILE)).each do |g|
197
+ # convert keys to symbols
198
+ g = g.map { |k, v| [k.to_sym, v] }
199
+ g = Hash[g]
200
+ # convert OUI octets to integers
201
+ g[:id] = oui_to_i(g[:id])
202
+ create_unless_present(g)
203
+ end
204
+ rescue Errno::ENOENT
205
+ end
206
+
207
+ def install_updates
208
+ lines = fetch.split("\n").map { |x| x.sub(/\r$/, '') }
209
+ parse_lines_into_groups(lines).each_with_index do |group, idx|
210
+ create_from_line_group(group)
211
+ $stderr.print "#{ERASE_LINE}Created records #{idx}" if idx % 1000 == 0
212
+ end.count
213
+ $stderr.puts "#{ERASE_LINE}#{BLANK_LINE}"
214
+ end
215
+
216
+ # Expected duplicates are 00-01-C8 (2x) and 08-00-30 (3x)
217
+ def expected_duplicate?(id)
218
+ EXPECTED_DUPLICATES.include? id
219
+ end
220
+
221
+ # Has a particular id been added yet?
222
+ def added
223
+ @added ||= {}
224
+ end
225
+
226
+ def create_unless_present(opts)
227
+ id = opts[:id]
228
+ if added[id]
229
+ unless expected_duplicate? id
230
+ $stderr.puts "OUI unexpected duplicate #{opts}"
231
+ end
232
+ else
233
+ table.insert(opts)
234
+ # self.create! opts
235
+ added[id] = true
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,31 @@
1
+ Gem::Specification::new do |s|
2
+ s.name = 'oui-offline'
3
+ s.version = '1.0.0'
4
+ s.platform = Gem::Platform::RUBY
5
+ s.summary = 'Organizationally Unique Idenitfiers (OUI)'
6
+ s.description = 'Organizationally Unique Idenitfiers (OUI) offline database'
7
+ s.license = 'MIT'
8
+
9
+ s.files =
10
+ ['Gemfile',
11
+ 'README.md',
12
+ 'bin/oui',
13
+ 'data/oui-manual.json',
14
+ 'db/oui.sqlite3',
15
+ 'lib/oui.rb',
16
+ 'oui-offline.gemspec',
17
+ ]
18
+ s.required_ruby_version = '>= 1.9.3'
19
+
20
+ s.require_path = 'lib'
21
+ s.executables << 'oui'
22
+
23
+ s.author = 'Barry Allard'
24
+ s.email = 'barry.allard@gmail.com'
25
+ s.homepage = 'https://github.com/steakknife/oui'
26
+ s.post_install_message = 'Oui!'
27
+
28
+ s.add_dependency 'sqlite3', '>= 1.3', '< 2'
29
+ s.add_dependency 'sequel', '>= 4', '< 5'
30
+ end
31
+ .tap {|gem| pk = File.expand_path(File.join('~/.keys', 'gem-private_key.pem')); gem.signing_key = pk if File.exist? pk; gem.cert_chain = ['gem-public_cert.pem']} # pressed firmly by waxseal
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oui-offline
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Barry Allard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ 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
42
+ t/HWh1yhriCUe/y8+De8M87btOs=
43
+ -----END CERTIFICATE-----
44
+ date: 2015-01-23 00:00:00.000000000 Z
45
+ dependencies:
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '1.3'
53
+ - - "<"
54
+ - !ruby/object:Gem::Version
55
+ version: '2'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '1.3'
63
+ - - "<"
64
+ - !ruby/object:Gem::Version
65
+ version: '2'
66
+ - !ruby/object:Gem::Dependency
67
+ name: sequel
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '4'
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: '5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '4'
83
+ - - "<"
84
+ - !ruby/object:Gem::Version
85
+ version: '5'
86
+ description: Organizationally Unique Idenitfiers (OUI) offline database
87
+ email: barry.allard@gmail.com
88
+ executables:
89
+ - oui
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - Gemfile
94
+ - README.md
95
+ - bin/oui
96
+ - data/oui-manual.json
97
+ - db/oui.sqlite3
98
+ - lib/oui.rb
99
+ - oui-offline.gemspec
100
+ homepage: https://github.com/steakknife/oui
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message: Oui!
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 1.9.3
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.4.5
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Organizationally Unique Idenitfiers (OUI)
124
+ test_files: []
125
+ has_rdoc:
metadata.gz.sig ADDED
Binary file