bitwise operations for flags?

Viewed 307

I've seen some pieces of code which use bitwise operations in order to create options/flags.

For example, consider the following examples from the ssl library:

# bitwise AND and NOT
ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
ctx.options &= ~ssl.OP_NO_SSLv3

# bitwise OR
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.options |= ssl.OP_NO_TLSv1
client_context.options |= ssl.OP_NO_TLSv1_1

The number values of these flags:

>>> ssl.OP_NO_SSLv3.value
33554432
>>> ssl.OP_NO_TLSv1.value
67108864
>>> ssl.OP_NO_TLSv1_1.value
268435456

I don't understand how this kind of system for creating flags works, or how those numbers were chosen. I've researched how the operators work, but I'm still confused.

Can someone please shine some light on how this works, and the appropriate use case?

2 Answers

The numbers seem a lot less random when you look at them in binary :)

>>> "{:032b}".format(ssl.OP_NO_SSLv3.value)
'00000010000000000000000000000000'

>>> "{:032b}".format(ssl.OP_NO_TLSv1.value)
'00000100000000000000000000000000'

>>> "{:032b}".format(ssl.OP_NO_TLSv1_1.value)
'00010000000000000000000000000000'

>>> client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
... client_context.options |= ssl.OP_NO_TLSv1
... client_context.options |= ssl.OP_NO_TLSv1_1
>>> "{:032b}".format(client_context.options)
'10010110010100100000000001010100'

Each option is just a single bit that gets set or cleared.

Unless you automatically convert decimal to binary in your head, it's silly to print the decimal values of flag clusters. Print in binary, octal, or hex, according to your preference.

The typical use cases are when you have

  • a cluster of related Booleans.
  • a dense sequence of binary fields, such as a machine-code operation.

Think of these as packed records of Booleans. For instance, you might need to track object permissions (read write modify destroy). You can do this as four separate Booleans, or simply make a single value to code all four. For instance, someone with only read and destroy permissions, R--D, would have a permissions value of 1001, or 0x9.

When you want to check for a particular permission, you use the bitwise operations to mask off the ones you don't want. For instance:

r_mask = b'1000
w_mask = b'0100
m_mask = b'0010
d_mask = b'0001

Now, the check is simple. To see whether someone has read permission on an object:

if obj.permission & d_mask:
    # Allow operation

For changing the value, you might allow the operation give either write or modify authority:

if obj.permission & (w_mask | m_mask):

or if you're a hard-liner who memorizes access codes:

if obj.permission & b'0110':

When you're working with a packed record of small fields, such as machine code or communication protocol, it's useful to extract one field from a transmission word. For instance, if you need to grab the opcode from an instruction, you would mask off all but (say) 5 opcode bits and shift them to the right edge of the word:

op_mask = 0x7c00                         # bits 30-26 hold the op code
opcode = (instruction & op_mask) >> 26   # Shift right 26 bits
Related