set.go 6.5 KB

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