diff options
Diffstat (limited to 'netaddr/eui/ieee.py')
-rwxr-xr-x | netaddr/eui/ieee.py | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/netaddr/eui/ieee.py b/netaddr/eui/ieee.py new file mode 100755 index 0000000..d7d177f --- /dev/null +++ b/netaddr/eui/ieee.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +# +# DISCLAIMER +# +# netaddr is not sponsored nor endorsed by the IEEE. +# +# Use of data from the IEEE (Institute of Electrical and Electronics +# Engineers) is subject to copyright. See the following URL for +# details :- +# +# - http://www.ieee.org/web/publications/rights/legal.html +# +# IEEE data files included with netaddr are not modified in any way but are +# parsed and made available to end users through an API. There is no +# guarantee that referenced files are not out of date. +# +# See README file and source code for URLs to latest copies of the relevant +# files. +# +#----------------------------------------------------------------------------- +""" +Provides access to public OUI and IAB registration data published by the IEEE. + +More details can be found at the following URLs :- + + - IEEE Home Page - http://www.ieee.org/ + - Registration Authority Home Page - http://standards.ieee.org/regauth/ +""" + +import os.path as _path +import csv as _csv + +from netaddr.compat import _bytes_type, _open_binary +from netaddr.core import Subscriber, Publisher + + +#: OUI index lookup dictionary. +OUI_INDEX = {} + +#: IAB index lookup dictionary. +IAB_INDEX = {} + + +class FileIndexer(Subscriber): + """ + A concrete Subscriber that receives OUI record offset information that is + written to an index data file as a set of comma separated records. + """ + def __init__(self, index_file): + """ + Constructor. + + :param index_file: a file-like object or name of index file where + index records will be written. + """ + if hasattr(index_file, 'readline') and hasattr(index_file, 'tell'): + self.fh = index_file + else: + self.fh = open(index_file, 'w') + + self.writer = _csv.writer(self.fh, lineterminator="\n") + + def update(self, data): + """ + Receives and writes index data to a CSV data file. + + :param data: record containing offset record information. + """ + self.writer.writerow(data) + + +class OUIIndexParser(Publisher): + """ + A concrete Publisher that parses OUI (Organisationally Unique Identifier) + records from IEEE text-based registration files + + It notifies registered Subscribers as each record is encountered, passing + on the record's position relative to the start of the file (offset) and + the size of the record (in bytes). + + The file processed by this parser is available online from this URL :- + + - http://standards.ieee.org/regauth/oui/oui.txt + + This is a sample of the record structure expected:: + + 00-CA-FE (hex) ACME CORPORATION + 00CAFE (base 16) ACME CORPORATION + 1 MAIN STREET + SPRINGFIELD + UNITED STATES + """ + def __init__(self, ieee_file): + """ + Constructor. + + :param ieee_file: a file-like object or name of file containing OUI + records. When using a file-like object always open it in binary + mode otherwise offsets will probably misbehave. + """ + super(OUIIndexParser, self).__init__() + + if hasattr(ieee_file, 'readline') and hasattr(ieee_file, 'tell'): + self.fh = ieee_file + else: + self.fh = open(ieee_file, 'rb') + + def parse(self): + """ + Starts the parsing process which detects records and notifies + registered subscribers as it finds each OUI record. + """ + skip_header = True + record = None + size = 0 + + marker = _bytes_type('(hex)') + hyphen = _bytes_type('-') + empty_string = _bytes_type('') + + while True: + line = self.fh.readline() + + if not line: + break # EOF, we're done + + if skip_header and marker in line: + skip_header = False + + if skip_header: + # ignoring header section + continue + + if marker in line: + # record start + if record is not None: + # a complete record. + record.append(size) + self.notify(record) + + size = len(line) + offset = (self.fh.tell() - len(line)) + oui = line.split()[0] + index = int(oui.replace(hyphen, empty_string), 16) + record = [index, offset] + else: + # within record + size += len(line) + + # process final record on loop exit + record.append(size) + self.notify(record) + + +class IABIndexParser(Publisher): + """ + A concrete Publisher that parses IAB (Individual Address Block) records + from IEEE text-based registration files + + It notifies registered Subscribers as each record is encountered, passing + on the record's position relative to the start of the file (offset) and + the size of the record (in bytes). + + The file processed by this parser is available online from this URL :- + + - http://standards.ieee.org/regauth/oui/iab.txt + + This is a sample of the record structure expected:: + + 00-50-C2 (hex) ACME CORPORATION + ABC000-ABCFFF (base 16) ACME CORPORATION + 1 MAIN STREET + SPRINGFIELD + UNITED STATES + """ + def __init__(self, ieee_file): + """ + Constructor. + + :param ieee_file: a file-like object or name of file containing IAB + records. When using a file-like object always open it in binary + mode otherwise offsets will probably misbehave. + """ + super(IABIndexParser, self).__init__() + + if hasattr(ieee_file, 'readline') and hasattr(ieee_file, 'tell'): + self.fh = ieee_file + else: + self.fh = open(ieee_file, 'rb') + + def parse(self): + """ + Starts the parsing process which detects records and notifies + registered subscribers as it finds each IAB record. + """ + skip_header = True + record = None + size = 0 + + hex_marker = _bytes_type('(hex)') + base16_marker = _bytes_type('(base 16)') + hyphen = _bytes_type('-') + empty_string = _bytes_type('') + + while True: + line = self.fh.readline() + + if not line: + break # EOF, we're done + + if skip_header and hex_marker in line: + skip_header = False + + if skip_header: + # ignoring header section + continue + + if hex_marker in line: + # record start + if record is not None: + record.append(size) + self.notify(record) + + offset = (self.fh.tell() - len(line)) + iab_prefix = line.split()[0] + index = iab_prefix + record = [index, offset] + size = len(line) + elif base16_marker in line: + # within record + size += len(line) + prefix = record[0].replace(hyphen, empty_string) + suffix = line.split()[0] + suffix = suffix.split(hyphen)[0] + record[0] = (int(prefix + suffix, 16)) >> 12 + else: + # within record + size += len(line) + + # process final record on loop exit + record.append(size) + self.notify(record) + + +def create_index_from_registry(registry_fh, index_path, parser): + """Generate an index files from the IEEE registry file.""" + oui_parser = parser(registry_fh) + oui_parser.attach(FileIndexer(index_path)) + oui_parser.parse() + + +def create_indices(): + """Create indices for OUI and IAB file based lookups""" + create_index_from_registry( + _path.join(_path.dirname(__file__), 'oui.txt'), + _path.join(_path.dirname(__file__), 'oui.idx'), + OUIIndexParser, + ) + create_index_from_registry( + _path.join(_path.dirname(__file__), 'iab.txt'), + _path.join(_path.dirname(__file__), 'iab.idx'), + IABIndexParser, + ) + + +def load_index(index, fp): + """Load index from file into index data structure.""" + try: + for row in _csv.reader([x.decode('UTF-8') for x in fp]): + (key, offset, size) = [int(_) for _ in row] + index.setdefault(key, []) + index[key].append((offset, size)) + finally: + fp.close() + + +def load_indices(): + """Load OUI and IAB lookup indices into memory""" + load_index(OUI_INDEX, _open_binary(__package__, 'oui.idx')) + load_index(IAB_INDEX, _open_binary(__package__, 'iab.idx')) + + +if __name__ == '__main__': + # Generate indices when module is executed as a script. + create_indices() +else: + # On module load read indices in memory to enable lookups. + load_indices() |