github.com/npaton/distribution@v2.3.1-rc.0+incompatible/digest/set.go (about) 1 package digest 2 3 import ( 4 "errors" 5 "sort" 6 "strings" 7 "sync" 8 ) 9 10 var ( 11 // ErrDigestNotFound is used when a matching digest 12 // could not be found in a set. 13 ErrDigestNotFound = errors.New("digest not found") 14 15 // ErrDigestAmbiguous is used when multiple digests 16 // are found in a set. None of the matching digests 17 // should be considered valid matches. 18 ErrDigestAmbiguous = errors.New("ambiguous digest string") 19 ) 20 21 // Set is used to hold a unique set of digests which 22 // may be easily referenced by easily referenced by a string 23 // representation of the digest as well as short representation. 24 // The uniqueness of the short representation is based on other 25 // digests in the set. If digests are ommited from this set, 26 // collisions in a larger set may not be detected, therefore it 27 // is important to always do short representation lookups on 28 // the complete set of digests. To mitigate collisions, an 29 // appropriately long short code should be used. 30 type Set struct { 31 mutex sync.RWMutex 32 entries digestEntries 33 } 34 35 // NewSet creates an empty set of digests 36 // which may have digests added. 37 func NewSet() *Set { 38 return &Set{ 39 entries: digestEntries{}, 40 } 41 } 42 43 // checkShortMatch checks whether two digests match as either whole 44 // values or short values. This function does not test equality, 45 // rather whether the second value could match against the first 46 // value. 47 func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool { 48 if len(hex) == len(shortHex) { 49 if hex != shortHex { 50 return false 51 } 52 if len(shortAlg) > 0 && string(alg) != shortAlg { 53 return false 54 } 55 } else if !strings.HasPrefix(hex, shortHex) { 56 return false 57 } else if len(shortAlg) > 0 && string(alg) != shortAlg { 58 return false 59 } 60 return true 61 } 62 63 // Lookup looks for a digest matching the given string representation. 64 // If no digests could be found ErrDigestNotFound will be returned 65 // with an empty digest value. If multiple matches are found 66 // ErrDigestAmbiguous will be returned with an empty digest value. 67 func (dst *Set) Lookup(d string) (Digest, error) { 68 dst.mutex.RLock() 69 defer dst.mutex.RUnlock() 70 if len(dst.entries) == 0 { 71 return "", ErrDigestNotFound 72 } 73 var ( 74 searchFunc func(int) bool 75 alg Algorithm 76 hex string 77 ) 78 dgst, err := ParseDigest(d) 79 if err == ErrDigestInvalidFormat { 80 hex = d 81 searchFunc = func(i int) bool { 82 return dst.entries[i].val >= d 83 } 84 } else { 85 hex = dgst.Hex() 86 alg = dgst.Algorithm() 87 searchFunc = func(i int) bool { 88 if dst.entries[i].val == hex { 89 return dst.entries[i].alg >= alg 90 } 91 return dst.entries[i].val >= hex 92 } 93 } 94 idx := sort.Search(len(dst.entries), searchFunc) 95 if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) { 96 return "", ErrDigestNotFound 97 } 98 if dst.entries[idx].alg == alg && dst.entries[idx].val == hex { 99 return dst.entries[idx].digest, nil 100 } 101 if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) { 102 return "", ErrDigestAmbiguous 103 } 104 105 return dst.entries[idx].digest, nil 106 } 107 108 // Add adds the given digest to the set. An error will be returned 109 // if the given digest is invalid. If the digest already exists in the 110 // set, this operation will be a no-op. 111 func (dst *Set) Add(d Digest) error { 112 if err := d.Validate(); err != nil { 113 return err 114 } 115 dst.mutex.Lock() 116 defer dst.mutex.Unlock() 117 entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d} 118 searchFunc := func(i int) bool { 119 if dst.entries[i].val == entry.val { 120 return dst.entries[i].alg >= entry.alg 121 } 122 return dst.entries[i].val >= entry.val 123 } 124 idx := sort.Search(len(dst.entries), searchFunc) 125 if idx == len(dst.entries) { 126 dst.entries = append(dst.entries, entry) 127 return nil 128 } else if dst.entries[idx].digest == d { 129 return nil 130 } 131 132 entries := append(dst.entries, nil) 133 copy(entries[idx+1:], entries[idx:len(entries)-1]) 134 entries[idx] = entry 135 dst.entries = entries 136 return nil 137 } 138 139 // Remove removes the given digest from the set. An err will be 140 // returned if the given digest is invalid. If the digest does 141 // not exist in the set, this operation will be a no-op. 142 func (dst *Set) Remove(d Digest) error { 143 if err := d.Validate(); err != nil { 144 return err 145 } 146 dst.mutex.Lock() 147 defer dst.mutex.Unlock() 148 entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d} 149 searchFunc := func(i int) bool { 150 if dst.entries[i].val == entry.val { 151 return dst.entries[i].alg >= entry.alg 152 } 153 return dst.entries[i].val >= entry.val 154 } 155 idx := sort.Search(len(dst.entries), searchFunc) 156 // Not found if idx is after or value at idx is not digest 157 if idx == len(dst.entries) || dst.entries[idx].digest != d { 158 return nil 159 } 160 161 entries := dst.entries 162 copy(entries[idx:], entries[idx+1:]) 163 entries = entries[:len(entries)-1] 164 dst.entries = entries 165 166 return nil 167 } 168 169 // All returns all the digests in the set 170 func (dst *Set) All() []Digest { 171 dst.mutex.RLock() 172 defer dst.mutex.RUnlock() 173 retValues := make([]Digest, len(dst.entries)) 174 for i := range dst.entries { 175 retValues[i] = dst.entries[i].digest 176 } 177 178 return retValues 179 } 180 181 // ShortCodeTable returns a map of Digest to unique short codes. The 182 // length represents the minimum value, the maximum length may be the 183 // entire value of digest if uniqueness cannot be achieved without the 184 // full value. This function will attempt to make short codes as short 185 // as possible to be unique. 186 func ShortCodeTable(dst *Set, length int) map[Digest]string { 187 dst.mutex.RLock() 188 defer dst.mutex.RUnlock() 189 m := make(map[Digest]string, len(dst.entries)) 190 l := length 191 resetIdx := 0 192 for i := 0; i < len(dst.entries); i++ { 193 var short string 194 extended := true 195 for extended { 196 extended = false 197 if len(dst.entries[i].val) <= l { 198 short = dst.entries[i].digest.String() 199 } else { 200 short = dst.entries[i].val[:l] 201 for j := i + 1; j < len(dst.entries); j++ { 202 if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) { 203 if j > resetIdx { 204 resetIdx = j 205 } 206 extended = true 207 } else { 208 break 209 } 210 } 211 if extended { 212 l++ 213 } 214 } 215 } 216 m[dst.entries[i].digest] = short 217 if i >= resetIdx { 218 l = length 219 } 220 } 221 return m 222 } 223 224 type digestEntry struct { 225 alg Algorithm 226 val string 227 digest Digest 228 } 229 230 type digestEntries []*digestEntry 231 232 func (d digestEntries) Len() int { 233 return len(d) 234 } 235 236 func (d digestEntries) Less(i, j int) bool { 237 if d[i].val != d[j].val { 238 return d[i].val < d[j].val 239 } 240 return d[i].alg < d[j].alg 241 } 242 243 func (d digestEntries) Swap(i, j int) { 244 d[i], d[j] = d[j], d[i] 245 }