How to translate from decimal to bit-mask?

3.2k views Asked by At

I have a ACLs system previously built by someone else and I am trying to understand how bit-masking works there. I have this 4 constants defined:

const NONE = 0;
const READ = 1;
const WRITE = 2;
const UPDATE = 4;
const DELETE = 8;

Then in DB I am seeing users with permissions like 1, 2, 5, 9, 15. I have tried to transform them using this tool and I end up with this result:

0 // NONE
1 // READ
2 // WRITE
3 // UPDATE|DELETE
4 // UPDATE
5 // WRITE|DELETE
6 // WRITE|UPDATE
7 // WRITE|UPDATE|DELETE
8 // DELETE
9 // READ|DELETE
10 // READ|UPDATE
11 // READ|UPDATE|DELETE
12 // READ|WRITE
13 // READ|WRITE|DELETE
14 // READ|WRITE|UPDATE
15 // READ|WRITE|DELETE|UPDATE

How I think this work is as follow:

Decimal    Hexadecimal
3          00000011

Because the two last bits are 1 I am assuming that those users having 3 will have UPDATE|DELETE permissions (see table above). Is that right? If not what's the right way to translate from decimal to bit-mask?

3

There are 3 answers

1
maraca On BEST ANSWER

0 = NONE is a special case which can be checked by simple comparison.

If you want to ask the question is constant cn with the value of 2^(n-1) set, then we do this with (1 = yes, 0 = no, % = modulo):

(value / cn) % 2

If we want to get all flags that are set, you can do this with the following pseudo code:

c := 1
while value > 0
    if value % 2 = 1
        // constant c is set
        ...
    end if
    value := value / 2
    c := c * 2
end while
0
CJM On

I know this is years old, but not seeing a resolution was bugging me.

If I had to grade this:

   0 // NONE
   1 // READ
   2 // WRITE
X  3 // UPDATE|DELETE
   4 // UPDATE
X  5 // WRITE|DELETE
   6 // WRITE|UPDATE
X  7 // WRITE|UPDATE|DELETE
   8 // DELETE
   9 // READ|DELETE
X 10 // READ|UPDATE
X 11 // READ|UPDATE|DELETE
X 12 // READ|WRITE
X 13 // READ|WRITE|DELETE
X 14 // READ|WRITE|UPDATE
  15 // READ|WRITE|DELETE|UPDATE

I don't quite see how the permissions "end up with this result". The only thing I can think of is that the "0" is being treated as one of the values--thereby confusing the values--when it is actually the absence any enabled values, meaning effectively no access.

   const NONE = 0;
   const READ = 1;
   const WRITE = 2;
   const UPDATE = 4;
   const DELETE = 8;

Instead, think of the multiple-of-2s constants as bit switches, each of which can be independently enabled(1)/disabled(0), each multiplied using the following positional values added together:

8421

Examples:

0000 is 0+0+0+0 or 0: No access

0011 is 0+0+2+1 or 3: READ/WRITE

0101 is 0+4+0+1 or 5: READ/UPDATE

How I think this work is as follow:

   Decimal    Hexadecimal
   3          00000011

Hex and Binary are getting confused here. It's actually:

Decimal    Hex    Binary
3          03     00000011

Given that, the complete list would translate as:

         _______________________________ 
        | ______________________        |
        || _____________        |       |
        ||| ____        |       |       |
        ||||    |       |       |       |
Value   8421    READ    WRITE   UPDATE  DELETE  Effect
-----   ----    ------  ------  ------  ------  ------
0       0000                                    No Access
1       0001    READ                            Read-Only (view)
2       0010            WRITE                   Write-Only (dropbox-like: add new, no other access)
3       0011    READ    WRITE                   Read/Write (view exiting; add new)
4       0100                    UPDATE          Update-Only (update existing, difficult to imagine an application without read)
5       0101    READ            UPDATE          Read/Update (view, update existing)
6       0110            WRITE   UPDATE          Write/Update (add new; update existing)
7       0111    READ    WRITE   UPDATE          Read/Write/Update
8       1000                            DELETE  Delete-Only (difficult to imagine an application without read)
9       1001    READ                    DELETE  Read/Delete
10      1010            WRITE           DELETE  Write/Delete
11      1011    READ    WRITE           DELETE  Read/Write/Delete
12      1100                    UPDATE  DELETE  Update/Delete
13      1101    READ            UPDATE  DELETE  Read/Update/Delete
14      1110            WRITE   UPDATE  DELETE  Write/Update/Delete
15      1111    READ    WRITE   UPDATE  DELETE  Full-Access
0
U. Windl On

From some Perl code I wrote:

#!/usr/bin/perl
use warnings;
use strict;

# convert numeric status to string as enumerated type
sub enum_status_string($$)
{
    my ($value, $aref) = @_;
    my $last_index = $#$aref;

    return sprintf('%u(%s)', $value, $value > $last_index ?
                   $aref->[$last_index] : $aref->[$value]);
}

# convert numeric status to string as bitset
sub bit_status_string($$)
{
    my ($value, $aref) = @_;
    my $last_index = $#$aref;
    my $result = sprintf('%#x(', $value);
    my $sep;

    for (my ($bit, $mask) = (0, 1); $bit <= $last_index; ++$bit, $mask <<= 1) {
        if ($value & $mask) {
            $result .= $sep
                if ($sep);
            $result .= enum_status_string($bit, $aref);
            $sep = '+';
        }
    }
    return $result . ')';
}

my @ACL = (qw(NONE READ WRITE UPDATE DELETE));

for (my $i = 0; $i < 16; ++$i) {
    print "$i: ", bit_status_string($i, \@ACL), "\n";
}

Output is:

0: 0()
1: 0x1(0(NONE))
2: 0x2(1(READ))
3: 0x3(0(NONE)+1(READ))
4: 0x4(2(WRITE))
5: 0x5(0(NONE)+2(WRITE))
6: 0x6(1(READ)+2(WRITE))
7: 0x7(0(NONE)+1(READ)+2(WRITE))
8: 0x8(3(UPDATE))
9: 0x9(0(NONE)+3(UPDATE))
10: 0xa(1(READ)+3(UPDATE))
11: 0xb(0(NONE)+1(READ)+3(UPDATE))
12: 0xc(2(WRITE)+3(UPDATE))
13: 0xd(0(NONE)+2(WRITE)+3(UPDATE))
14: 0xe(1(READ)+2(WRITE)+3(UPDATE))
15: 0xf(0(NONE)+1(READ)+2(WRITE)+3(UPDATE))