mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
Go 1.19 includes some changes to gofmt which intend to make lists and heading clearer when rendered (https://go.dev/doc/go1.19). This commit is the result of running the new gofmt and manually fixing some of it. This was necessary because the new gofmt assumed lines beginning w/ tabs to start lists or examples. While this is often true in our codebase, we occasionally also use tabs to indent the lines after a TODO or FIXME prefix or in yaml (e.g., excerpts of a snap.yaml). This meant that a lot of the reformatted comments were broken and had to be fixed manually. Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
287 lines
8.3 KiB
Go
287 lines
8.3 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2020 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package internal
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// Groupings maintain labels to identify membership to one or more groups.
|
|
// Labels are implemented as subsets of integers from 0
|
|
// up to an excluded maximum, where the integers represent the groups.
|
|
// Assumptions:
|
|
// - most labels are for one group or very few
|
|
// - a few labels are sparse with more groups in them
|
|
// - very few comprise the universe of all groups
|
|
type Groupings struct {
|
|
n uint
|
|
maxGroup uint16
|
|
bitsetThreshold uint16
|
|
}
|
|
|
|
// NewGroupings creates a new Groupings supporting labels for membership
|
|
// to up n groups. n must be a positive multiple of 16 and <=65536.
|
|
func NewGroupings(n int) (*Groupings, error) {
|
|
if n <= 0 || n > 65536 {
|
|
return nil, fmt.Errorf("n=%d groups is outside of valid range (0, 65536]", n)
|
|
}
|
|
if n%16 != 0 {
|
|
return nil, fmt.Errorf("n=%d groups is not a multiple of 16", n)
|
|
}
|
|
return &Groupings{n: uint(n), bitsetThreshold: uint16(n / 16)}, nil
|
|
}
|
|
|
|
// N returns up to how many groups are supported.
|
|
// That is the value that was passed to NewGroupings.
|
|
func (gr *Groupings) N() int {
|
|
return int(gr.n)
|
|
}
|
|
|
|
// WithinRange checks whether group is within the admissible range for
|
|
// labeling otherwise it returns an error.
|
|
func (gr *Groupings) WithinRange(group uint16) error {
|
|
if uint(group) >= gr.n {
|
|
return fmt.Errorf("group exceeds admissible maximum: %d >= %d", group, gr.n)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Grouping struct {
|
|
size uint16
|
|
elems []uint16
|
|
}
|
|
|
|
func (g Grouping) Copy() Grouping {
|
|
elems2 := make([]uint16, len(g.elems), cap(g.elems))
|
|
copy(elems2[:], g.elems[:])
|
|
g.elems = elems2
|
|
return g
|
|
}
|
|
|
|
// search locates group among the sorted Grouping elements, it returns:
|
|
// - true if found
|
|
// - false if not found
|
|
// - the index at which group should be inserted to keep the
|
|
// elements sorted if not found and the bit-set representation is not in use
|
|
func (gr *Groupings) search(g *Grouping, group uint16) (found bool, j uint16) {
|
|
if g.size > gr.bitsetThreshold {
|
|
return bitsetContains(g, group), 0
|
|
}
|
|
j = uint16(sort.Search(int(g.size), func(i int) bool { return g.elems[i] >= group }))
|
|
if j < g.size && g.elems[j] == group {
|
|
return true, 0
|
|
}
|
|
return false, j
|
|
}
|
|
|
|
func bitsetContains(g *Grouping, group uint16) bool {
|
|
return (g.elems[group/16] & (1 << (group % 16))) != 0
|
|
}
|
|
|
|
// AddTo adds the given group to the grouping.
|
|
func (gr *Groupings) AddTo(g *Grouping, group uint16) error {
|
|
if err := gr.WithinRange(group); err != nil {
|
|
return err
|
|
}
|
|
if group > gr.maxGroup {
|
|
gr.maxGroup = group
|
|
}
|
|
if g.size == 0 {
|
|
g.size = 1
|
|
g.elems = []uint16{group}
|
|
return nil
|
|
}
|
|
found, j := gr.search(g, group)
|
|
if found {
|
|
return nil
|
|
}
|
|
newsize := g.size + 1
|
|
if newsize > gr.bitsetThreshold {
|
|
// switching to a bit-set representation after the size point
|
|
// where the space cost is the same, the representation uses
|
|
// bitsetThreshold-many 16-bits words stored in elems.
|
|
// We don't always use the bit-set representation because
|
|
// * we expect small groupings and iteration to be common,
|
|
// iteration is more costly over the bit-set representation
|
|
// * serialization matches more or less what we do in memory,
|
|
// so again is more efficient for small groupings in the
|
|
// extensive representation.
|
|
if g.size == gr.bitsetThreshold {
|
|
prevelems := g.elems
|
|
g.elems = make([]uint16, gr.bitsetThreshold)
|
|
for _, e := range prevelems {
|
|
bitsetAdd(g, e)
|
|
}
|
|
}
|
|
g.size = newsize
|
|
bitsetAdd(g, group)
|
|
return nil
|
|
}
|
|
var newelems []uint16
|
|
if int(g.size) == cap(g.elems) {
|
|
newelems = make([]uint16, newsize, cap(g.elems)*2)
|
|
copy(newelems, g.elems[:j])
|
|
} else {
|
|
newelems = g.elems[:newsize]
|
|
}
|
|
if j < g.size {
|
|
copy(newelems[j+1:], g.elems[j:])
|
|
}
|
|
// inserting new group at j index keeping the elements sorted
|
|
newelems[j] = group
|
|
g.size = newsize
|
|
g.elems = newelems
|
|
return nil
|
|
}
|
|
|
|
func bitsetAdd(g *Grouping, group uint16) {
|
|
g.elems[group/16] |= 1 << (group % 16)
|
|
}
|
|
|
|
// Contains returns whether the given group is a member of the grouping.
|
|
func (gr *Groupings) Contains(g *Grouping, group uint16) bool {
|
|
found, _ := gr.search(g, group)
|
|
return found
|
|
}
|
|
|
|
// Serialize produces a string encoding the given integers.
|
|
func Serialize(elems []uint16) string {
|
|
b := bytes.NewBuffer(make([]byte, 0, len(elems)*2))
|
|
binary.Write(b, binary.LittleEndian, elems)
|
|
return base64.RawURLEncoding.EncodeToString(b.Bytes())
|
|
}
|
|
|
|
// Serialize produces a string representing the grouping label.
|
|
func (gr *Groupings) Serialize(g *Grouping) string {
|
|
// groupings are serialized as:
|
|
// * the actual element groups if there are up to
|
|
// bitsetThreshold elements: elems[0], elems[1], ...
|
|
// * otherwise the number of elements, followed by the bitset
|
|
// representation comprised of bitsetThreshold-many 16-bits words
|
|
// (stored using elems as well)
|
|
if g.size > gr.bitsetThreshold {
|
|
return gr.bitsetSerialize(g)
|
|
}
|
|
return Serialize(g.elems)
|
|
}
|
|
|
|
func (gr *Groupings) bitsetSerialize(g *Grouping) string {
|
|
b := bytes.NewBuffer(make([]byte, 0, (gr.bitsetThreshold+1)*2))
|
|
binary.Write(b, binary.LittleEndian, g.size)
|
|
binary.Write(b, binary.LittleEndian, g.elems)
|
|
return base64.RawURLEncoding.EncodeToString(b.Bytes())
|
|
}
|
|
|
|
const errSerializedLabelFmt = "invalid serialized grouping label: %v"
|
|
|
|
// Deserialize reconstructs a grouping out of the serialized label.
|
|
func (gr *Groupings) Deserialize(label string) (*Grouping, error) {
|
|
b, err := base64.RawURLEncoding.DecodeString(label)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, err)
|
|
}
|
|
if len(b)%2 != 0 {
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, "not divisible into 16-bits words")
|
|
}
|
|
m := len(b) / 2
|
|
var g Grouping
|
|
if m == int(gr.bitsetThreshold+1) {
|
|
// deserialize number of elements + bitset representation
|
|
// comprising bitsetThreshold-many 16-bits words
|
|
return gr.bitsetDeserialize(&g, b)
|
|
}
|
|
if m > int(gr.bitsetThreshold) {
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, "too large")
|
|
}
|
|
g.size = uint16(m)
|
|
esz := uint16(1)
|
|
for esz < g.size {
|
|
esz *= 2
|
|
}
|
|
g.elems = make([]uint16, g.size, esz)
|
|
binary.Read(bytes.NewBuffer(b), binary.LittleEndian, g.elems)
|
|
for i, e := range g.elems {
|
|
if e > gr.maxGroup {
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, "element larger than maximum group")
|
|
}
|
|
if i > 0 && g.elems[i-1] >= e {
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, "not sorted")
|
|
}
|
|
}
|
|
return &g, nil
|
|
}
|
|
|
|
func (gr *Groupings) bitsetDeserialize(g *Grouping, b []byte) (*Grouping, error) {
|
|
buf := bytes.NewBuffer(b)
|
|
binary.Read(buf, binary.LittleEndian, &g.size)
|
|
if g.size > gr.maxGroup+1 {
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, "bitset size cannot be possibly larger than maximum group plus 1")
|
|
}
|
|
if g.size <= gr.bitsetThreshold {
|
|
// should not have used a bitset repr for so few elements
|
|
return nil, fmt.Errorf(errSerializedLabelFmt, "bitset for too few elements")
|
|
}
|
|
g.elems = make([]uint16, gr.bitsetThreshold)
|
|
binary.Read(buf, binary.LittleEndian, g.elems)
|
|
return g, nil
|
|
}
|
|
|
|
// Iter iterates over the groups in the grouping and calls f with each of
|
|
// them. If f returns an error Iter immediately returns with it.
|
|
func (gr *Groupings) Iter(g *Grouping, f func(group uint16) error) error {
|
|
if g.size > gr.bitsetThreshold {
|
|
return gr.bitsetIter(g, f)
|
|
}
|
|
for _, e := range g.elems {
|
|
if err := f(e); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (gr *Groupings) bitsetIter(g *Grouping, f func(group uint16) error) error {
|
|
c := g.size
|
|
for i := uint16(0); i <= gr.maxGroup/16; i++ {
|
|
w := g.elems[i]
|
|
if w == 0 {
|
|
continue
|
|
}
|
|
for j := uint16(0); w != 0; j++ {
|
|
if w&1 != 0 {
|
|
if err := f(i*16 + j); err != nil {
|
|
return err
|
|
}
|
|
c--
|
|
if c == 0 {
|
|
// found all elements
|
|
return nil
|
|
}
|
|
}
|
|
w >>= 1
|
|
}
|
|
}
|
|
return nil
|
|
}
|