Create Flag Using Bitmask and Bitwise Operator in Go

tech · Jul 15, 2022 · ~5 min
Photo by @swimstaralex on Unsplash
Photo by @swimstaralex on Unsplash

Introduction

I revisit some of my college notes, and find that in my first year of college, in Algorithm and Programming Course, Bitwise operator was mentioned but wasn’t highlighted. It’s either I didn’t write the notes on that specific topic (I stopped writing notes in my second semester) or the Lecturer didn’t explain anything about the usage. But then I found it is quite useful for Bitmasking. For example, in Unix’ file access control it uses bitmask to represent the access:

1
2
3
4
5
6
7
8
000 => 0 => can't access anything
001 => 1 => can only execute
010 => 2 => can only read
011 => 3 => can execute+read
100 => 4 => can only write
101 => 5 => can execute+write
110 => 6 => can read+write
111 => 7 => can execute+read+write

All of those combination can be construct with just only 3 bits. As you can see, Bitmask uses sets of bit to show specific access is given or not. If it is 0, means that specific access is not given, otherwise it is given. In this article, you will learn how to implement Bitmasking and its operation in Golang.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main

import "fmt"

type Permission uint8

const (
	// 1
	PermissionExecute Permission = 1 << iota

	// 2
	PermissionRead

	// 4
	PermissionWrite
)

const (
	PermissionAll = PermissionExecute | PermissionRead | PermissionWrite
)

func Set(p, flag Permission) Permission {
	return p | flag
}

func Clear(p, flag Permission) Permission {
	return p &^ flag
}

func HasAll(p, flag Permission) bool {
	return p&flag == flag
}

func HasOneOf(p, flag Permission) bool {
	return p&flag != 0
}

func main() {
	var p Permission
    // you can use Set/Clear(p, PermissionExecute|PermissionRead|PermissionWrite) to set multiple bits
	p = Set(p, PermissionAll)
	p = Clear(p, PermissionRead)

	// false, because PermissionRead is cleared
	hasExecuteAndRead := HasAll(p, PermissionExecute|PermissionRead)

	// true, because PermissionExecute and PermissionWrite are set
	hasExecuteAndWrite := HasAll(p, PermissionExecute|PermissionWrite)

	// true, because PermissionExecute is set even though PermissionRead is cleared
	hasExecuteOrRead := HasOneOf(p, PermissionExecute|PermissionRead)

	fmt.Println(hasExecuteAndRead, hasExecuteAndWrite, hasExecuteOrRead)
}

Let’s go to the explanation line per line.

Set

1
2
3
4
5
6
7
var p Permission // has no permission 000
p = Set(p, PermissionAll) // set all permission 111

// 000 (current permission)
// 111 (PermissionAll)
// --- OR
// 111

Clear

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// clear the bit of PermissionRead (010)
p = Clear(p, PermissionRead)

// reverse the PermissionRead first
// 010
// ---- NOT
// 101 (NOT result)

// 111 (current permission)
// 101 (NOT result)
// --- AND
// 101

It is important to use BITCLEAR (AND NOT) operator instead of NOT (in this case, XOR) operator. BITCLEAR is always set the bit to 0 because it reverse the flag first using NOT, then using AND. Meanwhile, NOT is reverse the bit, so if you doing NOT operation twice, it will toggle back. See the snippet below for example.

Toggle vs Clear

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// toggling using NOT/XOR
foo := PermissionAll
fmt.Printf("Set All %03b\n", foo) // 111
foo ^= PermissionRead
fmt.Printf("Toggle %03b\n", foo) // 101
foo ^= PermissionRead
fmt.Printf("Toggle %03b\n", foo) // 111 it toggles back!

// clearing using BITCLEAR
bar := PermissionAll
fmt.Printf("Set All %03b\n", bar) // 111
bar &^= PermissionRead
fmt.Printf("Clear %03b\n", bar) // 101
bar &^= PermissionRead
fmt.Printf("Clear %03b\n", bar) // 101

HasAll

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// check whether has both Execute AND Read permission
hasExecuteAndRead := HasAll(p, PermissionExecute|PermissionRead) // false

// PermissionExecute|PermissionRead
// 001 (PermissionExecute)
// 010 (PermissionRead)
// --- OR
// 011 (PermissionRead+PermissionExecute)

// check whether has both Execute AND Read permission
// 101 (current permission)
// 011 (PermissionRead+PermissionExecute)
// --- AND
// 001 (PermissionExecute)

// 001 != 011 so it doesn't have both Execute AND Read permission

// check whether has both Execute AND Write permission
hasExecuteAndWrite := HasAll(p, PermissionExecute|PermissionWrite)

// PermissionExecute|PermissionWrite
// 001 (PermissionExecute)
// 100 (PermissionWrite)
// --- OR
// 101 (Write+Execute)

// check whether has both Execute AND Write permission
// 101 (current permission)
// 101 (PermissionWrite+PermissionExecute)
// --- AND
// 101 (PermissionWrite+PermissionExecute)

// 101 == 101 so it has both Execute AND Write permission

HasOneOf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// check whether has either Execute OR Read permission
hasExecuteOrRead := HasOneOf(p, PermissionExecute|PermissionRead)

// PermissionExecute|PermissionRead
// 001 (PermissionExecute)
// 010 (PermissionRead)
// --- OR
// 011 (PermissionRead+PermissionExecute)

// check whether has either Execute OR Read permission
// 101 (current permission)
// 011 (PermissionRead+PermissionExecute)
// --- AND
// 001 (PermissionExecute)

// 001 != 0 so it has either Execute OR Read permission

Things To Be Concerned

You can store the bitmasks on your databases also, but it might be not a good idea to use iota on your logic. From the code above, let’s say you want to remove PermissionRead. Then PermissionWrite will be represented by 2 instead of 4. To avoid this, you may want to set a Deprecated flag to your permission instead of remove it completely from your code. Or, as an alternative, you can put the number manually but it just another pain to handle if you have lots of permission.

Conclusion

In this case, Bitmasking is a useful tool to implement access control. You have implemented the access control in only 1 byte because you only need to use uint8. For example, if you use sets of boolean variables to represent the access control, you need more than 1 variable and it will take more than 1 byte to store.

Thank you for reading!

· · ·

Love This Content?

Any kind of supports is greatly appreciated! Kindly support me via Bitcoin, Ko-fi, Trakteer, or just continue to read another content. You can write a response via Webmention and let me know the URL via Telegraph.

Drop Your Comment Below