ADDED:
* netx.IsPub
* encodingx/hexx

Rest are mostly small corrections and docs
This commit is contained in:
brent saner
2026-06-22 18:51:13 -04:00
parent c6fc692f5e
commit 58556d7281
35 changed files with 5492 additions and 2486 deletions
+30 -39
View File
@@ -11,11 +11,11 @@ import (
type MaskBit uint
/*
NewMaskBit is a convenience function.
It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly.
NewMaskBit is a convenience function.
It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly.
It is highly recommended to set this default as a "None" flag (separate from your iotas!)
as shown in the example.
It is highly recommended to set this default as a "None" flag (separate from your iotas!)
as shown in the example.
*/
func NewMaskBit() (m *MaskBit) {
@@ -35,26 +35,10 @@ func NewMaskBitExplicit(value uint) (m *MaskBit) {
}
/*
HasFlag is true if m has MaskBit flag set/enabled.
HasFlag is true if m has MaskBit flag set/enabled.
THIS WILL RETURN FALSE FOR OR'd FLAGS.
For example:
flagA MaskBit = 0x01
flagB MaskBit = 0x02
flagComposite = flagA | flagB
m *MaskBit = NewMaskBitExplicit(uint(flagA))
m.HasFlag(flagComposite) will return false even though flagComposite is an OR
that contains flagA.
Use [MaskBit.IsOneOf] instead if you do not desire this behavior,
and instead want to test composite flag *membership*.
(MaskBit.IsOneOf will also return true for non-composite equality.)
To be more clear, if MaskBit flag is a composite MaskBit (e.g. flagComposite above),
HasFlag will only return true of ALL bits in flag are also set in MaskBit m.
See the "Composite (OR'd) Flags" section in this module's
documentation for important caveats.
*/
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
@@ -67,19 +51,20 @@ func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
}
/*
IsOneOf is like a "looser" form of [MaskBit.HasFlag]
in that it allows for testing composite membership.
HasOneOf is like a "looser" form of [MaskBit.HasFlag]
in that it allows for testing composite membership.
See [MaskBit.HasFlag] for more information.
See [MaskBit.HasFlag] and the "Composite (OR'd) Flags"
section in this module's documentation for more details.
If composite is *not* an OR'd MaskBit (i.e.
it falls directly on a boundary -- 0, 1, 2, 4, 8, 16, etc.),
then IsOneOf will behave exactly like HasFlag.
If composite is *not* an OR'd MaskBit (i.e.
it falls directly on a boundary -- 0, 1, 2, 4, 8, 16, etc.),
then HasOneOf will behave exactly like [MaskBit.HasFlag].
If m is a composite MaskBit (it usually is) and composite is ALSO a composite MaskBit,
IsOneOf will return true if ANY of the flags set in m is set in composite.
If m is a composite [MaskBit] (it usually is) and composite is ALSO a composite MaskBit,
HasOneOf will return true if ANY of the flags set in composite is set in m.
*/
func (m *MaskBit) IsOneOf(composite MaskBit) (r bool) {
func (m *MaskBit) HasOneOf(composite MaskBit) (r bool) {
var b MaskBit = *m
@@ -89,6 +74,13 @@ func (m *MaskBit) IsOneOf(composite MaskBit) (r bool) {
return
}
/*
IsOneOf is the old name for [MaskBit.HasOneOf].
DEPRECATED: This method will be removed sometime in the future. Use [MaskBit.HasOneOf] instead.
*/
func (m *MaskBit) IsOneOf(composite MaskBit) (r bool) { return m.HasOneOf(composite) }
// AddFlag adds MaskBit flag to m.
func (m *MaskBit) AddFlag(flag MaskBit) {
@@ -114,15 +106,14 @@ func (m *MaskBit) ToggleFlag(flag MaskBit) {
}
/*
Bytes returns the current value of a MasBit as a byte slice (big-endian).
Bytes returns the current value of a MasBit as a byte slice (big-endian).
If trim is false, b will (probably) be 4 bytes long if you're on a 32-bit size system,
and b will (probably) be 8 bytes long if you're on a 64-bit size system. You can determine
the size of the resulting slice via (math/)bits.UintSize / 8.
If trim is true, it will trim leading null bytes (if any). This will lead to an unpredictable
byte slice length in b, but is most likely preferred for byte operations.
If trim is false, b will (probably) be 4 bytes long if you're on a 32-bit size system,
and b will (probably) be 8 bytes long if you're on a 64-bit size system. You can determine
the size of the resulting slice via (math/)bits.UintSize / 8.
If trim is true, it will trim leading null bytes (if any). This will lead to an unpredictable
byte slice length in b, but is most likely preferred for byte operations.
*/
func (m *MaskBit) Bytes(trim bool) (b []byte) {
+117 -44
View File
@@ -11,21 +11,84 @@ However, a [bitmask.MaskBit] is backed by a uint which (depending on your platfo
"But wait, that takes up more memory though!"
Yep, but bitmasking lets you store a "boolean" AT EACH BIT - it operates on
Yep - compared to a *single boolean*. But bitmasking lets you store a "boolean" AT EACH BIT boundary - it operates on
whether a bit in a byte/set of bytes at a given position is 0 or 1.
Which means on 32-bit platforms, a [MaskBit] can have up to 4294967295 "booleans" in a single value (0 to (2^32)-1).
In other words:
On 64-bit platforms, a [MaskBit] can have up to 18446744073709551615 "booleans" in a single value (0 to (2^64)-1).
type (
OptStruct struct {
A bool
B bool
C bool
D bool
E bool
F bool
G bool
H bool
}
)
If you tried to do that with Go bool values, that'd take up 4294967295 bytes (4 GiB)
or 18446744073709551615 bytes (16 EiB - yes, that's [exbibytes]) of RAM for 32-bit/64-bit platforms respectively.
One instance of OptStruct takes up *8 bytes* (*64 bits*).
As a uint8 bitmask, however, it takes up exactly *one byte/8 bits*:
type (
Opt uint8
)
const OptNone Opt = 0
const (
A Opt = 1 << iota // 1
B // 2
C // 4
D // 8
E // 16
F // 32
G // 64
H // 128
// Would overflow to 0:
// I
)
As shown, the size of an unsigned integer determines the number of bit-boundaried flags for that bitmask.
Which means on 32-bit platforms, a [MaskBit] can have up to 32 different flags but only occupies 4 bytes of memory.
On 64-bit platforms, a [MaskBit] can have up to 64 different flags but only occupies 8 bytes of memory.
If you tried to do that with a struct of booleans instead, that'd occupy 32 *bytes* and 64 *bytes* respectively.
In summary, a bitmask set occupies 1/8th the amount of memory as a set of equal number of booleans in Go.
You can, of course, extend this even further by defining meaningful "composites",
or OR'd-combination of flags (see the "Composite Flags" section below):
type Perms uint8
const NoPerms Perms = 0
const (
PermsRead Perms = 1 << iota
PermsList
PermsUpdate
PermsCreate
PermsDelete
PermsUpdateBulk
PermsCreateBulk
PermsDeleteBulk
// This makes it easy to add multiple permissions at once.
PermsCRUD Perms = PermsCreate | PermsRead | PermsUpdate | PermsDelete
PermsCRUDL Perms = PermsCRUD | PermsList
// And so forth.
)
"But that has to be so slow to unpack that!"
Nope. It's not using compression or anything, the CPU is just comparing bit "A" vs. bit "B" 32/64 times. That's super easy work for a CPU.
Nope. It's not using compression or anything, the CPU is just comparing bit "A" vs. bit "B" 32/64 times.
That's super easy work for a CPU.
There's a reason Doom used bitmasking for the "dmflags" value in its server configs.
There's a reason ZDoom used bitmasking for the [dmflags] value in its server configs,
and why *NIX platforms uses them for permission/type modes.
# Usage
@@ -34,6 +97,8 @@ To use this library, set constants like thus:
package main
import (
"fmt"
"r00t2.io/goutils/bitmask"
)
@@ -45,34 +110,32 @@ To use this library, set constants like thus:
// ...
)
var MyMask *bitmask.MaskBit
var (
MyMask *bitmask.MaskBit = bitmask.NewMaskBit()
)
func main() {
MyMask = bitmask.NewMaskBit()
MyMask.AddFlag(OPT1)
MyMask.AddFlag(OPT3)
_ = MyMask
// This would print true.
fmt.Println(MyMask.HasFlag(OPT1))
// As would this:
fmt.Println(MyMask.HasFlag(OPT3))
// But this would print false:
fmt.Println(MyMask.HasFlag(OPT2))
}
This would return true:
MyMask.HasFlag(OPT1)
As would this:
MyMask.HasFlag(OPT3)
But this would return false:
MyMask.HasFlag(OPT2)
# Technical Caveats
TARGETING
Targeting
When implementing, you should always set a "source" mask (e.g. MyMask, from Usage section above)
as the actual value.
When implementing, you should always set MyMask (from Usage section above) as the actual value.
For example, if you are checking a permissions set for a user that has the value, say, 6
var userPerms uint = 6 // 0x0000000000000006
@@ -88,7 +151,7 @@ and your library has the following permission bits defined:
PermsAdmin // 16
)
And you want to see if the user has the PermsRead flag set, you would do:
and you want to see if the user has the PermsRead flag set, you would do:
userPermMask = bitmask.NewMaskBitExplicit(userPerms)
if userPermMask.HasFlag(PermsRead) {
@@ -98,55 +161,65 @@ And you want to see if the user has the PermsRead flag set, you would do:
NOT:
userPermMask = bitmask.NewMaskBitExplicit(PermsRead)
// Nor:
// userPermMask = PermsRead
if userPermMask.HasFlag(userPerms) {
userPermMask.HasFlag(bitmask.MaskBit(userPerms))
NOR:
userPermMask = PermsRead
if userPermMask.HasFlag(bitmask.MaskBit(userPerms)) {
// ...
}
This will be terribly, horribly wrong, cause incredibly unexpected results,
and quite possibly cause massive security issues. Don't do it.
COMPOSITES
Remember, [MaskBit.HasFlag] (and other methods) are for comparing/modifying an
*authoritative/concrete value* with one or more *attributes*, not the other way around.
If you want to define a set of flags that are a combination of other flags,
your inclination would be to bitwise-OR them together:
Composite Flags
If you want to define a set of flags that are a combination
(a "composite", or OR'd set) of other flags, your inclination
would be to bitwise-OR them together:
const (
flagA bitmask.MaskBit = 1 << iota // 1
flagB // 2
flagC // 4
// ...
)
const (
flagAB bitmask.MaskBit = flagA | flagB // 3
)
Which is fine and dandy. But if you then have:
Which is fine, and the correct approach.
But if you then have:
var myMask *bitmask.MaskBit = bitmask.NewMaskBit()
myMask.AddFlag(flagA)
You may expect this call to [MaskBit.HasFlag]:
you may expect this call to [MaskBit.HasFlag]:
myMask.HasFlag(flagAB)
to be true, since flagA is "in" flagAB.
It will return false - HasFlag does strict comparisons.
It will only return true if you then ALSO do:
// This would require setting flagA first.
// The order of setting flagA/flagB doesn't matter,
// but you must have both set for HasFlag(flagAB) to return true.
myMask.AddFlag(flagB)
It would only return true if you did:
or if you do:
// This can be done with or without additionally setting flagA.
// ...
myMask.AddFlag(flagAB)
Instead, if you want to see if a mask has membership within a composite flag,
you can use [MaskBit.IsOneOf].
or:
// ...
myMask.AddFlag(flagA)
myMask.AddFlag(flagB)
Instead, if you want to see if a mask has *at least one flag of* a composite flag,
you can use [MaskBit.HasOneOf].
# Other Options
@@ -157,6 +230,6 @@ you may be interested in one of the following libraries:
* [github.com/abice/go-enum]
* [github.com/jeffreyrichter/enum/enum]
[exbibytes]: https://simple.wikipedia.org/wiki/Exbibyte
[dmflags]: https://doomwiki.org/wiki/DMFlags
*/
package bitmask