1/*
2Copyright 2013 The Perkeep Authors
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// Package blob defines types to refer to and retrieve low-level Perkeep blobs.
18package blob // import "perkeep.org/pkg/blob"
19
20import (
21	"bytes"
22	"crypto/sha1"
23	"crypto/sha256"
24	"errors"
25	"fmt"
26	"hash"
27	"io"
28	"reflect"
29	"strings"
30
31	"perkeep.org/internal/testhooks"
32)
33
34// Pattern is the regular expression which matches a blobref.
35// It does not contain ^ or $.
36const Pattern = `\b([a-z][a-z0-9]*)-([a-f0-9]+)\b`
37
38// Ref is a reference to a Perkeep blob.
39// It is used as a value type and supports equality (with ==) and the ability
40// to use it as a map key.
41type Ref struct {
42	digest digestType
43}
44
45// SizedRef is like a Ref but includes a size.
46// It should also be used as a value type and supports equality.
47type SizedRef struct {
48	Ref  Ref    `json:"blobRef"`
49	Size uint32 `json:"size"`
50}
51
52// Less reports whether sr sorts before o. Invalid references blobs sort first.
53func (sr SizedRef) Less(o SizedRef) bool {
54	return sr.Ref.Less(o.Ref)
55}
56
57func (sr SizedRef) Valid() bool { return sr.Ref.Valid() }
58
59func (sr SizedRef) HashMatches(h hash.Hash) bool { return sr.Ref.HashMatches(h) }
60
61func (sr SizedRef) String() string {
62	return fmt.Sprintf("[%s; %d bytes]", sr.Ref.String(), sr.Size)
63}
64
65// digestType is an interface type, but any type implementing it must
66// be of concrete type [N]byte, so it supports equality with ==,
67// which is a requirement for ref.
68type digestType interface {
69	bytes() []byte
70	digestName() string
71	newHash() hash.Hash
72	equalString(string) bool
73	hasPrefix(string) bool
74}
75
76func (r Ref) String() string {
77	if r.digest == nil {
78		return "<invalid-blob.Ref>"
79	}
80	dname := r.digest.digestName()
81	bs := r.digest.bytes()
82	buf := getBuf(len(dname) + 1 + len(bs)*2)[:0]
83	defer putBuf(buf)
84	return string(r.appendString(buf))
85}
86
87// StringMinusOne returns the first string that's before String.
88func (r Ref) StringMinusOne() string {
89	if r.digest == nil {
90		return "<invalid-blob.Ref>"
91	}
92	dname := r.digest.digestName()
93	bs := r.digest.bytes()
94	buf := getBuf(len(dname) + 1 + len(bs)*2)[:0]
95	defer putBuf(buf)
96	buf = r.appendString(buf)
97	buf[len(buf)-1]-- // no need to deal with carrying underflow (no 0 bytes ever)
98	return string(buf)
99}
100
101// EqualString reports whether r.String() is equal to s.
102// It does not allocate.
103func (r Ref) EqualString(s string) bool { return r.digest.equalString(s) }
104
105// HasPrefix reports whether s is a prefix of r.String(). It returns false if s
106// does not contain at least the digest name prefix (e.g. "sha224-") and one byte of
107// digest.
108// It does not allocate.
109func (r Ref) HasPrefix(s string) bool { return r.digest.hasPrefix(s) }
110
111func (r Ref) appendString(buf []byte) []byte {
112	dname := r.digest.digestName()
113	bs := r.digest.bytes()
114	buf = append(buf, dname...)
115	buf = append(buf, '-')
116	for _, b := range bs {
117		buf = append(buf, hexDigit[b>>4], hexDigit[b&0xf])
118	}
119	if o, ok := r.digest.(otherDigest); ok && o.odd {
120		buf = buf[:len(buf)-1]
121	}
122	return buf
123}
124
125// HashName returns the lowercase hash function name of the reference.
126// It panics if r is zero.
127func (r Ref) HashName() string {
128	if r.digest == nil {
129		panic("HashName called on invalid Ref")
130	}
131	return r.digest.digestName()
132}
133
134// Digest returns the lower hex digest of the blobref, without
135// the e.g. "sha224-" prefix. It panics if r is zero.
136func (r Ref) Digest() string {
137	if r.digest == nil {
138		panic("Digest called on invalid Ref")
139	}
140	bs := r.digest.bytes()
141	buf := getBuf(len(bs) * 2)[:0]
142	defer putBuf(buf)
143	for _, b := range bs {
144		buf = append(buf, hexDigit[b>>4], hexDigit[b&0xf])
145	}
146	if o, ok := r.digest.(otherDigest); ok && o.odd {
147		buf = buf[:len(buf)-1]
148	}
149	return string(buf)
150}
151
152func (r Ref) DigestPrefix(digits int) string {
153	v := r.Digest()
154	if len(v) < digits {
155		return v
156	}
157	return v[:digits]
158}
159
160func (r Ref) DomID() string {
161	if !r.Valid() {
162		return ""
163	}
164	return "camli-" + r.String()
165}
166
167func (r Ref) Sum32() uint32 {
168	var v uint32
169	for _, b := range r.digest.bytes()[:4] {
170		v = v<<8 | uint32(b)
171	}
172	return v
173}
174
175func (r Ref) Sum64() uint64 {
176	var v uint64
177	for _, b := range r.digest.bytes()[:8] {
178		v = v<<8 | uint64(b)
179	}
180	return v
181}
182
183// Hash returns a new hash.Hash of r's type.
184// It panics if r is zero.
185func (r Ref) Hash() hash.Hash {
186	return r.digest.newHash()
187}
188
189func (r Ref) HashMatches(h hash.Hash) bool {
190	if r.digest == nil {
191		return false
192	}
193	return bytes.Equal(h.Sum(nil), r.digest.bytes())
194}
195
196const hexDigit = "0123456789abcdef"
197
198func (r Ref) Valid() bool { return r.digest != nil }
199
200func (r Ref) IsSupported() bool {
201	if !r.Valid() {
202		return false
203	}
204	_, ok := metaFromString[r.digest.digestName()]
205	return ok
206}
207
208// ParseKnown is like Parse, but only parse blobrefs known to this
209// server. It returns ok == false for well-formed but unsupported
210// blobrefs.
211func ParseKnown(s string) (ref Ref, ok bool) {
212	return parse(s, false)
213}
214
215// Parse parse s as a blobref and returns the ref and whether it was
216// parsed successfully.
217func Parse(s string) (ref Ref, ok bool) {
218	return parse(s, true)
219}
220
221func parse(s string, allowAll bool) (ref Ref, ok bool) {
222	i := strings.Index(s, "-")
223	if i < 0 {
224		return
225	}
226	name := s[:i] // e.g. "sha1", "sha224"
227	hex := s[i+1:]
228	meta, ok := metaFromString[name]
229	if !ok {
230		if allowAll || testRefType[name] {
231			return parseUnknown(name, hex)
232		}
233		return
234	}
235	if len(hex) != meta.size*2 {
236		ok = false
237		return
238	}
239	dt, ok := meta.ctors(hex)
240	if !ok {
241		return
242	}
243	return Ref{dt}, true
244}
245
246var testRefType = map[string]bool{
247	"fakeref": true,
248	"testref": true,
249	"perma":   true,
250}
251
252// ParseBytes is like Parse, but parses from a byte slice.
253func ParseBytes(s []byte) (ref Ref, ok bool) {
254	i := bytes.IndexByte(s, '-')
255	if i < 0 {
256		return
257	}
258	name := s[:i] // e.g. "sha1", "sha224"
259	hex := s[i+1:]
260	meta, ok := metaFromBytes(name)
261	if !ok {
262		return parseUnknown(string(name), string(hex))
263	}
264	if len(hex) != meta.size*2 {
265		ok = false
266		return
267	}
268	dt, ok := meta.ctorb(hex)
269	if !ok {
270		return
271	}
272	return Ref{dt}, true
273}
274
275// ParseOrZero parses as a blobref. If s is invalid, a zero Ref is
276// returned which can be tested with the Valid method.
277func ParseOrZero(s string) Ref {
278	ref, ok := Parse(s)
279	if !ok {
280		return Ref{}
281	}
282	return ref
283}
284
285// MustParse parse s as a blobref and panics on failure.
286func MustParse(s string) Ref {
287	ref, ok := Parse(s)
288	if !ok {
289		panic("Invalid blobref " + s)
290	}
291	return ref
292}
293
294// '0' => 0 ... 'f' => 15, else sets *bad to true.
295func hexVal(b byte, bad *bool) byte {
296	if '0' <= b && b <= '9' {
297		return b - '0'
298	}
299	if 'a' <= b && b <= 'f' {
300		return b - 'a' + 10
301	}
302	*bad = true
303	return 0
304}
305
306func validDigestName(name string) bool {
307	if name == "" {
308		return false
309	}
310	for _, r := range name {
311		if 'a' <= r && r <= 'z' {
312			continue
313		}
314		if '0' <= r && r <= '9' {
315			continue
316		}
317		return false
318	}
319	return true
320}
321
322// parseUnknown parses a blobref where the digest type isn't known to this server.
323// e.g. ("foo-ababab")
324func parseUnknown(digest, hex string) (ref Ref, ok bool) {
325	if !validDigestName(digest) {
326		return
327	}
328
329	// TODO: remove this short hack and don't allow odd numbers of hex digits.
330	odd := false
331	if len(hex)%2 != 0 {
332		hex += "0"
333		odd = true
334	}
335
336	if len(hex) < 2 || len(hex)%2 != 0 || len(hex) > maxOtherDigestLen*2 {
337		return
338	}
339	o := otherDigest{
340		name:   digest,
341		sumLen: len(hex) / 2,
342		odd:    odd,
343	}
344	bad := false
345	for i := 0; i < len(hex); i += 2 {
346		o.sum[i/2] = hexVal(hex[i], &bad)<<4 | hexVal(hex[i+1], &bad)
347	}
348	if bad {
349		return
350	}
351	return Ref{o}, true
352}
353
354func sha1FromBinary(b []byte) digestType {
355	var d sha1Digest
356	if len(d) != len(b) {
357		panic("bogus sha-1 length")
358	}
359	copy(d[:], b)
360	return d
361}
362
363func sha1FromHexString(hex string) (digestType, bool) {
364	var d sha1Digest
365	var bad bool
366	for i := 0; i < len(hex); i += 2 {
367		d[i/2] = hexVal(hex[i], &bad)<<4 | hexVal(hex[i+1], &bad)
368	}
369	if bad {
370		return nil, false
371	}
372	return d, true
373}
374
375// yawn. exact copy of sha1FromHexString.
376func sha1FromHexBytes(hex []byte) (digestType, bool) {
377	var d sha1Digest
378	var bad bool
379	for i := 0; i < len(hex); i += 2 {
380		d[i/2] = hexVal(hex[i], &bad)<<4 | hexVal(hex[i+1], &bad)
381	}
382	if bad {
383		return nil, false
384	}
385	return d, true
386}
387
388func sha224FromBinary(b []byte) digestType {
389	var d sha224Digest
390	if len(d) != len(b) {
391		panic("bogus sha-224 length")
392	}
393	copy(d[:], b)
394	return d
395}
396
397func sha224FromHexString(hex string) (digestType, bool) {
398	var d sha224Digest
399	var bad bool
400	for i := 0; i < len(hex); i += 2 {
401		d[i/2] = hexVal(hex[i], &bad)<<4 | hexVal(hex[i+1], &bad)
402	}
403	if bad {
404		return nil, false
405	}
406	return d, true
407}
408
409// yawn. exact copy of sha224FromHexString.
410func sha224FromHexBytes(hex []byte) (digestType, bool) {
411	var d sha224Digest
412	var bad bool
413	for i := 0; i < len(hex); i += 2 {
414		d[i/2] = hexVal(hex[i], &bad)<<4 | hexVal(hex[i+1], &bad)
415	}
416	if bad {
417		return nil, false
418	}
419	return d, true
420}
421
422// RefFromHash returns a blobref representing the given hash.
423// It panics if the hash isn't of a known type.
424func RefFromHash(h hash.Hash) Ref {
425	meta, ok := metaFromType[hashSig{reflect.TypeOf(h), h.Size()}]
426	if !ok {
427		panic(fmt.Sprintf("Currently-unsupported hash type %T", h))
428	}
429	return Ref{meta.ctor(h.Sum(nil))}
430}
431
432// RefFromString returns a blobref from the given string, for the currently
433// recommended hash function.
434func RefFromString(s string) Ref {
435	h := NewHash()
436	io.WriteString(h, s)
437	return RefFromHash(h)
438}
439
440// RefFromBytes returns a blobref from the given string, for the currently
441// recommended hash function.
442func RefFromBytes(b []byte) Ref {
443	h := NewHash()
444	h.Write(b)
445	return RefFromHash(h)
446}
447
448type sha1Digest [20]byte
449
450func (d sha1Digest) digestName() string { return "sha1" }
451func (d sha1Digest) bytes() []byte      { return d[:] }
452func (d sha1Digest) newHash() hash.Hash { return sha1.New() }
453func (d sha1Digest) equalString(s string) bool {
454	if len(s) != 45 {
455		return false
456	}
457	if !strings.HasPrefix(s, "sha1-") {
458		return false
459	}
460	s = s[len("sha1-"):]
461	for i, b := range d[:] {
462		if s[i*2] != hexDigit[b>>4] || s[i*2+1] != hexDigit[b&0xf] {
463			return false
464		}
465	}
466	return true
467}
468
469func (d sha1Digest) hasPrefix(s string) bool {
470	if len(s) > 45 {
471		return false
472	}
473	if len(s) == 45 {
474		return d.equalString(s)
475	}
476	if !strings.HasPrefix(s, "sha1-") {
477		return false
478	}
479	s = s[len("sha1-"):]
480	if len(s) == 0 {
481		// we want at least one digest char to match on
482		return false
483	}
484	for i, b := range d[:] {
485		even := i * 2
486		if even == len(s) {
487			break
488		}
489		if s[even] != hexDigit[b>>4] {
490			return false
491		}
492		odd := i*2 + 1
493		if odd == len(s) {
494			break
495		}
496		if s[odd] != hexDigit[b&0xf] {
497			return false
498		}
499	}
500	return true
501}
502
503type sha224Digest [28]byte
504
505const sha224StrLen = 63 // len("sha224-d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f")
506
507func (d sha224Digest) digestName() string { return "sha224" }
508func (d sha224Digest) bytes() []byte      { return d[:] }
509func (d sha224Digest) newHash() hash.Hash { return sha256.New224() }
510func (d sha224Digest) equalString(s string) bool {
511	if len(s) != sha224StrLen {
512		return false
513	}
514	if !strings.HasPrefix(s, "sha224-") {
515		return false
516	}
517	s = s[len("sha224-"):]
518	for i, b := range d[:] {
519		if s[i*2] != hexDigit[b>>4] || s[i*2+1] != hexDigit[b&0xf] {
520			return false
521		}
522	}
523	return true
524}
525
526func (d sha224Digest) hasPrefix(s string) bool {
527	if len(s) > sha224StrLen {
528		return false
529	}
530	if len(s) == sha224StrLen {
531		return d.equalString(s)
532	}
533	if !strings.HasPrefix(s, "sha224-") {
534		return false
535	}
536	s = s[len("sha224-"):]
537	if len(s) == 0 {
538		// we want at least one digest char to match on
539		return false
540	}
541	for i, b := range d[:] {
542		even := i * 2
543		if even == len(s) {
544			break
545		}
546		if s[even] != hexDigit[b>>4] {
547			return false
548		}
549		odd := i*2 + 1
550		if odd == len(s) {
551			break
552		}
553		if s[odd] != hexDigit[b&0xf] {
554			return false
555		}
556	}
557	return true
558}
559
560const maxOtherDigestLen = 128
561
562type otherDigest struct {
563	name   string
564	sum    [maxOtherDigestLen]byte
565	sumLen int  // bytes in sum that are valid
566	odd    bool // odd number of hex digits in input
567}
568
569func (d otherDigest) digestName() string { return d.name }
570func (d otherDigest) bytes() []byte      { return d.sum[:d.sumLen] }
571func (d otherDigest) newHash() hash.Hash { return nil }
572func (d otherDigest) equalString(s string) bool {
573	wantLen := len(d.name) + len("-") + 2*d.sumLen
574	if d.odd {
575		wantLen--
576	}
577	if len(s) != wantLen || !strings.HasPrefix(s, d.name) || s[len(d.name)] != '-' {
578		return false
579	}
580	s = s[len(d.name)+1:]
581	for i, b := range d.sum[:d.sumLen] {
582		if s[i*2] != hexDigit[b>>4] {
583			return false
584		}
585		if i == d.sumLen-1 && d.odd {
586			break
587		}
588		if s[i*2+1] != hexDigit[b&0xf] {
589			return false
590		}
591	}
592	return true
593}
594
595func (d otherDigest) hasPrefix(s string) bool {
596	maxLen := len(d.name) + len("-") + 2*d.sumLen
597	if d.odd {
598		maxLen--
599	}
600	if len(s) > maxLen || !strings.HasPrefix(s, d.name) || s[len(d.name)] != '-' {
601		return false
602	}
603	if len(s) == maxLen {
604		return d.equalString(s)
605	}
606	s = s[len(d.name)+1:]
607	if len(s) == 0 {
608		// we want at least one digest char to match on
609		return false
610	}
611	for i, b := range d.sum[:d.sumLen] {
612		even := i * 2
613		if even == len(s) {
614			break
615		}
616		if s[even] != hexDigit[b>>4] {
617			return false
618		}
619		odd := i*2 + 1
620		if odd == len(s) {
621			break
622		}
623		if i == d.sumLen-1 && d.odd {
624			break
625		}
626		if s[odd] != hexDigit[b&0xf] {
627			return false
628		}
629	}
630	return true
631}
632
633var (
634	sha1Meta = &digestMeta{
635		ctor:  sha1FromBinary,
636		ctors: sha1FromHexString,
637		ctorb: sha1FromHexBytes,
638		size:  sha1.Size,
639	}
640	sha224Meta = &digestMeta{
641		ctor:  sha224FromBinary,
642		ctors: sha224FromHexString,
643		ctorb: sha224FromHexBytes,
644		size:  sha256.Size224,
645	}
646)
647
648var metaFromString = map[string]*digestMeta{
649	"sha1":   sha1Meta,
650	"sha224": sha224Meta,
651}
652
653type blobTypeAndMeta struct {
654	name []byte
655	meta *digestMeta
656}
657
658var metas []blobTypeAndMeta
659
660func metaFromBytes(name []byte) (meta *digestMeta, ok bool) {
661	for _, bm := range metas {
662		if bytes.Equal(name, bm.name) {
663			return bm.meta, true
664		}
665	}
666	return
667}
668
669func init() {
670	for name, meta := range metaFromString {
671		metas = append(metas, blobTypeAndMeta{
672			name: []byte(name),
673			meta: meta,
674		})
675	}
676}
677
678// HashFuncs returns the names of the supported hash functions.
679func HashFuncs() []string {
680	hashes := make([]string, len(metas))
681	for i, m := range metas {
682		hashes[i] = string(m.name)
683	}
684	return hashes
685}
686
687var (
688	sha1Type   = reflect.TypeOf(sha1.New())
689	sha224Type = reflect.TypeOf(sha256.New224())
690)
691
692// hashSig is the tuple (reflect.Type, hash size), for use as a map key.
693// The size disambiguates SHA-256 vs SHA-224, both of which have the same
694// reflect.Type (crypto/sha256.digest, but one has is224 bool set true).
695type hashSig struct {
696	rt   reflect.Type
697	size int
698}
699
700var metaFromType = map[hashSig]*digestMeta{
701	{sha1Type, sha1.Size}:        sha1Meta,
702	{sha224Type, sha256.Size224}: sha224Meta,
703}
704
705type digestMeta struct {
706	ctor  func(binary []byte) digestType
707	ctors func(hex string) (digestType, bool)
708	ctorb func(hex []byte) (digestType, bool)
709	size  int // bytes of digest
710}
711
712var bufPool = make(chan []byte, 20)
713
714func getBuf(size int) []byte {
715	for {
716		select {
717		case b := <-bufPool:
718			if cap(b) >= size {
719				return b[:size]
720			}
721		default:
722			return make([]byte, size)
723		}
724	}
725}
726
727func putBuf(b []byte) {
728	select {
729	case bufPool <- b:
730	default:
731	}
732}
733
734// NewHash returns a new hash.Hash of the currently recommended hash type.
735// Currently this is SHA-224, but is subject to change over time.
736func NewHash() hash.Hash {
737	if testhooks.UseSHA1() {
738		return sha1.New()
739	}
740	return sha256.New224()
741}
742
743func ValidRefString(s string) bool {
744	// TODO: optimize to not allocate
745	return ParseOrZero(s).Valid()
746}
747
748var null = []byte(`null`)
749
750func (r *Ref) UnmarshalJSON(d []byte) error {
751	if r.digest != nil {
752		return errors.New("Can't UnmarshalJSON into a non-zero Ref")
753	}
754	if len(d) == 0 || bytes.Equal(d, null) {
755		return nil
756	}
757	if len(d) < 2 || d[0] != '"' || d[len(d)-1] != '"' {
758		return fmt.Errorf("blob: expecting a JSON string to unmarshal, got %q", d)
759	}
760	d = d[1 : len(d)-1]
761	p, ok := ParseBytes(d)
762	if !ok {
763		return fmt.Errorf("blobref: invalid blobref %q (%d)", d, len(d))
764	}
765	*r = p
766	return nil
767}
768
769func (r Ref) MarshalJSON() ([]byte, error) {
770	if !r.Valid() {
771		return null, nil
772	}
773	dname := r.digest.digestName()
774	bs := r.digest.bytes()
775	buf := make([]byte, 0, 3+len(dname)+len(bs)*2)
776	buf = append(buf, '"')
777	buf = r.appendString(buf)
778	buf = append(buf, '"')
779	return buf, nil
780}
781
782// MarshalBinary implements Go's encoding.BinaryMarshaler interface.
783func (r Ref) MarshalBinary() (data []byte, err error) {
784	dname := r.digest.digestName()
785	bs := r.digest.bytes()
786	data = make([]byte, 0, len(dname)+1+len(bs))
787	data = append(data, dname...)
788	data = append(data, '-')
789	data = append(data, bs...)
790	return
791}
792
793// UnmarshalBinary implements Go's encoding.BinaryUnmarshaler interface.
794func (r *Ref) UnmarshalBinary(data []byte) error {
795	if r.digest != nil {
796		return errors.New("Can't UnmarshalBinary into a non-zero Ref")
797	}
798	i := bytes.IndexByte(data, '-')
799	if i < 1 {
800		return errors.New("no digest name")
801	}
802
803	digName := string(data[:i])
804	buf := data[i+1:]
805
806	meta, ok := metaFromString[digName]
807	if !ok {
808		r2, ok := parseUnknown(digName, fmt.Sprintf("%x", buf))
809		if !ok {
810			return errors.New("invalid blobref binary data")
811		}
812		*r = r2
813		return nil
814	}
815	if len(buf) != meta.size {
816		return errors.New("wrong size of data for digest " + digName)
817	}
818	r.digest = meta.ctor(buf)
819	return nil
820}
821
822// Less reports whether r sorts before o. Invalid references blobs sort first.
823func (r Ref) Less(o Ref) bool {
824	if r.Valid() != o.Valid() {
825		return o.Valid()
826	}
827	if !r.Valid() {
828		return false
829	}
830	if n1, n2 := r.digest.digestName(), o.digest.digestName(); n1 != n2 {
831		return n1 < n2
832	}
833	return bytes.Compare(r.digest.bytes(), o.digest.bytes()) < 0
834}
835
836// ByRef sorts blob references.
837type ByRef []Ref
838
839func (s ByRef) Len() int           { return len(s) }
840func (s ByRef) Less(i, j int) bool { return s[i].Less(s[j]) }
841func (s ByRef) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
842
843// SizedByRef sorts SizedRefs by their blobref.
844type SizedByRef []SizedRef
845
846func (s SizedByRef) Len() int           { return len(s) }
847func (s SizedByRef) Less(i, j int) bool { return s[i].Less(s[j]) }
848func (s SizedByRef) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
849
850// TypeAlphabet returns the valid characters in the given blobref type.
851// It returns the empty string if the typ is unknown.
852func TypeAlphabet(typ string) string {
853	switch typ {
854	case "sha1":
855		return hexDigit
856	case "sha224":
857		return hexDigit
858	}
859	return ""
860}
861