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  }