Device-OUI
view release on metacpan or search on metacpan
lib/Device/OUI.pm view on Meta::CPAN
if ( ref $oui ) { return map { hex } split( '-', $oui->norm ) }
# 00-06-2A or 0:6:2a, etc. any non-hex delimiter will do
{
my @parts = grep { length } split( /[^a-f0-9]+/i, $oui );
if ( @parts == 3 ) { return map { hex } @parts }
}
# 00062a, requires exactly 6 hex characters
{
my @parts = ( $oui =~ /([a-f0-9])/ig );
if ( @parts == 6 ) {
return(
hex( $parts[0].$parts[1] ),
hex( $parts[2].$parts[3] ),
hex( $parts[4].$parts[5] ),
);
}
}
return ();
}
sub normalize_oui {
my $oui = shift;
my @int = oui_to_integers( $oui ) or return;
return sprintf( '%02X-%02X-%02X', @int );
}
sub normalized { return shift->{ 'oui_norm' } }
*norm = \&normalized;
sub organization { return shift->lookup->{ 'organization' } }
sub company_id { return shift->lookup->{ 'company_id' } }
sub address { return shift->lookup->{ 'address' } }
sub is_private {
my $self = shift;
return $self->organization eq 'PRIVATE' ? 1 : 0;
}
sub lookup {
my $self = shift;
my $x;
if ( $x = $self->{ 'lookup' } ) { return $x }
if ( $x = $self->cache ) { return $self->{ 'lookup' } = $x }
if ( $x = $self->update_from_file ) { return $self->{ 'lookup' } = $x }
if ( $x = $self->update_from_web ) { return $self->{ 'lookup' } = $x }
if ( $self->mirror_file ) {
if ( $x = $self->update_from_file ) {
return $self->{ 'lookup' } = $x;
}
}
return $self->{ 'lookup' } = {};
}
sub update_from_file {
my $self = shift;
my $oui = $self->norm;
my $cf = $self->cache_file;
if ( ! $cf ) { return }
my $fh = IO::File->new( $cf, 'r' );
if ( ! $fh ) { return }
local $/ = "";
while ( my $entry = $fh->getline ) {
if ( substr( $entry, 0, 8 ) eq $oui ) {
my $data = $self->parse_oui_entry( $entry );
$self->cache( $data );
return $data;
}
}
return;
}
{
my $HAVE_LWP_SIMPLE;
sub have_lwp_simple {
my $self = shift;
if ( defined $HAVE_LWP_SIMPLE ) { return $HAVE_LWP_SIMPLE }
eval "require LWP::Simple"; ## no critic
if ( $@ ) {
carp "Unable to load LWP::Simple, network access not available\n";
$HAVE_LWP_SIMPLE = 0;
} else {
$HAVE_LWP_SIMPLE = 1;
}
}
}
sub mirror_file {
my $self = shift;
my $url = shift || $self->file_url;
if ( ! $url ) { return }
my $file = shift || $self->cache_file;
if ( ! $file ) { return }
if ( ! $self->have_lwp_simple ) { return }
my $res = LWP::Simple::mirror( $url, $file );
if ( $res == LWP::Simple::RC_NOT_MODIFIED() ) { return 0 }
if ( ! LWP::Simple::is_success( $res ) ) {
carp "Failed to mirror $url to $file ($res)";
return;
}
return 1;
}
sub get_url {
my $self = shift;
my $url = shift;
if ( ! $url ) { return }
return LWP::Simple::get( $url );
}
sub load_cache_from_web {
my $self = shift;
my $url = shift || $self->file_url;
if ( ! $url ) { return }
my $file = shift || $self->cache_file;
if ( ! $file ) { return }
if ( $self->mirror_file( $url, $file ) ) {
return $self->load_cache_from_file( $file );
}
return;
}
sub load_cache_from_file {
my $self = shift;
my $file = shift;
if ( ! $file ) { $file = $self->cache_file }
if ( ! $file ) { return }
my $fh = IO::File->new( $file );
local $/ = "";
$fh->getline; # dump the header
my $counter = 0;
while ( my $entry = $fh->getline ) {
my $data = $self->parse_oui_entry( $entry );
$self->cache( $data->{ 'oui' } => $data );
$counter++;
}
return $counter;
}
sub search_url_for {
my $self = shift;
my $oui = normalize_oui( shift );
if ( ! $oui ) { $oui = $self->norm }
my $url_format = $self->search_url;
if ( ! $url_format ) { return }
if ( $url_format =~ /%s/ ) {
return sprintf( $url_format, $oui );
} else {
return $url_format.$oui;
}
}
sub update_from_web {
my $self = shift;
if ( not ref $self ) { return }
if ( not $self->have_lwp_simple ) { return }
my $url = $self->search_url_for;
if ( ! $url ) { return }
if ( my $page = $self->get_url( $url ) ) {
if ( $page =~ /listing contains no match/ ) { return }
my @entries = ( $page =~ m{<pre>(.*?)</pre>}gs );
if ( @entries > 1 ) { croak "Too many entries returned from $url\n" }
my $data = $self->parse_oui_entry( shift( @entries ) );
$self->cache( $data );
return $data;
}
return;
}
sub parse_oui_entry {
local $_ = pop( @_ ); # pop in case we get called as a class method
use Carp qw( confess );
if ( ! $_ ) { confess "eh?" }
s{</?b>}{}g;
s/\r//g;
s/\s*\(hex\)\s*/\n/gm;
s/\s*\(base 16\).*$//gm;
s/^\s*|\s*$//gsm;
my %data = ();
@data{ qw( oui organization company_id address ) } = split( "\n", $_, 4 );
delete $data{ 'address' } unless $data{ 'address' };
return \%data;
}
sub overload_cmp { return oui_cmp( pop( @_ ) ? reverse @_ : @_ ) }
sub oui_cmp {
my @l = oui_to_integers( shift );
my @r = oui_to_integers( shift );
return ( $l[0] <=> $r[0] || $l[1] <=> $r[1] || $l[2] <=> $r[2] );
}
sub dump_cache {
my $self = shift;
my @lines = (
"\n",
"OUI\t\t\t\tOrganization\n",
"company_id\t\t\tOrganization\n",
"\t\t\t\tAddress\n",
"\n", "\n",
);
my $db = $self->cache_handle;
foreach my $oui ( sort { $a cmp $b } keys %{ $db } ) {
my $d = { split( "\0", $db->{ $oui } ) };
my $org = $d->{ 'organization' };
( run in 2.412 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )