File Coverage

blib/lib/Data/Bitfield.pm
Criterion Covered Total %
statement 119 132 90.1
branch 27 38 71.0
condition 13 18 72.2
subroutine 21 27 77.7
pod 5 6 83.3
total 185 221 83.7


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2014-2019 -- leonerd@leonerd.org.uk
5              
6             package Data::Bitfield;
7              
8 5     5   284694 use strict;
  5         47  
  5         140  
9 5     5   23 use warnings;
  5         11  
  5         200  
10              
11             our $VERSION = '0.03';
12              
13 5     5   26 use Exporter 'import';
  5         10  
  5         289  
14             our @EXPORT_OK = qw( bitfield boolfield intfield enumfield constfield );
15              
16 5     5   33 use Carp;
  5         8  
  5         3162  
17              
18             =head1 NAME
19              
20             C - manage data packed into bytes of multiple bit fields
21              
22             =head1 SYNOPSIS
23              
24             use Data::Bitfield qw( bitfield boolfield enumfield );
25              
26             # The stat(2) st_mode field on Linux
27             bitfield MODE =>
28             format => enumfield(12,
29             undef, "fifo", "char", undef, "dir", undef, "block", undef,
30             "regular", undef, "link", undef, "socket", undef, undef, undef ),
31             set_uid => boolfield(11),
32             set_gid => boolfield(10),
33             sticky => boolfield(9),
34             user_read => boolfield(8),
35             user_write => boolfield(7),
36             user_exec => boolfield(6),
37             group_read => boolfield(5),
38             group_write => boolfield(4),
39             group_exec => boolfield(3),
40             other_read => boolfield(2),
41             other_write => boolfield(1),
42             other_exec => boolfield(0);
43              
44             my %modebits = unpack_MODE( stat($path)->mode );
45              
46             Z<>
47              
48             # The flag register of a Z80
49             bitfield FLAGS =>
50             sign => boolfield(7),
51             zero => boolfield(6),
52             halfcarry => boolfield(4),
53             parity => boolfield(2),
54             subtract => boolfield(1),
55             carry => boolfield(0);
56              
57             =head1 DESCRIPTION
58              
59             This module provides a single primary function, C, which creates
60             helper functions in the package that calls it, to assist in managing data that
61             is encoded in sets of bits, called bitfields. This may be useful when
62             interacting with a low-level networking protocol, binary file format, hardware
63             devices, or similar purposes.
64              
65             =head2 bitfield
66              
67             bitfield $name, %fields
68              
69             Creates two new functions in the calling package whose names are derived from
70             the string C<$name> passed here. These functions will be symmetric opposites,
71             which convert between a key/value list of field values, and their packed
72             binary byte-string or integer representation.
73              
74             $packed_value = pack_$name( %field_values )
75              
76             %field_values = unpack_$name( $packed_value )
77              
78             These two functions will work to a set of field names that match those field
79             definitions given to the C function that declared them.
80              
81             Each field has a name and a definition. Its definition comes from one of the
82             following field-declaration functions.
83              
84             Additional options may be passed by giving a C reference as the first
85             argument, before the structure name.
86              
87             bitfield { %options }, $name, %fields
88              
89             Recognised options are:
90              
91             =over 4
92              
93             =item format => "bytes-LE" | "bytes-BE" | "integer"
94              
95             Defines the format that the C function will return and the
96             C function will expect to receive as input. The two C
97             formats describe a packed binary string in either little- or big-endian
98             direction, and C describes an integer numerical value.
99              
100             Note that currently the C format is limited to values 32bits wide or
101             smaller.
102              
103             Optional; will default to C if not supplied. This default may change
104             in a later version - make sure to always specify it for now.
105              
106             =item unrecognised_ok => BOOL
107              
108             If true, the C function will not complain about unrecognised field
109             names; they will simply be ignored.
110              
111             =back
112              
113             =cut
114              
115             sub bitfield
116             {
117 15     15 1 38 my $pkg = caller;
118 15         46 bitfield_into_caller( $pkg, @_ );
119             }
120              
121             my %VALID_FORMATS = map { $_ => 1 } qw( bytes-LE bytes-BE integer );
122              
123             sub bitfield_into_caller
124             {
125 15     15 0 27 my $pkg = shift;
126 15 100       50 my %options = ( ref $_[0] eq "HASH" ) ? %{ +shift } : ();
  10         49  
127 15         52 my ( $name, @args ) = @_;
128              
129 15         34 my $unrecognised_ok = !!$options{unrecognised_ok};
130 15   100     103 my $format = $options{format} // "integer";
131 15 50       45 $VALID_FORMATS{$format} or
132             croak "Invalid 'format' value $format";
133              
134 15         34 my $used_bits = '';
135              
136 15         23 my $constmask = '';
137 15         23 my $constval = '';
138              
139 15         49 my %fields;
140              
141 15         35 while( @args ) {
142 35         60 my $name = shift @args;
143 35 100       71 if( !defined $name ) {
144 1         2 my ( $shift, $width, $value ) = @{ shift @args };
  1         2  
145 1         4 my $mask = pack "L<", ( ( 1 << $width ) - 1 ) << $shift;
146              
147 1 50       6 croak "Constfield collides with other defined bits"
148             if ( $used_bits & $mask ) !~ m/^\0*$/;
149              
150 1         3 $constval |= pack( "L<", $value << $shift );
151 1         2 $used_bits |= $mask;
152              
153 1         4 next;
154             }
155              
156 34         48 my ( $shift, $width, $encoder, $decoder ) = @{ shift @args };
  34         70  
157 34         82 my $offs = int( $shift / 8 ); $shift %= 8;
  34         48  
158              
159 34         126 my $mask = ( "\x00" x $offs ) . pack "L<", ( ( 1 << $width ) - 1 ) << $shift;
160              
161             croak "Field $name is defined twice"
162 34 100       205 if $fields{$name};
163 33 100       264 croak "Field $name collides with other defined fields"
164             if ( $used_bits & $mask ) !~ m/^\0*$/;
165              
166 32         97 $fields{$name} = [ $mask, $offs, $shift, $encoder, $decoder ];
167 32         97 $used_bits |= $mask;
168             }
169              
170 13         58 $used_bits =~ s/\0+$//;
171 13         27 my $datalen = length $used_bits;
172              
173             my $packsub = sub {
174 16     16   529 my %args = @_;
175 16         41 my $ret = $constval;
176 16         45 foreach ( keys %args ) {
177 27 50 100     293 defined( my $f = $fields{$_} ) or
      66        
178             $unrecognised_ok and next or
179             croak "Unexpected field '$_'";
180              
181 25         61 my ( $mask, $offs, $shift, $encoder ) = @$f;
182              
183 25         39 my $v = $args{$_};
184 25 100       65 $v = $encoder->($v) if $encoder;
185 25 100       242 defined $v or croak "Unsupported value for '$_'";
186              
187             {
188 5     5   39 no warnings 'numeric';
  5         9  
  5         3240  
  24         39  
189 24 100       299 int $v eq $v or
190             croak "Expected an integer value for '$_'";
191             }
192              
193 23         80 my $bits = ( "\x00" x $offs ) . ( pack "L<", $v << $shift );
194 23 100 66     263 $v >= 0 and ( $bits & ~$mask ) =~ m/^\0+$/ or
195             croak "Value out of range for '$_'";
196              
197 22         62 $ret |= $mask & $bits;
198             }
199 12         106 return substr( $ret, 0, $datalen );
200 13         88 };
201              
202             my $unpacksub = sub {
203 9     9   21 my ( $val ) = @_;
204             # Bitwise extend so there's always enough bits to unpack
205 9         18 $val .= "\0\0\0";
206             # TODO: check constmask
207 9         16 my @ret;
208 9         31 foreach ( keys %fields ) {
209 22         81 my $f = $fields{$_};
210 22         55 my ( $mask, $offs, $shift, undef, $decoder ) = @$f;
211              
212 22         79 my $v = unpack( "L<", substr( $val & $mask, $offs, 4 ) ) >> $shift;
213              
214 22 100       57 $v = $decoder->($v) if $decoder;
215 22         64 push @ret, $_ => $v;
216             }
217 9         85 return @ret;
218 13         62 };
219              
220 13 100       53 if( $format eq "bytes-BE" ) {
    100          
221 2         4 my $orig_packsub = $packsub;
222 2         3 my $orig_unpacksub = $unpacksub;
223              
224             $packsub = sub {
225 2     2   5 return scalar reverse $orig_packsub->(@_);
226 2         6 };
227             $unpacksub = sub {
228 2     2   9 return $orig_unpacksub->(scalar reverse $_[0]);
229 2         6 };
230             }
231             elsif( $format eq "integer" ) {
232 4         9 my $orig_packsub = $packsub;
233 4         7 my $orig_unpacksub = $unpacksub;
234              
235 4         9 my $nbits = $datalen * 8;
236              
237 4 50       12 if( $nbits <= 8 ) {
    0          
    0          
    0          
238 4     6   13 $packsub = sub { unpack "C", $orig_packsub->( @_ ) };
  6         588  
239 4     2   26 $unpacksub = sub { $orig_unpacksub->( pack "C", $_[0] ) };
  2         12  
240             }
241             elsif( $nbits <= 16 ) {
242 0     0   0 $packsub = sub { unpack "S<", $orig_packsub->( @_ ) };
  0         0  
243 0     0   0 $unpacksub = sub { $orig_unpacksub->( pack "S<", $_[0] ) };
  0         0  
244             }
245             elsif( $nbits <= 24 ) {
246 0     0   0 $packsub = sub { unpack( "L<", $orig_packsub->( @_ ) . "\0" ) };
  0         0  
247 0     0   0 $unpacksub = sub { $orig_unpacksub->( pack "L<", $_[0] ) };
  0         0  
248             }
249             elsif( $nbits <= 32 ) {
250 0     0   0 $packsub = sub { unpack "L<", $orig_packsub->( @_ ) };
  0         0  
251 0     0   0 $unpacksub = sub { $orig_unpacksub->( pack "L<", $_[0] ) };
  0         0  
252             }
253             else {
254 0         0 croak "Cannot currently handle integer packing of $nbits wide values";
255             }
256             }
257              
258 13         26 my %subs;
259              
260 13         41 $subs{"pack_$name"} = $packsub;
261 13         32 $subs{"unpack_$name"} = $unpacksub;
262              
263 5     5   39 no strict 'refs';
  5         9  
  5         2409  
264 13         43 *{"${pkg}::$_"} = $subs{$_} for keys %subs;
  26         146  
265             }
266              
267             =head1 FIELD TYPES
268              
269             =cut
270              
271             =head2 boolfield
272              
273             boolfield( $bitnum )
274              
275             Declares a single bit-wide field at the given bit index, whose value is a
276             simple boolean truth.
277              
278             =cut
279              
280             sub boolfield
281             {
282 23     23 1 4269 my ( $bitnum ) = @_;
283 23     14   164 return [ $bitnum, 1, sub { 0 + !!shift }, sub { !!shift } ];
  16         34  
  14         29  
284             }
285              
286             =head2 intfield
287              
288             intfield( $bitnum, $width )
289              
290             Declares a field of C<$width> bits wide, starting at the given bit index,
291             whose value is an integer. It will be shifted appropriately.
292              
293             =cut
294              
295             sub intfield
296             {
297 12     12 1 689 my ( $bitnum, $width ) = @_;
298 12         36 return [ $bitnum, $width ];
299             }
300              
301             =head2 enumfield
302              
303             enumfield( $bitnum, @values )
304              
305             Declares a field some number of bits wide, sufficient to store an integer big
306             enough to act as an index into the list of values, starting at the given bit
307             index. Its value will be automatically converted to or from one of the values
308             given, which should act sensibly as strings for comparison purposes. Holes can
309             be placed in the range of supported values by using C.
310              
311             =cut
312              
313             sub enumfield
314             {
315 6     6 1 823 my ( $bitnum, @values ) = @_;
316 6         10 my $nvalues = scalar @values;
317              
318             # Need to ceil(log2) it
319 6         10 my $width = 1;
320 6         18 $width++ while ( 1 << $width ) < $nvalues;
321              
322 6         11 my $def = intfield( $bitnum, $width );
323             $def->[2] = sub {
324 3     3   5 my $v = shift;
325             defined $values[$_] and $v eq $values[$_] and return $_
326 3   66     30 for 0 .. $#values;
      100        
327 1         3 return undef;
328 6         22 };
329             $def->[3] = sub {
330 2     2   4 return $values[shift];
331 6         27 };
332              
333 6         31 return $def;
334             }
335              
336             =head2 constfield
337              
338             constfield( $bitnum, $width, $value )
339              
340             Declares a field some number of bits wide that stores a constant value. This
341             value will be packed automatically.
342              
343             Unlike other field definitions, this field is not named. It returns a
344             2-element list directly for use in the C list.
345              
346             =cut
347              
348             sub constfield
349             {
350 1     1 1 3 my ( $bitnum, $width, $value ) = @_;
351              
352 1 50 33     10 $value >= 0 and $value < ( 1 << $width ) or
353             croak "Invalid value for constfield of width $width";
354              
355 1         5 return undef, [ $bitnum, $width, $value ];
356             }
357              
358             =head1 TODO
359              
360             =over 4
361              
362             =item *
363              
364             More flexible error-handling - missing/extra values to C, extra bits to C.
365              
366             =item *
367              
368             Allow truely-custom field handling, including code to support discontiguous fields.
369              
370             =back
371              
372             =cut
373              
374             =head1 AUTHOR
375              
376             Paul Evans
377              
378             =cut
379              
380             0x55AA;