File Coverage

blib/lib/Device/MAC.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Device::MAC;
2 2     2   253296 use strict; use warnings;
  2     2   7  
  2         69  
  2         80  
  2         4  
  2         96  
3             our $VERSION = '1.00';
4 2     2   1404 use Moose;
  0            
  0            
5             use Device::OUI;
6             use Carp qw( croak );
7             use overload (
8             '<=>' => 'overload_cmp',
9             'cmp' => 'overload_cmp',
10             '""' => 'overload_stringify',
11             fallback => 1,
12             );
13             use Sub::Exporter -setup => {
14             exports => [qw( mac_to_integers normalize_mac mac_cmp )],
15             };
16              
17             has 'mac' => (
18             is => 'rw',
19             isa => 'Str',
20             predicate => 'has_mac',
21             clearer => 'clear_mac',
22             required => 1,
23             trigger => sub {
24             my ( $self, $val ) = @_;
25             if ( $val ) {
26             $self->clear_mac_dependent
27             } else {
28             $self->clear_mac;
29             }
30             },
31             );
32              
33             sub clear_mac_dependent {
34             my $self = shift;
35              
36             $self->clear_is_universal;
37             $self->clear_is_local;
38             $self->clear_is_unicast;
39             $self->clear_is_multicast;
40             $self->clear_oui;
41             $self->clear_is_eui48;
42             $self->clear_is_eui64;
43             }
44              
45             has 'oui' => ( is => 'rw', isa => 'Maybe[Device::OUI]', lazy_build => 1 );
46             sub _build_oui {
47             my $self = shift;
48             ( my $mac = $self->normalized ) =~ s/[^a-f0-9]//ig;
49             return Device::OUI->new( substr( $mac, 0, 6 ) );
50             }
51              
52             has 'is_eui48' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );
53             sub _build_is_eui48 { return length( shift->normalized ) == 12 }
54              
55             has 'is_eui64' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );
56             sub _build_is_eui64 { return length( shift->normalized ) == 16 }
57              
58             has 'is_unicast' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );
59             sub _build_is_unicast { ! shift->is_multicast }
60              
61             has 'is_multicast' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );
62             sub _build_is_multicast {
63             my $self = shift;
64             my @bytes = mac_to_integers( $self->mac );
65             return $bytes[0] & 1;
66             }
67              
68             has 'is_universal' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );
69             sub _build_is_universal { ! shift->is_local }
70              
71             has 'is_local' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );
72             sub _build_is_local {
73             my $self = shift;
74             my @bytes = mac_to_integers( $self->mac );
75             return $bytes[0] & 2;
76             }
77              
78             sub mac_to_integers {
79             my $mac = shift || return;
80              
81             my @parts = grep { length } split( /[^a-f0-9]+/i, "$mac" );
82             if ( @parts == 1 ) {
83             # 12 characters for EUI-48, 16 for EUI-64
84             if ( length $parts[0] == 12 || length $parts[0] == 16 ) { # 0019e3010e72
85             local $_ = shift( @parts );
86             while ( /([a-f0-9]{2})/ig ) { push( @parts, $1 ) }
87             return map { hex } @parts;
88             }
89             } elsif ( @parts == 6 || @parts == 8 ) { # 00:19:e3:01:0e:72
90             return map { hex } @parts;
91             } elsif ( @parts == 3 || @parts == 4 ) { # 0019:e301:0e72
92             return map { /^(\w\w)(\w\w)$/ && ( hex( $1 ), hex( $2 ) ) } @parts;
93             } else {
94             croak "Invalid MAC format '$mac'";
95             }
96             }
97              
98             sub normalize_mac {
99             my @ints = mac_to_integers( shift );
100             croak "MAC must be 6 bytes long for EUI-48 and 8 bytes long for EUI-64"
101             unless ( @ints == 6 || @ints == 8 );
102             return join( ':', map { sprintf( '%02x', $_ ) } @ints );
103             }
104              
105             sub BUILDARGS {
106             my $class = shift;
107             if ( @_ == 1 && ! ref $_[0] ) { return { mac => shift() } }
108             $class->SUPER::BUILDARGS( @_ );
109             }
110              
111             sub overload_stringify { return shift->normalized }
112              
113             sub overload_cmp { return mac_cmp( pop( @_ ) ? reverse @_ : @_ ) }
114             sub mac_cmp {
115             my @l = mac_to_integers( shift );
116             my @r = mac_to_integers( shift );
117              
118             while ( @l && @r ) {
119             if ( $l[0] == $r[0] ) { shift( @l ); shift( @r ); }
120             return $l[0] <=> $r[0];
121             }
122             return 0;
123             }
124              
125             has 'normalized' => ( is => 'rw', isa => 'Maybe[Str]', lazy_build => 1 );
126             sub _build_normalized { normalize_mac( shift->mac ) }
127              
128             1;
129             __END__
130              
131             =head1 NAME
132              
133             Device::MAC - Handle hardware MAC Addresses (EUI-48 and EUI-64)
134              
135             =head1 SYNOPSIS
136              
137             use Device::MAC;
138            
139             my $mac = Device::MAC->new( '00:19:e3:01:0e:72' );
140             print $mac->normalized."\n";
141             if ( $mac->is_unicast ) {
142             print "\tIs Unicast\n";
143             } elsif ( $mac->is_multicast ) {
144             print "\tIs Multicast\n";
145             }
146             if ( $mac->is_local ) {
147             print "\tIs Locally Administered\n";
148             } elsif ( $mac->is_universal ) {
149             print "\tIs Universally Administered\n";
150             print "\tVendor: ".$mac->oui->organization."\n";
151             }
152              
153             =head1 DESCRIPTION
154              
155             This module provides an interface to deal with Media Access Control (or MAC)
156             addresses. These are the addresses that uniquely identify a device on a
157             network. Although the common case is hardware addresses on network cards,
158             there are a variety of devices that use this system. This module supports
159             both EUI-48 and EUI-64 addresses.
160              
161             Some devices that use EUI-48 (or MAC-48) addresses include:
162              
163             Ethernet
164             802.11 wireless networks
165             Bluetooth
166             IEEE 802.5 token ring
167             FDDI
168             ATM
169              
170             Some devices that use EUI-64 addresses include:
171              
172             Firewire
173             IPv6
174             ZigBee / 802.15.4 wireless personal-area networks
175              
176             =head1 METHODS
177              
178             =head2 Device::MAC->new( $mac )
179              
180             Creates and returns a new Device::MAC object. The MAC value is required.
181              
182             =head2 $mac->mac
183              
184             Return the MAC that this object was created with.
185              
186             =head2 $mac->oui
187              
188             Returns a L<Device::OUI|Device::OUI> object representing the OUI
189             (Organizationally Unique Identifier) for the MAC. This object can give you
190             information about the vendor of the device represented by this MAC.
191              
192             =head2 $mac->normalized
193              
194             Return a 'normalized' MAC value for this object. The normalized value is in
195             lower-case hex, with colon separators (such as '00:19:e3:01:0e:72').
196              
197             L<Device::MAC|Device::MAC> objects have stringification overloaded to return
198             this value.
199              
200             =head1 FUNCTIONS / EXPORTS
201              
202             Although this module is entirely object oriented, there are a handful of
203             utility functions that you can import from this module if you find a need
204             for them. Nothing is exported by default, so if you want to import any of
205             them you need to say so explicitly:
206              
207             use Device::MAC qw( ... );
208              
209             You can get all of them by importing the ':all' tag:
210              
211             use Device::MAC ':all';
212              
213             The exporting is handled by L<Sub::Exporter|Sub::Exporter>.
214              
215             =head2 normalize_mac( $mac )
216              
217             Given a MAC in any common format, normalizes it into a lower-case, zero padded,
218             hexadecimal format with colon separators.
219              
220             =head2 mac_cmp( $mac1, $mac2 )
221              
222             This is a convenience method, given two Device::MAC objects, or two MACs (in
223             any format acceptable to L</normalize_mac>) will return -1, 0, or 1, depending
224             on whether the first MAC is less than, equal to, or greater than the second
225             one.
226              
227             L<Device::MAC|Device::MAC> objects have C<cmp> and C<< <=> >> overloaded so that
228             simply comparing them will work as expected.
229              
230             =head2 mac_to_integers( $mac )
231              
232             Decodes a MAC into a list of 8 integers. This is primarily used internally,
233             but may be useful in some circumstances.
234              
235             =head1 INTERNAL METHODS
236              
237             These are internal methods that you generally won't have to worry about.
238              
239             =head2 BUILDARGS
240              
241             The BUILDARGS method overloads L<Moose::Object|Moose::Object> to allow you
242             to pass a single string argument containing the MAC when calling L</new>.
243              
244             =head2 overload_cmp
245              
246             A utility method that calls mac_cmp with the appropriate arguments. Used
247             by L<overload|overload>.
248              
249             =head2 overload_stringify
250              
251             Internal method for L<overload> to call when attempting to stringify the
252             object.
253              
254             =head2 clear_mac_dependent
255              
256             This utility method clears the values of any attributes that depend on the
257             MAC. It is called when the MAC attribute it set.
258              
259             =head1 MODULE HOME PAGE
260              
261             The home page of this module is
262             L<http://www.jasonkohles.com/software/device-mac>. This is where you can
263             always find the latest version, development versions, and bug reports. You
264             will also find a link there to report bugs.
265              
266             =head1 SEE ALSO
267              
268             L<http://www.jasonkohles.com/software/device-mac>
269              
270             L<http://en.wikipedia.org/wiki/MAC_Address>
271              
272             L<Device::OUI|Device::OUI>
273              
274             =head1 AUTHOR
275              
276             Jason Kohles C<< <email@jasonkohles.com> >>
277              
278             L<http://www.jasonkohles.com>
279              
280             =head1 COPYRIGHT AND LICENSE
281              
282             Copyright 2008, 2009 Jason Kohles
283              
284             This program is free software; you can redistribute it and/or modify it
285             under the same terms as Perl itself.
286              
287             =cut
288