github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/asserts/internal/grouping.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package internal 21 22 import ( 23 "bytes" 24 "encoding/base64" 25 "encoding/binary" 26 "fmt" 27 "sort" 28 ) 29 30 // Groupings maintain labels to identify membership to one or more groups. 31 // Labels are implemented as subsets of integers from 0 32 // up to an excluded maximum, where the integers represent the groups. 33 // Assumptions: 34 // - most labels are for one group or very few 35 // - a few labels are sparse with more groups in them 36 // - very few comprise the universe of all groups 37 type Groupings struct { 38 n uint 39 maxGroup uint16 40 bitsetThreshold uint16 41 } 42 43 // NewGroupings creates a new Groupings supporting labels for membership 44 // to up n groups. n must be a positive multiple of 16 and <=65536. 45 func NewGroupings(n int) (*Groupings, error) { 46 if n <= 0 || n > 65536 { 47 return nil, fmt.Errorf("n=%d groups is outside of valid range (0, 65536]", n) 48 } 49 if n%16 != 0 { 50 return nil, fmt.Errorf("n=%d groups is not a multiple of 16", n) 51 } 52 return &Groupings{n: uint(n), bitsetThreshold: uint16(n / 16)}, nil 53 } 54 55 // N returns up to how many groups are supported. 56 // That is the value that was passed to NewGroupings. 57 func (gr *Groupings) N() int { 58 return int(gr.n) 59 } 60 61 // WithinRange checks whether group is within the admissible range for 62 // labeling otherwise it returns an error. 63 func (gr *Groupings) WithinRange(group uint16) error { 64 if uint(group) >= gr.n { 65 return fmt.Errorf("group exceeds admissible maximum: %d >= %d", group, gr.n) 66 } 67 return nil 68 } 69 70 type Grouping struct { 71 size uint16 72 elems []uint16 73 } 74 75 func (g Grouping) Copy() Grouping { 76 elems2 := make([]uint16, len(g.elems), cap(g.elems)) 77 copy(elems2[:], g.elems[:]) 78 g.elems = elems2 79 return g 80 } 81 82 // search locates group among the sorted Grouping elements, it returns: 83 // * true if found 84 // * false if not found 85 // * the index at which group should be inserted to keep the 86 // elements sorted if not found and the bit-set representation is not in use 87 func (gr *Groupings) search(g *Grouping, group uint16) (found bool, j uint16) { 88 if g.size > gr.bitsetThreshold { 89 return bitsetContains(g, group), 0 90 } 91 j = uint16(sort.Search(int(g.size), func(i int) bool { return g.elems[i] >= group })) 92 if j < g.size && g.elems[j] == group { 93 return true, 0 94 } 95 return false, j 96 } 97 98 func bitsetContains(g *Grouping, group uint16) bool { 99 return (g.elems[group/16] & (1 << (group % 16))) != 0 100 } 101 102 // AddTo adds the given group to the grouping. 103 func (gr *Groupings) AddTo(g *Grouping, group uint16) error { 104 if err := gr.WithinRange(group); err != nil { 105 return err 106 } 107 if group > gr.maxGroup { 108 gr.maxGroup = group 109 } 110 if g.size == 0 { 111 g.size = 1 112 g.elems = []uint16{group} 113 return nil 114 } 115 found, j := gr.search(g, group) 116 if found { 117 return nil 118 } 119 newsize := g.size + 1 120 if newsize > gr.bitsetThreshold { 121 // switching to a bit-set representation after the size point 122 // where the space cost is the same, the representation uses 123 // bitsetThreshold-many 16-bits words stored in elems. 124 // We don't always use the bit-set representation because 125 // * we expect small groupings and iteration to be common, 126 // iteration is more costly over the bit-set representation 127 // * serialization matches more or less what we do in memory, 128 // so again is more efficient for small groupings in the 129 // extensive representation. 130 if g.size == gr.bitsetThreshold { 131 prevelems := g.elems 132 g.elems = make([]uint16, gr.bitsetThreshold) 133 for _, e := range prevelems { 134 bitsetAdd(g, e) 135 } 136 } 137 g.size = newsize 138 bitsetAdd(g, group) 139 return nil 140 } 141 var newelems []uint16 142 if int(g.size) == cap(g.elems) { 143 newelems = make([]uint16, newsize, cap(g.elems)*2) 144 copy(newelems, g.elems[:j]) 145 } else { 146 newelems = g.elems[:newsize] 147 } 148 if j < g.size { 149 copy(newelems[j+1:], g.elems[j:]) 150 } 151 // inserting new group at j index keeping the elements sorted 152 newelems[j] = group 153 g.size = newsize 154 g.elems = newelems 155 return nil 156 } 157 158 func bitsetAdd(g *Grouping, group uint16) { 159 g.elems[group/16] |= 1 << (group % 16) 160 } 161 162 // Contains returns whether the given group is a member of the grouping. 163 func (gr *Groupings) Contains(g *Grouping, group uint16) bool { 164 found, _ := gr.search(g, group) 165 return found 166 } 167 168 // Serialize produces a string encoding the given integers. 169 func Serialize(elems []uint16) string { 170 b := bytes.NewBuffer(make([]byte, 0, len(elems)*2)) 171 binary.Write(b, binary.LittleEndian, elems) 172 return base64.RawURLEncoding.EncodeToString(b.Bytes()) 173 } 174 175 // Serialize produces a string representing the grouping label. 176 func (gr *Groupings) Serialize(g *Grouping) string { 177 // groupings are serialized as: 178 // * the actual element groups if there are up to 179 // bitsetThreshold elements: elems[0], elems[1], ... 180 // * otherwise the number of elements, followed by the bitset 181 // representation comprised of bitsetThreshold-many 16-bits words 182 // (stored using elems as well) 183 if g.size > gr.bitsetThreshold { 184 return gr.bitsetSerialize(g) 185 } 186 return Serialize(g.elems) 187 } 188 189 func (gr *Groupings) bitsetSerialize(g *Grouping) string { 190 b := bytes.NewBuffer(make([]byte, 0, (gr.bitsetThreshold+1)*2)) 191 binary.Write(b, binary.LittleEndian, g.size) 192 binary.Write(b, binary.LittleEndian, g.elems) 193 return base64.RawURLEncoding.EncodeToString(b.Bytes()) 194 } 195 196 const errSerializedLabelFmt = "invalid serialized grouping label: %v" 197 198 // Deserialize reconstructs a grouping out of the serialized label. 199 func (gr *Groupings) Deserialize(label string) (*Grouping, error) { 200 b, err := base64.RawURLEncoding.DecodeString(label) 201 if err != nil { 202 return nil, fmt.Errorf(errSerializedLabelFmt, err) 203 } 204 if len(b)%2 != 0 { 205 return nil, fmt.Errorf(errSerializedLabelFmt, "not divisible into 16-bits words") 206 } 207 m := len(b) / 2 208 var g Grouping 209 if m == int(gr.bitsetThreshold+1) { 210 // deserialize number of elements + bitset representation 211 // comprising bitsetThreshold-many 16-bits words 212 return gr.bitsetDeserialize(&g, b) 213 } 214 if m > int(gr.bitsetThreshold) { 215 return nil, fmt.Errorf(errSerializedLabelFmt, "too large") 216 } 217 g.size = uint16(m) 218 esz := uint16(1) 219 for esz < g.size { 220 esz *= 2 221 } 222 g.elems = make([]uint16, g.size, esz) 223 binary.Read(bytes.NewBuffer(b), binary.LittleEndian, g.elems) 224 for i, e := range g.elems { 225 if e > gr.maxGroup { 226 return nil, fmt.Errorf(errSerializedLabelFmt, "element larger than maximum group") 227 } 228 if i > 0 && g.elems[i-1] >= e { 229 return nil, fmt.Errorf(errSerializedLabelFmt, "not sorted") 230 } 231 } 232 return &g, nil 233 } 234 235 func (gr *Groupings) bitsetDeserialize(g *Grouping, b []byte) (*Grouping, error) { 236 buf := bytes.NewBuffer(b) 237 binary.Read(buf, binary.LittleEndian, &g.size) 238 if g.size > gr.maxGroup+1 { 239 return nil, fmt.Errorf(errSerializedLabelFmt, "bitset size cannot be possibly larger than maximum group plus 1") 240 } 241 if g.size <= gr.bitsetThreshold { 242 // should not have used a bitset repr for so few elements 243 return nil, fmt.Errorf(errSerializedLabelFmt, "bitset for too few elements") 244 } 245 g.elems = make([]uint16, gr.bitsetThreshold) 246 binary.Read(buf, binary.LittleEndian, g.elems) 247 return g, nil 248 } 249 250 // Iter iterates over the groups in the grouping and calls f with each of 251 // them. If f returns an error Iter immediately returns with it. 252 func (gr *Groupings) Iter(g *Grouping, f func(group uint16) error) error { 253 if g.size > gr.bitsetThreshold { 254 return gr.bitsetIter(g, f) 255 } 256 for _, e := range g.elems { 257 if err := f(e); err != nil { 258 return err 259 } 260 } 261 return nil 262 } 263 264 func (gr *Groupings) bitsetIter(g *Grouping, f func(group uint16) error) error { 265 c := g.size 266 for i := uint16(0); i <= gr.maxGroup/16; i++ { 267 w := g.elems[i] 268 if w == 0 { 269 continue 270 } 271 for j := uint16(0); w != 0; j++ { 272 if w&1 != 0 { 273 if err := f(i*16 + j); err != nil { 274 return err 275 } 276 c-- 277 if c == 0 { 278 // found all elements 279 return nil 280 } 281 } 282 w >>= 1 283 } 284 } 285 return nil 286 }