go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/common/hash.go (about) 1 // Copyright 2017 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package common 16 17 import ( 18 "encoding/hex" 19 "fmt" 20 "hash" 21 "reflect" 22 23 api "go.chromium.org/luci/cipd/api/cipd/v1" 24 ) 25 26 // DefaultHashAlgo is a hash algorithm to use for deriving IDs of new package 27 // instances. Older existing instances are allowed to use some other hash algo. 28 const DefaultHashAlgo = api.HashAlgo_SHA256 29 30 // Supported algo => its digest length (in hex encoding) + factory function. 31 // 32 // Actual entries are added in individual hash_*.go files, so that supported 33 // hashes can be turned off by build flags or by omitting files when vendoring. 34 var supportedAlgos = make([]struct { 35 hash func() hash.Hash 36 typ reflect.Type 37 hexDigestLen int 38 }, len(api.HashAlgo_value)) 39 40 // registerHash is used from hash_*.go files to update supportedAlgos. 41 func registerHashAlgo(algo api.HashAlgo, h func() hash.Hash) { 42 if supportedAlgos[algo].hash != nil { 43 panic(fmt.Sprintf("hash algo %s is already registered", algo)) 44 } 45 inst := h() 46 supportedAlgos[algo].hash = h 47 supportedAlgos[algo].typ = reflect.TypeOf(inst) 48 supportedAlgos[algo].hexDigestLen = 2 * len(inst.Sum(nil)) 49 } 50 51 // NewHash returns a hash implementation or an error if the algo is unknown. 52 func NewHash(algo api.HashAlgo) (hash.Hash, error) { 53 if err := ValidateHashAlgo(algo); err != nil { 54 return nil, err 55 } 56 return supportedAlgos[algo].hash(), nil 57 } 58 59 // MustNewHash as like NewHash, but panics on errors. 60 // 61 // Appropriate for cases when the hash algo has already been validated. 62 func MustNewHash(algo api.HashAlgo) hash.Hash { 63 h, err := NewHash(algo) 64 if err != nil { 65 panic(err) 66 } 67 return h 68 } 69 70 // ValidateHashAlgo returns a grpc-annotated error if the given algo is invalid, 71 // e.g. either unspecified or not known to the current version of the code. 72 // 73 // Errors have InvalidArgument grpc code. 74 func ValidateHashAlgo(h api.HashAlgo) error { 75 switch { 76 case h == api.HashAlgo_HASH_ALGO_UNSPECIFIED: 77 return validationErr("the hash algorithm is not specified or unrecognized") 78 case int(h) >= len(supportedAlgos) || supportedAlgos[h].hash == nil: 79 return validationErr("unsupported hash algorithm %d", h) 80 } 81 return nil 82 } 83 84 // HexDigest returns a digest string as it is used in ObjectRef. 85 func HexDigest(h hash.Hash) string { 86 return hex.EncodeToString(h.Sum(nil)) 87 } 88 89 // ObjectRefFromHash returns ObjectRef based on the given hash.Hash. 90 // 91 // `h` must be one of the supported hashes (as created by `NewHash`). Panics 92 // otherwise. 93 func ObjectRefFromHash(h hash.Hash) *api.ObjectRef { 94 typ := reflect.TypeOf(h) 95 for algo, props := range supportedAlgos { 96 if props.typ == typ { 97 return &api.ObjectRef{ 98 HashAlgo: api.HashAlgo(algo), 99 HexDigest: HexDigest(h), 100 } 101 } 102 } 103 panic(fmt.Sprintf("unknown hash algo %T", h)) 104 }