File Coverage

blib/lib/Data/BitMask.pm
Criterion Covered Total %
statement 147 159 92.4
branch 54 80 67.5
condition n/a
subroutine 18 19 94.7
pod 8 9 88.8
total 227 267 85.0


line stmt bran cond sub pod time code
1             #############################################################################
2             #
3             # Data::BitMask - bitmask manipulation
4             #
5             # Author: Toby Ovod-Everett
6             #############################################################################
7             # Copyright 2003, 2004 Toby Ovod-Everett. All rights reserved
8             #
9             # This program is free software; you can redistribute it and/or modify it
10             # under the same terms as Perl itself.
11             #
12             # For comments, questions, bugs or general interest, feel free to
13             # contact Toby Ovod-Everett at toby@ovod-everett.org
14             #############################################################################
15              
16             =head1 NAME
17              
18             Data::BitMask - bitmask manipulation
19              
20             =head1 SYNOPSIS
21              
22             use Data::BitMask;
23              
24             my $FileMask = Data::BitMask->new(
25             READ => 1,
26             WRITE => 2,
27             EXECUTE => 4,
28             RX => 5,
29             RWX => 7,
30             FULL => 7,
31             );
32              
33             my $mask = $FileMask->build_mask('READ|WRITE');
34             print Data::Dumper->Dump([
35             $FileMask->explain_mask($mask),
36             $FileMask->break_mask($mask)
37             ]);
38            
39             my $mask2 = $FileMask->build_mask({FULL => 1, WRITE => 0});
40              
41             =head1 DESCRIPTION
42              
43             This module allows one to create bitmask manipulator objects that can be used to
44             create bitmask values based on a list of constants, as well as to break apart
45             masks using those constants. The advantages are that you don't have to pollute
46             namespaces to use constants, you can ensure that only appropriate constants are
47             used for specific masks, you can easily break apart and explain masks, and in
48             general it is much easier for the user to interact with masks.
49              
50             The module only interacts with masks that fit in Perl integers. In some places,
51             it presumes that you are using 32 bit integers (i.e. canonicalizing negative
52             values).
53              
54             The module expends a modest amount of overhead in creating the C<Data::BitMask>
55             object so as to speed up future mask manipulations.
56              
57             =head2 Installation instructions
58              
59             This module requires C<Module::Build 0.24> to use the automated installation
60             procedures. With C<Module::Build> installed:
61              
62             Build.PL
63             perl build test
64             perl build install
65              
66             It can also be installed manually by copying C<lib/Data/Bitmask.pm> to
67             C<perl/site/lib/Data/Bitmask.pm>.
68              
69             =head1 Suggest Module Implementation
70              
71             Here is one suggested approach to using bitmask manipulators in a module.
72              
73             {
74             my $cache;
75             sub SECURITY_INFORMATION {
76             $cache ||= Data::BitMask->new(
77             OWNER_SECURITY_INFORMATION => 0x1,
78             GROUP_SECURITY_INFORMATION => 0x2,
79             DACL_SECURITY_INFORMATION => 0x4,
80             SACL_SECURITY_INFORMATION => 0x8,
81             );
82             }
83             }
84              
85             The bitmask manipulator can then be accessed as:
86              
87             &SECURITY_INFORMATION->build_mask('DACL_SECURITY_INFORMATION');
88              
89             Or, if you are outside of the module, as:
90              
91             &Win32::Security::SECURITY_INFORMATION->build_mask('DACL_SECURITY_INFORMATION');
92              
93             This has several advantages:
94              
95             =over 4
96              
97             =item *
98              
99             Demand creation of the C<Data::Bitmask> object. Creating objects with huge
100             numbers of constants (i.e. hundreds or thousands) can be a bit time consuming,
101             so this delays creation until the object actually gets used. At the same time,
102             the created object is cached.
103              
104             =item *
105              
106             Easy access from within in the module, reasonably easy access from outside the
107             module.
108              
109             =item *
110              
111             If the user wants even easier access from outside the module, you can support
112             Exporter and let the sub be exported.
113              
114             =back
115              
116             =head1 Method Reference
117              
118             =cut
119              
120 1     1   33706 use strict;
  1         2  
  1         69  
121              
122             package Data::BitMask;
123              
124 1     1   7 use vars qw($VERSION $masks);
  1         2  
  1         2823  
125              
126             $VERSION = '0.91';
127              
128             $masks = {};
129              
130             =head2 new
131              
132             Creates a new bitmask manipulator. Pass a list of constant and value pairs. The
133             constants do not have to be disjoint, but order does matter. When executing
134             C<explain_mask> or C<explain_const>, constants that are earlier in the list take
135             precendence over those later in the list. Constant names are not allowed to
136             have space or pipes in them, and constant values have to be integers. Constant
137             names are case insensitive but preserving.
138              
139             If the passed value for the constant name is an anonymous array, then it is
140             presumed that the name is the first value and that the remainder consists of
141             name-value pairs of parameters. The only currently supported parameter is
142             C<full_match>, which implies that the constant should only be returned from
143             C<break_mask> or C<explain_mask> if it perfectly matches the mask being
144             explained. For example:
145              
146             [qw(FILES_ONLY_NO_INHERIT full_match 1)] => 1,
147              
148             =cut
149              
150             sub new {
151 3     3 1 282 my $class = shift;
152 3         11 my(@constants) = @_;
153              
154 3 50       14 scalar(@constants) % 2 and &croak("You have to pass an even number of parameters in \@constants.");
155            
156 3         10 my $self = {
157             constants => \@constants,
158             };
159              
160 3         10 bless $self, $class;
161              
162 3         11 $self->_check_constants;
163              
164 3         13 return $self;
165             }
166              
167              
168             =head2 add_constants
169              
170             Adds constants to an existing bitmask manipulator. Pass a list of constant and
171             value pairs as for C<new>. Constants will be added to the end of the list (see
172             C<new> for an explanation of ordering concerns).
173              
174             The main use for C<add_constants> is adding aggregate constants created by using
175             C<build_mask>.
176              
177             =cut
178              
179             sub add_constants {
180 1     1 1 82 my $self = shift;
181 1         5 my(@constants) = @_;
182              
183 1 50       4 scalar(@constants) % 2 and &croak("You have to pass an even number of parameters in \@constants.");
184 1         3 push(@{$self->{constants}}, @constants);
  1         8  
185 1         4 $self->_check_constants;
186             }
187              
188             sub _iterate_constants {
189 12     12   14 my $self = shift;
190 12         19 my($sub) = @_;
191              
192 12         13 foreach my $i (0..@{$self->{constants}}/2-1) {
  12         46  
193 90         160 my $name = $self->{constants}->[$i*2];
194 90         347 my $params;
195 90 100       171 if (ref($name) eq 'ARRAY') {
196 3         9 my(@temp) = @$name;
197 3         6 $name = shift @temp;
198 3         10 $params = {@temp};
199             }
200 90         217 $sub->($self, $name, $self->{constants}->[$i*2+1], $params);
201             }
202             }
203              
204             sub _check_constants {
205 4     4   6 my $self = shift;
206              
207             $self->_iterate_constants( sub {
208 30     30   74 local $^W = 0;
209 30 50       104 $_[1] =~ /(\s|\|)/ and &croak("Constant names cannot have spaces or pipes: '$_[1]'.");
210 30 50       91 int($_[1]) eq $_[1] and &croak("Constant names cannot be integers: '$_[1]'.");
211 30 50       83 int($_[2]) eq $_[2] or &croak("Constant values have to be integers: '$_[1]' '$_[2]'.");
212 30 50       60 int($_[2]) < 0 and &croak("Constant values have to be positive integers: '$_[1]' '$_[2]'.");
213 30         88 $_[2] = int($_[2]);
214 4         42 });
215              
216 4         77 $self->_build_forward_cache;
217 4         32 $self->_build_reverse_cache;
218 4         24 $self->_build_occlusion_cache;
219              
220             }
221              
222             sub _build_forward_cache {
223 4     4   8 my $self = shift;
224              
225 4         10 $self->{forward_cache} = {};
226              
227             $self->_iterate_constants( sub {
228 30     30   51 my($self, $name, $value, $params) = @_;
229 30         45 $name = uc($name);
230 30 50       71 if (exists $self->{forward_cache}->{$name}) {
231 0 0       0 $self->{forward_cache}->{$name} != $value and &croak("Multiple values for constant '$name'.");
232             }
233 30         98 $self->{forward_cache}->{$name} = $value;
234 4         27 });
235             }
236              
237             sub _build_reverse_cache {
238 4     4   6 my $self = shift;
239              
240 4         10 $self->{reverse_cache} = {};
241 4         10 $self->{full_match} = {};
242              
243             $self->_iterate_constants( sub {
244 30     30   43 my($self, $name, $value, $params) = @_;
245 30         31 push(@{$self->{reverse_cache}->{$value}}, $name);
  30         180  
246 30 100       112 $self->{full_match}->{$name} = undef if $params->{full_match};
247 4         24 });
248             }
249              
250             sub _build_occlusion_cache {
251 4     4   7 my $self = shift;
252              
253 4         9 $self->{occlusion_cache} = {};
254              
255 4         5 my(@temp) = map {int($_)} keys %{$self->{reverse_cache}};
  19         37  
  4         17  
256              
257 4         12 foreach my $valuer (@temp) {
258 19         41 my $namer = $self->{reverse_cache}->{$valuer}->[0];
259 19         44 $self->{occlusion_cache}->{$namer} = [];
260 19         31 foreach my $valued (@temp) {
261 125         162 foreach my $named (@{$self->{reverse_cache}->{$valued}}) {
  125         249  
262 207 100       405 $namer eq $named and next;
263 188 100       433 if ( $valued == ($valued & $valuer) ) {
264 36         35 push(@{$self->{occlusion_cache}->{$namer}}, $named);
  36         146  
265             }
266             }
267             }
268             }
269             }
270              
271              
272             =head2 build_mask
273              
274             This takes one of three things as a parameter:
275              
276             =over 4
277              
278             =item *
279              
280             scalar - string is split on 'C<|>' and/or whitespace to generate a list of
281             constants
282              
283             =item *
284              
285             ARRAY ref - elements are the list of constants
286              
287             =item *
288              
289             HASH ref - keys with true values are the list of constants; keys with false
290             values are subtracted from the resultant mask
291              
292             =back
293              
294             In all situations, integers are legal in place of constant names and are treated
295             as the value, after adding 2**32 to any negative integers.
296              
297             =cut
298              
299             sub build_mask {
300 29     29 1 1395 my $self = shift;
301 29         43 my($struct) = @_;
302              
303 29         32 my(@add, @sub);
304              
305 29         66 local $^W = 0;
306              
307 29 100       111 if (ref($struct) eq 'ARRAY') {
    100          
    100          
308 1         3 @add = map {uc($_)} @{$struct};
  2         6  
  1         2  
309             } elsif (ref($struct) eq 'HASH') {
310 10         28 @add = map {uc($_)} grep {$struct->{$_}} keys %$struct;
  16         35  
  21         38  
311 10         23 @sub = map {uc($_)} grep {!$struct->{$_}} keys %$struct;
  5         13  
  21         47  
312             } elsif (int($struct) eq $struct) {
313 2 100       13 return int($struct) < 0 ? int($struct) + 2**31 + 2**31 : int($struct);
314             } else {
315 16         91 @add = map {uc($_)} split(/\s*\|\s*|\s+/, $struct);
  32         71  
316             }
317              
318 27         40 my $mask = 0;
319 27         41 foreach my $i (@add) {
320 50 100       118 if (int($i) eq $i) {
321 11 100       29 $mask |= (int($i) < 0 ? int($i) + 2**31 + 2**31 : int($i));
322             } else {
323 39 100       250 exists $self->{forward_cache}->{$i} or &croak("Unable to find constant '$i'");
324 38         89 $mask |= $self->{forward_cache}->{$i};
325             }
326             }
327              
328 26         44 foreach my $i (@sub) {
329 5 100       13 if (int($i) eq $i) {
330 2 50       9 $mask &= ~(int($i) < 0 ? int($i) + 2**31 + 2**31 : int($i));
331             } else {
332 3 50       8 exists $self->{forward_cache}->{$i} or &croak("Unable to find constant '$i'");
333 3         9 $mask &= ~$self->{forward_cache}->{$i};
334             }
335             }
336              
337 26         142 return $mask;
338             }
339              
340             =head2 break_mask
341              
342             Breaks a mask apart. Pass a mask value as an integer. Returns a hash of all
343             constants whose values are subsets of the passed mask. Values are set to 1 so
344             the result can safely be passed to C<build_mask>.
345              
346             Commonly used for operations like:
347              
348             if ($MaskManipulator->break_mask($my_mask_value)->{CONSTANT}) {
349              
350             Note that C<break_mask> accepts
351              
352             To eliminate a constant from explain_mask or break_mask unless it perfectly
353             matches, use C<full_match> constants.
354              
355             =cut
356              
357             sub break_mask {
358 11     11 1 621 my $self = shift;
359 11         17 my($mask) = @_;
360              
361 11         43 local $^W = 0;
362              
363 11 50       39 if (int($mask) eq $mask) {
364 11 50       24 $mask = int($mask) < 0 ? int($mask) + 2**31 + 2**31 : int($mask);
365             } else {
366 0         0 $mask = $self->build_mask($mask);
367             }
368              
369 11         17 my($struct) = {};
370 11         14 my $testmask = 0;
371 11 50       24 $mask = int($mask + ($mask < 0 ? (2**31 + 2**31) : 0));
372              
373 11         12 while (my($value, $names) = each(%{$self->{reverse_cache}})) {
  75         234  
374 64 100       147 if ( int($value) == ($mask & int($value)) ) {
375 39         53 my(@names) = grep {!exists $self->{full_match}->{$_}} @$names;
  55         163  
376 39 100       73 scalar(@names) or next;
377 38         56 @{$struct}{@names} = (1) x scalar(@names);
  38         95  
378 38         83 $testmask |= int($value);
379             }
380             }
381              
382 11 100       816 $testmask == $mask or &croak("Unable to break down mask $mask completely. Found $testmask.");
383              
384 8         30 return $struct;
385             }
386              
387             =head2 explain_mask
388              
389             Explains a mask in terms of a relatively minimal set of constants. Pass either
390             a mask value as an integer or any valid parameter for C<build_mask>. Returns a
391             hash of constants that will recreate the mask. Many times, this will be the
392             minimum number of constants necessary to describe the mask. Note that creating
393             the true minimum set of constants is somewhat painful (see Knapsack problem).
394              
395             The algorithm used by C<explain_mask> is to first test for a constant that
396             perfectly matches the mask. If one is found, this is the obvious answer. In
397             the absence of a perfect match, C<break_mask> is used to generate a maximal
398             solution. All simply occluded constants are then eliminated (that is to say,
399             all constants in the list whose values are subsets of another single constant).
400             This means, for instance, that if you had only three constants, AB => 3, BC =>
401             6, and AC => 5, C<explain_mask> would return all three when passed the value 7
402             because no one constant is a subset of any single one of the others.
403              
404             To eliminate a constant from explain_mask or break_mask unless it perfectly
405             matches, use C<full_match> constants.
406              
407             =cut
408              
409             sub explain_mask {
410 10     10 1 1638 my $self = shift;
411 10         17 my($mask) = @_;
412              
413 10         28 local $^W = 0;
414              
415 10 50       28 if (int($mask) eq $mask) {
416 10 50       28 $mask = int($mask) < 0 ? int($mask) + 2**31 + 2**31 : int($mask);
417             } else {
418 0         0 $mask = $self->build_mask($mask);
419             }
420              
421 10 100       191 return {$self->{reverse_cache}->{$mask}->[0] => 1} if exists $self->{reverse_cache}->{$mask};
422              
423 7         20 my $struct = $self->break_mask($mask);
424 5         23 my(@temp) = keys(%$struct);
425              
426 5         12 foreach my $namer (@temp) {
427 34 100       70 exists $struct->{$namer} or next;
428 18         20 foreach my $named (@{$self->{occlusion_cache}->{$namer}}) {
  18         45  
429 31 100       84 delete $struct->{$named} if exists $struct->{$named};
430             }
431             }
432              
433 5         29 return $struct;
434             }
435              
436              
437             =head2 build_const
438              
439             This takes one of two things as a parameter:
440              
441             =over 4
442              
443             =item *
444              
445             scalar integer - if a scalar integer is passed, then the value is simply
446             returned, after adding 2**32 to any negative integers
447              
448             =item *
449              
450             scalar - string is looked up in the list of constants
451              
452             =back
453              
454             =cut
455              
456             sub build_const {
457 0     0 1 0 my $self = shift;
458 0         0 my($const) = @_;
459              
460 0         0 local $^W = 0;
461              
462 0 0       0 if (int($const) eq $const) {
463 0 0       0 return int($const) < 0 ? int($const) + 2**31 + 2**31 : int($const);
464             } else {
465 0 0       0 exists $self->{forward_cache}->{$const} or &croak("Unable to find constant '$const'");
466 0         0 return $self->{forward_cache}->{$const};
467             }
468             }
469              
470             =head2 explain_const
471              
472             Looks for a perfect match for the passed mask value. Pass either a mask value
473             as an integer or any valid parameter for C<build_mask>. If one is not found, it
474             croaks.
475              
476             =cut
477              
478             sub explain_const {
479 2     2 1 209 my $self = shift;
480 2         4 my($const) = @_;
481              
482 2         5 local $^W = 0;
483              
484 2 50       31 if (int($const) eq $const) {
485 2 50       9 $const = int($const) < 0 ? int($const) + 2**31 + 2**31 : int($const);
486             } else {
487 0 0       0 exists $self->{forward_cache}->{$const} or &croak("Unable to find constant '$const'");
488 0         0 $const = $self->{forward_cache}->{$const};
489             }
490              
491 2 100       18 return $self->{reverse_cache}->{$const}->[0] if exists $self->{reverse_cache}->{$const};
492 1         95 &croak("Unable to lookup $const.");
493             }
494              
495              
496             =head2 get_constants
497              
498             Returns all constants passed either to C<new> or C<add_constants>.
499              
500             =cut
501              
502             sub get_constants {
503 3     3 1 87 my $self = shift;
504              
505 3         4 return @{$self->{constants}};
  3         38  
506             }
507              
508              
509             ### croak autoload is courtesy of Mark Jason-Dominus,
510             ### http://perl.plover.com/yak/tricks/samples/slide122.html
511              
512             sub croak {
513 1     1 0 10 require Carp;
514              
515 1         4 local $^W = 0;
516 1         6 *croak = \&Carp::croak;
517 1         193 goto &croak;
518             }
519              
520              
521             =head1 AUTHOR
522              
523             Toby Ovod-Everett, toby@ovod-everett.org
524              
525             =head1 LICENSE
526              
527             Copyright 2003, 2004 Toby Ovod-Everett. All rights reserved.
528             This program is free software; you can redistribute it
529             and/or modify it under the same terms as Perl itself.
530              
531             =cut
532              
533             1;