csmnamer/hash.go (51 lines of code) (raw):
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// DO NOT EDIT: This is a sync of services_platform/thetis/common/gke_net/naming.go
// and should not be modified to maintain functional consistency.
package csmnamer
import (
"crypto/sha256"
"strconv"
)
// lookup table to maintain entropy when converting bytes to string.
var table []string
func init() {
for i := 0; i < 10; i++ {
table = append(table, strconv.Itoa(i))
}
for i := 0; i < 26; i++ {
table = append(table, string('a'+rune(i)))
}
}
// Hash creates a content hash string of length n of s utilizing sha256.
// Note that 256 is not evenly divisible by 36, so the first four elements
// will be slightly more likely (3.125% chance) than the rest (2.734375% chance).
// This results in a per-character chance of collision of
// (4 * ((8/256)^2) + (36-4) * ((7/256)^2)) instead of (1 / 36).
// For an 8 character hash string (used for cluster UID and suffix hash), this
// comes out to 3.600e-13 instead of 3.545e-13, which is a negligibly larger
// chance of collision.
func Hash(s string, n int) string {
var h string
bytes := sha256.Sum256(([]byte)(s))
for i := 0; i < n && i < len(bytes); i++ {
idx := int(bytes[i]) % len(table)
h += table[idx]
}
return h
}
// TrimFieldsEvenly trims the fields evenly and keeps the total length <= max.
// Truncation is spread in ratio with their original length, meaning smaller
// fields will be truncated less than longer ones.
func TrimFieldsEvenly(max int, fields ...string) []string {
if max <= 0 {
return fields
}
total := 0
for _, s := range fields {
total += len(s)
}
if total <= max {
return fields
}
// Distribute truncation evenly among the fields.
excess := total - max
remaining := max
var lengths []int
for _, s := range fields {
// Scale truncation to shorten longer fields more than ones that are already
// short.
l := len(s) - len(s)*excess/total - 1
lengths = append(lengths, l)
remaining -= l
}
// Add fractional space that was rounded down.
for i := 0; i < remaining; i++ {
lengths[i]++
}
var ret []string
for i, l := range lengths {
ret = append(ret, fields[i][:l])
}
return ret
}