forked from TrueCloudLab/frostfs-sdk-go
[#225] container: Refactor and document basic ACL
Replace basic ACL functionality from `acl` package to the `container` one. Create `BasicACL` type and provide convenient interface to work with it. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
af7e20073b
commit
e82a2d86ef
13 changed files with 961 additions and 247 deletions
40
acl/doc.go
40
acl/doc.go
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
Package acl provides primitives to perform handling basic ACL management in NeoFS.
|
||||
|
||||
BasicACL type provides functionality for managing container basic access-control list.
|
||||
For example, setting public basic ACL that could not be extended with any eACL rules:
|
||||
|
||||
import "github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
...
|
||||
c := container.New()
|
||||
c.SetBasicACL(acl.PublicBasicRule)
|
||||
|
||||
Using package types in an application is recommended to potentially work with
|
||||
different protocol versions with which these types are compatible.
|
||||
|
||||
Basic ACL bits meaning:
|
||||
|
||||
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
|
||||
│31│30│29│28│27│26│25│24│23│22│21│20│19│18│17│16│ <- Bit
|
||||
├──┼──┼──┼──┼──┴──┴──┴──┼──┴──┴──┴──┼──┴──┴──┴──┤
|
||||
│ │ │ │ │ RANGEHASH │ RANGE │ SEARCH │ <- Object service method
|
||||
│ │ │ │ ├──┬──┬──┬──┼──┬──┬──┬──┼──┬──┬──┬──┤
|
||||
│ │ │ X│ F│ U│ S│ O│ B│ U│ S│ O│ B│ U│ S│ O│ B│ <- Rule
|
||||
├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
|
||||
│15│14│13│12│11│10│09│08│07│06│05│04│03│02│01│00│ <- Bit
|
||||
├──┴──┴──┴──┼──┴──┴──┴──┼──┴──┴──┴──┼──┴──┴──┴──┤
|
||||
│ DELETE │ PUT │ HEAD │ GET │ <- Object service method
|
||||
├──┬──┬──┬──┼──┬──┬──┬──┼──┬──┬──┬──┼──┬──┬──┬──┤
|
||||
│ U│ S│ O│ B│ U│ S│ O│ B│ U│ S│ O│ B│ U│ S│ O│ B│ <- Rule
|
||||
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
|
||||
|
||||
U - Allows access to the owner of the container.
|
||||
S - Allows access to Inner Ring and container nodes in the current version of network map.
|
||||
O - Clients that do not match any of the categories above.
|
||||
B - Allows using Bear Token ACL rules to replace eACL rules.
|
||||
F - Flag denying Extended ACL. If set Extended ACL is ignored.
|
||||
X - Flag denying different owners of the request and the object.
|
||||
|
||||
Remaining bits are reserved and are not used.
|
||||
*/
|
||||
package acl
|
107
acl/types.go
107
acl/types.go
|
@ -1,107 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BasicACL is Access Control List that defines who can interact with containers and what exactly they can do.
|
||||
type BasicACL uint32
|
||||
|
||||
// String returns BasicACL string representation
|
||||
// in hexadecimal form with 0x prefix.
|
||||
func (a BasicACL) String() string {
|
||||
return fmt.Sprintf("0x%08x", uint32(a))
|
||||
}
|
||||
|
||||
const (
|
||||
// PublicBasicRule is a basic ACL value for final public-read-write container for which extended ACL CANNOT be set.
|
||||
PublicBasicRule BasicACL = 0x1FBFBFFF
|
||||
|
||||
// PrivateBasicRule is a basic ACL value for final private container for which extended ACL CANNOT be set.
|
||||
PrivateBasicRule BasicACL = 0x1C8C8CCC
|
||||
|
||||
// ReadOnlyBasicRule is a basic ACL value for final public-read container for which extended ACL CANNOT be set.
|
||||
ReadOnlyBasicRule BasicACL = 0x1FBF8CFF
|
||||
|
||||
// PublicAppendRule is a basic ACL value for final public-append container for which extended ACL CANNOT be set.
|
||||
PublicAppendRule BasicACL = 0x1FBF9FFF
|
||||
|
||||
// EACLPublicBasicRule is a basic ACL value for non-final public-read-write container for which extended ACL CAN be set.
|
||||
EACLPublicBasicRule BasicACL = 0x0FBFBFFF
|
||||
|
||||
// EACLPrivateBasicRule is a basic ACL value for non-final private container for which extended ACL CAN be set.
|
||||
EACLPrivateBasicRule BasicACL = 0x0C8C8CCC
|
||||
|
||||
// EACLReadOnlyBasicRule is a basic ACL value for non-final public-read container for which extended ACL CAN be set.
|
||||
EACLReadOnlyBasicRule BasicACL = 0x0FBF8CFF
|
||||
|
||||
// EACLPublicAppendRule is a basic ACL value for non-final public-append container for which extended ACL CAN be set.
|
||||
EACLPublicAppendRule BasicACL = 0x0FBF9FFF
|
||||
)
|
||||
|
||||
const (
|
||||
// PublicBasicName is a well-known name for 0x1FBFBFFF basic ACL.
|
||||
// It represents fully-public container without eACL.
|
||||
PublicBasicName = "public-read-write"
|
||||
|
||||
// PrivateBasicName is a well-known name for 0x1C8C8CCC basic ACL.
|
||||
// It represents fully-private container without eACL.
|
||||
PrivateBasicName = "private"
|
||||
|
||||
// ReadOnlyBasicName is a well-known name for 0x1FBF8CFF basic ACL.
|
||||
// It represents public read-only container without eACL.
|
||||
ReadOnlyBasicName = "public-read"
|
||||
|
||||
// PublicAppendName is a well-known name for 0x1FBF9FFF basic ACL.
|
||||
// It represents fully-public container without eACL except DELETE operation is only allowed on the owner.
|
||||
PublicAppendName = "public-append"
|
||||
|
||||
// EACLPublicBasicName is a well-known name for 0x0FBFBFFF basic ACL.
|
||||
// It represents fully-public container that allows eACL.
|
||||
EACLPublicBasicName = "eacl-public-read-write"
|
||||
|
||||
// EACLPrivateBasicName is a well-known name for 0x0C8C8CCC basic ACL.
|
||||
// It represents fully-private container that allows eACL.
|
||||
EACLPrivateBasicName = "eacl-private"
|
||||
|
||||
// EACLReadOnlyBasicName is a well-known name for 0x0FBF8CFF basic ACL.
|
||||
// It represents public read-only container that allows eACL.
|
||||
EACLReadOnlyBasicName = "eacl-public-read"
|
||||
|
||||
// EACLPublicAppendName is a well-known name for 0x0FBF9FFF basic ACL.
|
||||
// It represents fully-public container that allows eACL except DELETE operation is only allowed on the owner.
|
||||
EACLPublicAppendName = "eacl-public-append"
|
||||
)
|
||||
|
||||
// ParseBasicACL parse string ACL (well-known names or hex representation).
|
||||
func ParseBasicACL(basicACL string) (BasicACL, error) {
|
||||
switch basicACL {
|
||||
case PublicBasicName:
|
||||
return PublicBasicRule, nil
|
||||
case PrivateBasicName:
|
||||
return PrivateBasicRule, nil
|
||||
case ReadOnlyBasicName:
|
||||
return ReadOnlyBasicRule, nil
|
||||
case PublicAppendName:
|
||||
return PublicAppendRule, nil
|
||||
case EACLPublicBasicName:
|
||||
return EACLPublicBasicRule, nil
|
||||
case EACLPrivateBasicName:
|
||||
return EACLPrivateBasicRule, nil
|
||||
case EACLReadOnlyBasicName:
|
||||
return EACLReadOnlyBasicRule, nil
|
||||
case EACLPublicAppendName:
|
||||
return EACLPublicAppendRule, nil
|
||||
default:
|
||||
basicACL = strings.TrimPrefix(strings.ToLower(basicACL), "0x")
|
||||
|
||||
value, err := strconv.ParseUint(basicACL, 16, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("can't parse basic ACL: %s", basicACL)
|
||||
}
|
||||
|
||||
return BasicACL(value), nil
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
acl string
|
||||
expected BasicACL
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
acl: PublicBasicName,
|
||||
expected: PublicBasicRule,
|
||||
},
|
||||
{
|
||||
acl: PrivateBasicName,
|
||||
expected: PrivateBasicRule,
|
||||
},
|
||||
{
|
||||
acl: ReadOnlyBasicName,
|
||||
expected: ReadOnlyBasicRule,
|
||||
},
|
||||
{
|
||||
acl: PublicAppendName,
|
||||
expected: PublicAppendRule,
|
||||
},
|
||||
{
|
||||
acl: EACLPublicBasicName,
|
||||
expected: EACLPublicBasicRule,
|
||||
},
|
||||
{
|
||||
acl: EACLPrivateBasicName,
|
||||
expected: EACLPrivateBasicRule,
|
||||
},
|
||||
{
|
||||
acl: EACLReadOnlyBasicName,
|
||||
expected: EACLReadOnlyBasicRule,
|
||||
},
|
||||
{
|
||||
acl: EACLPublicAppendName,
|
||||
expected: EACLPublicAppendRule,
|
||||
},
|
||||
{
|
||||
acl: "0x1C8C8CCC",
|
||||
expected: 0x1C8C8CCC,
|
||||
},
|
||||
{
|
||||
acl: "1C8C8CCC",
|
||||
expected: 0x1C8C8CCC,
|
||||
},
|
||||
{
|
||||
acl: "123456789",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
acl: "0x1C8C8CCG",
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
actual, err := ParseBasicACL(tc.acl)
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
continue
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
acl := BasicACL(0x1fbfbfff)
|
||||
require.Equal(t, "0x1fbfbfff", acl.String())
|
||||
|
||||
acl2, err := ParseBasicACL(PrivateBasicName)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "0x1c8c8ccc", acl2.String())
|
||||
}
|
81
container/acl.go
Normal file
81
container/acl.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package container
|
||||
|
||||
import "strconv"
|
||||
|
||||
// ACLOp enumerates operations under access control inside container.
|
||||
// Non-positive values are reserved and depend on context (e.g. unsupported op).
|
||||
//
|
||||
// Note that type conversion from- and to numerical types is not recommended,
|
||||
// use corresponding constants and/or methods instead.
|
||||
type ACLOp uint32
|
||||
|
||||
const (
|
||||
aclOpZero ACLOp = iota // extreme value for testing
|
||||
|
||||
ACLOpObjectGet // Object.Get rpc
|
||||
ACLOpObjectHead // Object.Head rpc
|
||||
ACLOpObjectPut // Object.Put rpc
|
||||
ACLOpObjectDelete // Object.Delete rpc
|
||||
ACLOpObjectSearch // Object.Search rpc
|
||||
ACLOpObjectRange // Object.GetRange rpc
|
||||
ACLOpObjectHash // Object.GetRangeHash rpc
|
||||
|
||||
aclOpLast // extreme value for testing
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (x ACLOp) String() string {
|
||||
switch x {
|
||||
default:
|
||||
return "UNKNOWN#" + strconv.FormatUint(uint64(x), 10)
|
||||
case ACLOpObjectGet:
|
||||
return "OBJECT_GET"
|
||||
case ACLOpObjectHead:
|
||||
return "OBJECT_HEAD"
|
||||
case ACLOpObjectPut:
|
||||
return "OBJECT_PUT"
|
||||
case ACLOpObjectDelete:
|
||||
return "OBJECT_DELETE"
|
||||
case ACLOpObjectSearch:
|
||||
return "OBJECT_SEARCH"
|
||||
case ACLOpObjectRange:
|
||||
return "OBJECT_RANGE"
|
||||
case ACLOpObjectHash:
|
||||
return "OBJECT_HASH"
|
||||
}
|
||||
}
|
||||
|
||||
// ACLRole enumerates roles covered by container ACL. Each role represents
|
||||
// some party which can be authenticated during container op execution.
|
||||
// Non-positive values are reserved and depend on context (e.g. unsupported role).
|
||||
//
|
||||
// Note that type conversion from- and to numerical types is not recommended,
|
||||
// use corresponding constants and/or methods instead.
|
||||
type ACLRole uint32
|
||||
|
||||
const (
|
||||
aclRoleZero ACLRole = iota // extreme value for testing
|
||||
|
||||
ACLRoleOwner // container owner
|
||||
ACLRoleContainer // nodes of the related container
|
||||
ACLRoleInnerRing // Inner Ring nodes
|
||||
ACLRoleOthers // all others
|
||||
|
||||
aclRoleLast // extreme value for testing
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (x ACLRole) String() string {
|
||||
switch x {
|
||||
default:
|
||||
return "UNKNOWN#" + strconv.FormatUint(uint64(x), 10)
|
||||
case ACLRoleOwner:
|
||||
return "OWNER"
|
||||
case ACLRoleContainer:
|
||||
return "CONTAINER"
|
||||
case ACLRoleInnerRing:
|
||||
return "INNER_RING"
|
||||
case ACLRoleOthers:
|
||||
return "OTHERS"
|
||||
}
|
||||
}
|
272
container/acl_basic.go
Normal file
272
container/acl_basic.go
Normal file
|
@ -0,0 +1,272 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BasicACL represents basic part of the NeoFS container's ACL. It includes
|
||||
// common (pretty simple) access rules for operations inside the container.
|
||||
// See NeoFS Specification for details.
|
||||
//
|
||||
// One can find some similarities with the traditional Unix permission, such as
|
||||
// division into scopes: user, group, others
|
||||
// op-permissions: read, write, etc.
|
||||
// sticky bit
|
||||
// However, these similarities should only be used for better understanding,
|
||||
// in general these mechanisms are different.
|
||||
//
|
||||
// Instances can be created using built-in var declaration, but look carefully
|
||||
// at the default values, and how individual permissions are regulated.
|
||||
// Some frequently used values are presented in BasicACL* values.
|
||||
//
|
||||
// BasicACL instances are comparable: values can be compared directly using
|
||||
// == operator.
|
||||
type BasicACL struct {
|
||||
bits uint32
|
||||
}
|
||||
|
||||
func (x *BasicACL) fromUint32(num uint32) {
|
||||
x.bits = num
|
||||
}
|
||||
|
||||
func (x BasicACL) toUint32() uint32 {
|
||||
return x.bits
|
||||
}
|
||||
|
||||
// common bit sections.
|
||||
const (
|
||||
opAmount = 7
|
||||
bitsPerOp = 4
|
||||
|
||||
bitPosFinal = opAmount * bitsPerOp
|
||||
bitPosSticky = bitPosFinal + 1
|
||||
)
|
||||
|
||||
// per-op bit order.
|
||||
const (
|
||||
opBitPosBearer uint8 = iota
|
||||
opBitPosOthers
|
||||
opBitPosContainer
|
||||
opBitPosOwner
|
||||
)
|
||||
|
||||
// DisableExtension makes BasicACL FINAL. FINAL indicates the ACL non-extendability
|
||||
// in the related container.
|
||||
//
|
||||
// See also Extendable.
|
||||
func (x *BasicACL) DisableExtension() {
|
||||
setBit(&x.bits, bitPosFinal)
|
||||
}
|
||||
|
||||
// Extendable checks if BasicACL is NOT made FINAL using DisableExtension.
|
||||
//
|
||||
// Zero BasicACL is NOT FINAL or extendable.
|
||||
func (x BasicACL) Extendable() bool {
|
||||
return !isBitSet(x.bits, bitPosFinal)
|
||||
}
|
||||
|
||||
// MakeSticky makes BasicACL STICKY. STICKY indicates that only the owner of any
|
||||
// particular object is allowed to operate on it.
|
||||
//
|
||||
// See also Sticky.
|
||||
func (x *BasicACL) MakeSticky() {
|
||||
setBit(&x.bits, bitPosSticky)
|
||||
}
|
||||
|
||||
// Sticky checks if BasicACL is made STICKY using MakeSticky.
|
||||
//
|
||||
// Zero BasicACL is NOT STICKY.
|
||||
func (x BasicACL) Sticky() bool {
|
||||
return isBitSet(x.bits, bitPosSticky)
|
||||
}
|
||||
|
||||
// checks if op is used by the storage nodes within replication mechanism.
|
||||
func isReplicationOp(op ACLOp) bool {
|
||||
//nolint:exhaustive
|
||||
switch op {
|
||||
case
|
||||
ACLOpObjectGet,
|
||||
ACLOpObjectHead,
|
||||
ACLOpObjectPut,
|
||||
ACLOpObjectSearch,
|
||||
ACLOpObjectHash:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AllowOp allows the parties with the given role to the given operation.
|
||||
// Op MUST be one of the ACLOp enumeration. Role MUST be one of:
|
||||
// ACLRoleOwner
|
||||
// ACLRoleContainer
|
||||
// ACLRoleOthers
|
||||
// and if role is ACLRoleContainer, op MUST NOT be:
|
||||
// ACLOpObjectGet
|
||||
// ACLOpObjectHead
|
||||
// ACLOpObjectPut
|
||||
// ACLOpObjectSearch
|
||||
// ACLOpObjectHash
|
||||
//
|
||||
// See also IsOpAllowed.
|
||||
func (x *BasicACL) AllowOp(op ACLOp, role ACLRole) {
|
||||
var bitPos uint8
|
||||
|
||||
switch role {
|
||||
default:
|
||||
panic(fmt.Sprintf("unable to set rules for unsupported role %v", role))
|
||||
case ACLRoleInnerRing:
|
||||
panic("basic ACL MUST NOT be modified for Inner Ring")
|
||||
case ACLRoleOwner:
|
||||
bitPos = opBitPosOwner
|
||||
case ACLRoleContainer:
|
||||
if isReplicationOp(op) {
|
||||
panic("basic ACL for container replication ops MUST NOT be modified")
|
||||
}
|
||||
|
||||
bitPos = opBitPosContainer
|
||||
case ACLRoleOthers:
|
||||
bitPos = opBitPosOthers
|
||||
}
|
||||
|
||||
setOpBit(&x.bits, op, bitPos)
|
||||
}
|
||||
|
||||
// IsOpAllowed checks if parties with the given role are allowed to the given op
|
||||
// according to the BasicACL rules. Op MUST be one of the ACLOp enumeration.
|
||||
// Role MUST be one of the ACLRole enumeration.
|
||||
//
|
||||
// Members with ACLRoleContainer role have exclusive default access to the
|
||||
// operations of the data replication mechanism:
|
||||
// ACLOpObjectGet
|
||||
// ACLOpObjectHead
|
||||
// ACLOpObjectPut
|
||||
// ACLOpObjectSearch
|
||||
// ACLOpObjectHash
|
||||
//
|
||||
// ACLRoleInnerRing members are allowed to data audit ops only:
|
||||
// ACLOpObjectGet
|
||||
// ACLOpObjectHead
|
||||
// ACLOpObjectHash
|
||||
// ACLOpObjectSearch
|
||||
//
|
||||
// Zero BasicACL prevents any role from accessing any operation in the absence
|
||||
// of default rights.
|
||||
//
|
||||
// See also AllowOp.
|
||||
func (x BasicACL) IsOpAllowed(op ACLOp, role ACLRole) bool {
|
||||
var bitPos uint8
|
||||
|
||||
switch role {
|
||||
default:
|
||||
panic(fmt.Sprintf("role is unsupported %v", role))
|
||||
case ACLRoleInnerRing:
|
||||
switch op {
|
||||
case
|
||||
ACLOpObjectGet,
|
||||
ACLOpObjectHead,
|
||||
ACLOpObjectHash,
|
||||
ACLOpObjectSearch:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case ACLRoleOwner:
|
||||
bitPos = opBitPosOwner
|
||||
case ACLRoleContainer:
|
||||
if isReplicationOp(op) {
|
||||
return true
|
||||
}
|
||||
|
||||
bitPos = opBitPosContainer
|
||||
case ACLRoleOthers:
|
||||
bitPos = opBitPosOthers
|
||||
}
|
||||
|
||||
return isOpBitSet(x.bits, op, bitPos)
|
||||
}
|
||||
|
||||
// AllowBearerRules allows bearer to provide extended ACL rules for the given
|
||||
// operation. Bearer rules doesn't depend on container ACL
|
||||
// // extensibility.
|
||||
//
|
||||
// See also AllowedBearerRules.
|
||||
func (x *BasicACL) AllowBearerRules(op ACLOp) {
|
||||
setOpBit(&x.bits, op, opBitPosBearer)
|
||||
}
|
||||
|
||||
// AllowedBearerRules checks if bearer rules are allowed using AllowBearerRules.
|
||||
// Op MUST be one of the ACLOp enumeration.
|
||||
//
|
||||
// Zero BasicACL disallows bearer rules for any op.
|
||||
func (x BasicACL) AllowedBearerRules(op ACLOp) bool {
|
||||
return isOpBitSet(x.bits, op, opBitPosBearer)
|
||||
}
|
||||
|
||||
// EncodeToString encodes BasicACL into hexadecimal string.
|
||||
//
|
||||
// See also DecodeString.
|
||||
func (x BasicACL) EncodeToString() string {
|
||||
return strconv.FormatUint(uint64(x.bits), 16)
|
||||
}
|
||||
|
||||
// Names of the frequently used BasicACL values.
|
||||
const (
|
||||
BasicACLNamePrivate = "private"
|
||||
BasicACLNamePrivateExtended = "eacl-private"
|
||||
BasicACLNamePublicRO = "public-read"
|
||||
BasicACLNamePublicROExtended = "eacl-public-read"
|
||||
BasicACLNamePublicRW = "public-read-write"
|
||||
BasicACLNamePublicRWExtended = "eacl-public-read-write"
|
||||
BasicACLNamePublicAppend = "public-append"
|
||||
BasicACLNamePublicAppendExtended = "eacl-public-append"
|
||||
)
|
||||
|
||||
// Frequently used BasicACL values (each value MUST NOT be modified, make a
|
||||
// copy instead).
|
||||
var (
|
||||
BasicACLPrivate BasicACL // private
|
||||
BasicACLPrivateExtended BasicACL // eacl-private
|
||||
BasicACLPublicRO BasicACL // public-read
|
||||
BasicACLPublicROExtended BasicACL // eacl-public-read
|
||||
BasicACLPublicRW BasicACL // public-read-write
|
||||
BasicACLPublicRWExtended BasicACL // eacl-public-read-write
|
||||
BasicACLPublicAppend BasicACL // public-append
|
||||
BasicACLPublicAppendExtended BasicACL // eacl-public-append
|
||||
)
|
||||
|
||||
// DecodeString decodes string calculated using EncodeToString. Also supports
|
||||
// human-readable names (BasicACLName* constants).
|
||||
func (x *BasicACL) DecodeString(s string) error {
|
||||
switch s {
|
||||
case BasicACLNamePrivate:
|
||||
*x = BasicACLPrivate
|
||||
case BasicACLNamePrivateExtended:
|
||||
*x = BasicACLPrivateExtended
|
||||
case BasicACLNamePublicRO:
|
||||
*x = BasicACLPublicRO
|
||||
case BasicACLNamePublicROExtended:
|
||||
*x = BasicACLPublicROExtended
|
||||
case BasicACLNamePublicRW:
|
||||
*x = BasicACLPublicRW
|
||||
case BasicACLNamePublicRWExtended:
|
||||
*x = BasicACLPublicRWExtended
|
||||
case BasicACLNamePublicAppend:
|
||||
*x = BasicACLPublicAppend
|
||||
case BasicACLNamePublicAppendExtended:
|
||||
*x = BasicACLPublicAppendExtended
|
||||
default:
|
||||
s = strings.TrimPrefix(strings.ToLower(s), "0x")
|
||||
|
||||
v, err := strconv.ParseUint(s, 16, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse hex: %w", err)
|
||||
}
|
||||
|
||||
x.bits = uint32(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
384
container/acl_basic_test.go
Normal file
384
container/acl_basic_test.go
Normal file
|
@ -0,0 +1,384 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBasicACL_DisableExtension(t *testing.T) {
|
||||
var val, val2 BasicACL
|
||||
|
||||
require.True(t, val.Extendable())
|
||||
val2.fromUint32(val.toUint32())
|
||||
require.True(t, val2.Extendable())
|
||||
|
||||
val.DisableExtension()
|
||||
|
||||
require.False(t, val.Extendable())
|
||||
val2.fromUint32(val.toUint32())
|
||||
require.False(t, val2.Extendable())
|
||||
}
|
||||
|
||||
func TestBasicACL_MakeSticky(t *testing.T) {
|
||||
var val, val2 BasicACL
|
||||
|
||||
require.False(t, val.Sticky())
|
||||
val2.fromUint32(val.toUint32())
|
||||
require.False(t, val2.Sticky())
|
||||
|
||||
val.MakeSticky()
|
||||
|
||||
require.True(t, val.Sticky())
|
||||
val2.fromUint32(val.toUint32())
|
||||
require.True(t, val2.Sticky())
|
||||
}
|
||||
|
||||
func TestBasicACL_AllowBearerRules(t *testing.T) {
|
||||
var val BasicACL
|
||||
|
||||
require.Panics(t, func() { val.AllowBearerRules(aclOpZero) })
|
||||
require.Panics(t, func() { val.AllowBearerRules(aclOpLast) })
|
||||
|
||||
require.Panics(t, func() { val.AllowedBearerRules(aclOpZero) })
|
||||
require.Panics(t, func() { val.AllowedBearerRules(aclOpLast) })
|
||||
|
||||
for op := aclOpZero + 1; op < aclOpLast; op++ {
|
||||
val := val
|
||||
|
||||
require.False(t, val.AllowedBearerRules(op))
|
||||
|
||||
val.AllowBearerRules(op)
|
||||
|
||||
for j := aclOpZero + 1; j < aclOpLast; j++ {
|
||||
if j == op {
|
||||
require.True(t, val.AllowedBearerRules(j), op)
|
||||
} else {
|
||||
require.False(t, val.AllowedBearerRules(j), op)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicACL_AllowOp(t *testing.T) {
|
||||
var val, val2 BasicACL
|
||||
|
||||
require.Panics(t, func() { val.IsOpAllowed(aclOpZero, aclRoleZero+1) })
|
||||
require.Panics(t, func() { val.IsOpAllowed(aclOpLast, aclRoleZero+1) })
|
||||
require.Panics(t, func() { val.IsOpAllowed(aclOpZero+1, aclRoleZero) })
|
||||
require.Panics(t, func() { val.IsOpAllowed(aclOpZero+1, aclRoleLast) })
|
||||
|
||||
for op := aclOpZero + 1; op < aclOpLast; op++ {
|
||||
require.Panics(t, func() { val.AllowOp(op, ACLRoleInnerRing) })
|
||||
|
||||
if isReplicationOp(op) {
|
||||
require.Panics(t, func() { val.AllowOp(op, ACLRoleContainer) })
|
||||
require.True(t, val.IsOpAllowed(op, ACLRoleContainer))
|
||||
}
|
||||
}
|
||||
|
||||
require.True(t, val.IsOpAllowed(ACLOpObjectGet, ACLRoleInnerRing))
|
||||
require.True(t, val.IsOpAllowed(ACLOpObjectHead, ACLRoleInnerRing))
|
||||
require.True(t, val.IsOpAllowed(ACLOpObjectSearch, ACLRoleInnerRing))
|
||||
require.True(t, val.IsOpAllowed(ACLOpObjectHash, ACLRoleInnerRing))
|
||||
|
||||
const op = aclOpZero + 1
|
||||
const role = ACLRoleOthers
|
||||
|
||||
require.False(t, val.IsOpAllowed(op, role))
|
||||
val2.fromUint32(val.toUint32())
|
||||
require.False(t, val2.IsOpAllowed(op, role))
|
||||
|
||||
val.AllowOp(op, role)
|
||||
|
||||
require.True(t, val.IsOpAllowed(op, role))
|
||||
val2.fromUint32(val.toUint32())
|
||||
require.True(t, val2.IsOpAllowed(op, role))
|
||||
}
|
||||
|
||||
type opsExpected struct {
|
||||
owner, container, innerRing, others, bearer bool
|
||||
}
|
||||
|
||||
func testOp(t *testing.T, v BasicACL, op ACLOp, exp opsExpected) {
|
||||
require.Equal(t, exp.owner, v.IsOpAllowed(op, ACLRoleOwner), op)
|
||||
require.Equal(t, exp.container, v.IsOpAllowed(op, ACLRoleContainer), op)
|
||||
require.Equal(t, exp.innerRing, v.IsOpAllowed(op, ACLRoleInnerRing), op)
|
||||
require.Equal(t, exp.others, v.IsOpAllowed(op, ACLRoleOthers), op)
|
||||
require.Equal(t, exp.bearer, v.AllowedBearerRules(op), op)
|
||||
}
|
||||
|
||||
type expected struct {
|
||||
extendable, sticky bool
|
||||
|
||||
mOps map[ACLOp]opsExpected
|
||||
}
|
||||
|
||||
func testBasicACLPredefined(t *testing.T, val BasicACL, name string, exp expected) {
|
||||
require.Equal(t, exp.sticky, val.Sticky())
|
||||
require.Equal(t, exp.extendable, val.Extendable())
|
||||
|
||||
for op, exp := range exp.mOps {
|
||||
testOp(t, val, op, exp)
|
||||
}
|
||||
|
||||
s := val.EncodeToString()
|
||||
|
||||
var val2 BasicACL
|
||||
|
||||
require.NoError(t, val2.DecodeString(s))
|
||||
require.Equal(t, val, val2)
|
||||
|
||||
require.NoError(t, val2.DecodeString(name))
|
||||
require.Equal(t, val, val2)
|
||||
}
|
||||
|
||||
func TestBasicACLPredefined(t *testing.T) {
|
||||
t.Run("private", func(t *testing.T) {
|
||||
exp := expected{
|
||||
extendable: false,
|
||||
sticky: false,
|
||||
mOps: map[ACLOp]opsExpected{
|
||||
ACLOpObjectHash: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectRange: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectSearch: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectDelete: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectPut: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: false,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectHead: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectGet: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testBasicACLPredefined(t, BasicACLPrivate, BasicACLNamePrivate, exp)
|
||||
exp.extendable = true
|
||||
testBasicACLPredefined(t, BasicACLPrivateExtended, BasicACLNamePrivateExtended, exp)
|
||||
})
|
||||
|
||||
t.Run("public-read", func(t *testing.T) {
|
||||
exp := expected{
|
||||
extendable: false,
|
||||
sticky: false,
|
||||
mOps: map[ACLOp]opsExpected{
|
||||
ACLOpObjectHash: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectRange: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectSearch: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectDelete: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectPut: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: false,
|
||||
others: false,
|
||||
bearer: false,
|
||||
},
|
||||
ACLOpObjectHead: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectGet: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testBasicACLPredefined(t, BasicACLPublicRO, BasicACLNamePublicRO, exp)
|
||||
exp.extendable = true
|
||||
testBasicACLPredefined(t, BasicACLPublicROExtended, BasicACLNamePublicROExtended, exp)
|
||||
})
|
||||
|
||||
t.Run("public-read-write", func(t *testing.T) {
|
||||
exp := expected{
|
||||
extendable: false,
|
||||
sticky: false,
|
||||
mOps: map[ACLOp]opsExpected{
|
||||
ACLOpObjectHash: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectRange: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectSearch: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectDelete: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectPut: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: false,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectHead: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectGet: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testBasicACLPredefined(t, BasicACLPublicRW, BasicACLNamePublicRW, exp)
|
||||
exp.extendable = true
|
||||
testBasicACLPredefined(t, BasicACLPublicRWExtended, BasicACLNamePublicRWExtended, exp)
|
||||
})
|
||||
|
||||
t.Run("public-append", func(t *testing.T) {
|
||||
exp := expected{
|
||||
extendable: false,
|
||||
sticky: false,
|
||||
mOps: map[ACLOp]opsExpected{
|
||||
ACLOpObjectHash: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectRange: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectSearch: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectDelete: {
|
||||
owner: true,
|
||||
container: false,
|
||||
innerRing: false,
|
||||
others: false,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectPut: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: false,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectHead: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
ACLOpObjectGet: {
|
||||
owner: true,
|
||||
container: true,
|
||||
innerRing: true,
|
||||
others: true,
|
||||
bearer: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testBasicACLPredefined(t, BasicACLPublicAppend, BasicACLNamePublicAppend, exp)
|
||||
exp.extendable = true
|
||||
testBasicACLPredefined(t, BasicACLPublicAppendExtended, BasicACLNamePublicAppendExtended, exp)
|
||||
})
|
||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/acl"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
|
@ -23,7 +22,7 @@ type Container struct {
|
|||
// Defaults:
|
||||
// - token: nil;
|
||||
// - sig: nil;
|
||||
// - basicACL: acl.PrivateBasicRule;
|
||||
// - basicACL: BasicACLPrivate;
|
||||
// - version: version.Current;
|
||||
// - nonce: random UUID;
|
||||
// - attr: nil;
|
||||
|
@ -136,12 +135,13 @@ func (c *Container) SetNonceUUID(v uuid.UUID) {
|
|||
c.v2.SetNonce(data)
|
||||
}
|
||||
|
||||
func (c *Container) BasicACL() uint32 {
|
||||
return c.v2.GetBasicACL()
|
||||
func (c *Container) BasicACL() (res BasicACL) {
|
||||
res.fromUint32(c.v2.GetBasicACL())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) SetBasicACL(v acl.BasicACL) {
|
||||
c.v2.SetBasicACL(uint32(v))
|
||||
func (c *Container) SetBasicACL(v BasicACL) {
|
||||
c.v2.SetBasicACL(v.toUint32())
|
||||
}
|
||||
|
||||
func (c *Container) Attributes() Attributes {
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
containertest "github.com/nspcc-dev/neofs-sdk-go/container/test"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
|
@ -23,7 +22,7 @@ func TestNewContainer(t *testing.T) {
|
|||
ownerID := usertest.ID()
|
||||
policy := netmaptest.PlacementPolicy()
|
||||
|
||||
c.SetBasicACL(acl.PublicBasicRule)
|
||||
c.SetBasicACL(container.BasicACLPublicRW)
|
||||
|
||||
attrs := containertest.Attributes()
|
||||
c.SetAttributes(attrs)
|
||||
|
@ -40,7 +39,7 @@ func TestNewContainer(t *testing.T) {
|
|||
|
||||
require.EqualValues(t, newContainer.PlacementPolicy(), &policy)
|
||||
require.EqualValues(t, newContainer.Attributes(), attrs)
|
||||
require.EqualValues(t, newContainer.BasicACL(), acl.PublicBasicRule)
|
||||
require.EqualValues(t, newContainer.BasicACL(), container.BasicACLPublicRW)
|
||||
|
||||
newNonce, err := newContainer.NonceUUID()
|
||||
require.NoError(t, err)
|
||||
|
@ -89,7 +88,7 @@ func TestContainer_ToV2(t *testing.T) {
|
|||
require.Nil(t, cnt.PlacementPolicy())
|
||||
require.Nil(t, cnt.OwnerID())
|
||||
|
||||
require.EqualValues(t, acl.PrivateBasicRule, cnt.BasicACL())
|
||||
require.EqualValues(t, container.BasicACLPrivate, cnt.BasicACL())
|
||||
require.Equal(t, version.Current(), *cnt.Version())
|
||||
|
||||
nonce, err := cnt.NonceUUID()
|
||||
|
@ -108,7 +107,7 @@ func TestContainer_ToV2(t *testing.T) {
|
|||
require.Nil(t, cntV2.GetPlacementPolicy())
|
||||
require.Nil(t, cntV2.GetOwnerID())
|
||||
|
||||
require.Equal(t, uint32(acl.PrivateBasicRule), cntV2.GetBasicACL())
|
||||
require.EqualValues(t, 0x1C8C8CCC, cntV2.GetBasicACL())
|
||||
|
||||
var verV2 refs.Version
|
||||
version.Current().WriteToV2(&verV2)
|
||||
|
|
30
container/init.go
Normal file
30
container/init.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package container
|
||||
|
||||
func init() {
|
||||
// left-to-right order of the object operations
|
||||
orderedOps := [...]ACLOp{
|
||||
ACLOpObjectGet,
|
||||
ACLOpObjectHead,
|
||||
ACLOpObjectPut,
|
||||
ACLOpObjectDelete,
|
||||
ACLOpObjectSearch,
|
||||
ACLOpObjectRange,
|
||||
ACLOpObjectHash,
|
||||
}
|
||||
|
||||
mOrder = make(map[ACLOp]uint8, len(orderedOps))
|
||||
|
||||
for i := range orderedOps {
|
||||
mOrder[orderedOps[i]] = uint8(i)
|
||||
}
|
||||
|
||||
// numbers are taken from NeoFS Specification
|
||||
BasicACLPrivate.fromUint32(0x1C8C8CCC)
|
||||
BasicACLPrivateExtended.fromUint32(0x0C8C8CCC)
|
||||
BasicACLPublicRO.fromUint32(0x1FBF8CFF)
|
||||
BasicACLPublicROExtended.fromUint32(0x0FBF8CFF)
|
||||
BasicACLPublicRW.fromUint32(0x1FBFBFFF)
|
||||
BasicACLPublicRWExtended.fromUint32(0x0FBFBFFF)
|
||||
BasicACLPublicAppend.fromUint32(0x1FBF9FFF)
|
||||
BasicACLPublicAppendExtended.fromUint32(0x0FBF9FFF)
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"crypto/ecdsa"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
@ -13,7 +12,7 @@ type (
|
|||
Option func(*containerOptions)
|
||||
|
||||
containerOptions struct {
|
||||
acl acl.BasicACL
|
||||
acl BasicACL
|
||||
policy *netmap.PlacementPolicy
|
||||
attributes Attributes
|
||||
owner *user.ID
|
||||
|
@ -28,24 +27,24 @@ func defaultContainerOptions() containerOptions {
|
|||
}
|
||||
|
||||
return containerOptions{
|
||||
acl: acl.PrivateBasicRule,
|
||||
acl: BasicACLPrivate,
|
||||
nonce: rand,
|
||||
}
|
||||
}
|
||||
|
||||
func WithPublicBasicACL() Option {
|
||||
return func(option *containerOptions) {
|
||||
option.acl = acl.PublicBasicRule
|
||||
option.acl = BasicACLPublicRW
|
||||
}
|
||||
}
|
||||
|
||||
func WithReadOnlyBasicACL() Option {
|
||||
return func(option *containerOptions) {
|
||||
option.acl = acl.ReadOnlyBasicRule
|
||||
option.acl = BasicACLPublicRO
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomBasicACL(acl acl.BasicACL) Option {
|
||||
func WithCustomBasicACL(acl BasicACL) Option {
|
||||
return func(option *containerOptions) {
|
||||
option.acl = acl
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ func Container() *container.Container {
|
|||
x.SetVersion(&ver)
|
||||
x.SetAttributes(Attributes())
|
||||
x.SetOwnerID(usertest.ID())
|
||||
x.SetBasicACL(123)
|
||||
x.SetBasicACL(container.BasicACLPublicRW)
|
||||
p := netmaptest.PlacementPolicy()
|
||||
x.SetPlacementPolicy(&p)
|
||||
|
||||
|
|
45
container/util.go
Normal file
45
container/util.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package container
|
||||
|
||||
import "fmt"
|
||||
|
||||
// sets n-th bit in num (starting at 0).
|
||||
func setBit(num *uint32, n uint8) {
|
||||
*num |= 1 << n
|
||||
}
|
||||
|
||||
// resets n-th bit in num (starting at 0).
|
||||
func resetBit(num *uint32, n uint8) {
|
||||
var mask uint32
|
||||
setBit(&mask, n)
|
||||
|
||||
*num &= ^mask
|
||||
}
|
||||
|
||||
// checks if n-th bit in num is set (starting at 0).
|
||||
func isBitSet(num uint32, n uint8) bool {
|
||||
mask := uint32(1 << n)
|
||||
return mask != 0 && num&mask == mask
|
||||
}
|
||||
|
||||
// maps ACLOp to op-section index in BasicACL. Filled on init.
|
||||
var mOrder map[ACLOp]uint8
|
||||
|
||||
// sets n-th bit in num for the given op. Panics if op is unsupported.
|
||||
func setOpBit(num *uint32, op ACLOp, opBitPos uint8) {
|
||||
n, ok := mOrder[op]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("op is unsupported %v", op))
|
||||
}
|
||||
|
||||
setBit(num, n*bitsPerOp+opBitPos)
|
||||
}
|
||||
|
||||
// checks if n-th bit in num for the given op is set. Panics if op is unsupported.
|
||||
func isOpBitSet(num uint32, op ACLOp, n uint8) bool {
|
||||
off, ok := mOrder[op]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("op is unsupported %v", op))
|
||||
}
|
||||
|
||||
return isBitSet(num, bitsPerOp*off+n)
|
||||
}
|
133
container/util_test.go
Normal file
133
container/util_test.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBits(t *testing.T) {
|
||||
num := uint32(0b10110)
|
||||
|
||||
require.False(t, isBitSet(num, 0))
|
||||
require.True(t, isBitSet(num, 1))
|
||||
require.True(t, isBitSet(num, 2))
|
||||
require.False(t, isBitSet(num, 3))
|
||||
require.True(t, isBitSet(num, 4))
|
||||
require.False(t, isBitSet(num, 5))
|
||||
|
||||
setBit(&num, 3)
|
||||
require.EqualValues(t, 0b11110, num)
|
||||
|
||||
setBit(&num, 6)
|
||||
require.EqualValues(t, 0b1011110, num)
|
||||
|
||||
resetBit(&num, 1)
|
||||
require.EqualValues(t, 0b1011100, num)
|
||||
}
|
||||
|
||||
func TestOpBits(t *testing.T) {
|
||||
num := uint32(0b_1001_0101_1100_0011_0110_0111_1000_1111)
|
||||
|
||||
require.Panics(t, func() { isOpBitSet(num, aclOpZero, 0) })
|
||||
require.Panics(t, func() { isOpBitSet(num, aclOpLast, 0) })
|
||||
|
||||
cpNum := num
|
||||
|
||||
require.Panics(t, func() { setOpBit(&num, aclOpZero, 0) })
|
||||
require.EqualValues(t, cpNum, num)
|
||||
require.Panics(t, func() { setOpBit(&num, aclOpLast, 0) })
|
||||
require.EqualValues(t, cpNum, num)
|
||||
|
||||
for _, tc := range []struct {
|
||||
op ACLOp
|
||||
set [4]bool // is bit set (left-to-right)
|
||||
bits [4]uint32 // result of setting i-th bit (left-to-right) to zero num
|
||||
}{
|
||||
{
|
||||
op: ACLOpObjectHash,
|
||||
set: [4]bool{false, true, false, true},
|
||||
bits: [4]uint32{
|
||||
0b_0000_1000_0000_0000_0000_0000_0000_0000,
|
||||
0b_0000_0100_0000_0000_0000_0000_0000_0000,
|
||||
0b_0000_0010_0000_0000_0000_0000_0000_0000,
|
||||
0b_0000_0001_0000_0000_0000_0000_0000_0000,
|
||||
},
|
||||
},
|
||||
{
|
||||
op: ACLOpObjectRange,
|
||||
set: [4]bool{true, true, false, false},
|
||||
bits: [4]uint32{
|
||||
0b_0000_0000_1000_0000_0000_0000_0000_0000,
|
||||
0b_0000_0000_0100_0000_0000_0000_0000_0000,
|
||||
0b_0000_0000_0010_0000_0000_0000_0000_0000,
|
||||
0b_0000_0000_0001_0000_0000_0000_0000_0000,
|
||||
},
|
||||
},
|
||||
{
|
||||
op: ACLOpObjectSearch,
|
||||
set: [4]bool{false, false, true, true},
|
||||
bits: [4]uint32{
|
||||
0b_0000_0000_0000_1000_0000_0000_0000_0000,
|
||||
0b_0000_0000_0000_0100_0000_0000_0000_0000,
|
||||
0b_0000_0000_0000_0010_0000_0000_0000_0000,
|
||||
0b_0000_0000_0000_0001_0000_0000_0000_0000,
|
||||
},
|
||||
},
|
||||
{
|
||||
op: ACLOpObjectDelete,
|
||||
set: [4]bool{false, true, true, false},
|
||||
bits: [4]uint32{
|
||||
0b_0000_0000_0000_0000_1000_0000_0000_0000,
|
||||
0b_0000_0000_0000_0000_0100_0000_0000_0000,
|
||||
0b_0000_0000_0000_0000_0010_0000_0000_0000,
|
||||
0b_0000_0000_0000_0000_0001_0000_0000_0000,
|
||||
},
|
||||
},
|
||||
{
|
||||
op: ACLOpObjectPut,
|
||||
set: [4]bool{false, true, true, true},
|
||||
bits: [4]uint32{
|
||||
0b_0000_0000_0000_0000_0000_1000_0000_0000,
|
||||
0b_0000_0000_0000_0000_0000_0100_0000_0000,
|
||||
0b_0000_0000_0000_0000_0000_0010_0000_0000,
|
||||
0b_0000_0000_0000_0000_0000_0001_0000_0000,
|
||||
},
|
||||
},
|
||||
{
|
||||
op: ACLOpObjectHead,
|
||||
set: [4]bool{true, false, false, false},
|
||||
bits: [4]uint32{
|
||||
0b_0000_0000_0000_0000_0000_0000_1000_0000,
|
||||
0b_0000_0000_0000_0000_0000_0000_0100_0000,
|
||||
0b_0000_0000_0000_0000_0000_0000_0010_0000,
|
||||
0b_0000_0000_0000_0000_0000_0000_0001_0000,
|
||||
},
|
||||
},
|
||||
{
|
||||
op: ACLOpObjectGet,
|
||||
set: [4]bool{true, true, true, true},
|
||||
bits: [4]uint32{
|
||||
0b_0000_0000_0000_0000_0000_0000_0000_1000,
|
||||
0b_0000_0000_0000_0000_0000_0000_0000_0100,
|
||||
0b_0000_0000_0000_0000_0000_0000_0000_0010,
|
||||
0b_0000_0000_0000_0000_0000_0000_0000_0001,
|
||||
},
|
||||
},
|
||||
} {
|
||||
for i := range tc.set {
|
||||
require.EqualValues(t, tc.set[i], isOpBitSet(num, tc.op, uint8(len(tc.set)-1-i)),
|
||||
fmt.Sprintf("op %s, left bit #%d", tc.op, i),
|
||||
)
|
||||
}
|
||||
|
||||
for i := range tc.bits {
|
||||
num := uint32(0)
|
||||
|
||||
setOpBit(&num, tc.op, uint8(len(tc.bits)-1-i))
|
||||
|
||||
require.EqualValues(t, tc.bits[i], num)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue