File Coverage

blib/lib/Data/BitMask.pm
Criterion Covered Total %
statement 171 183 93.4
branch 64 90 71.1
condition n/a
subroutine 18 19 94.7
pod 8 9 88.8
total 261 301 86.7


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