opts.go 3.16 KB
Newer Older
Jeromy's avatar
Jeromy committed
1
2
3
4
5
6
7
8
9
10
11
12
13
// Package opts helps to write commands which may take multihash
// options.
package opts

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"strings"

Jeromy's avatar
Jeromy committed
14
	mh "QmdsKjp5fcCT8PZ8JBMcdFsCbbmKwSLCU5xXbsnwb5DMxy/go-multihash"
Jeromy's avatar
Jeromy committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
)

// package errors
var (
	ErrMatch = errors.New("multihash checksums did not match")
)

// Options is a struct used to parse cli flags.
type Options struct {
	Encoding      string
	Algorithm     string
	AlgorithmCode int
	Length        int

	fs *flag.FlagSet
}

// FlagValues are the values the various option flags can take.
var FlagValues = struct {
	Encodings  []string
	Algorithms []string
}{
	Encodings:  []string{"raw", "hex", "base58", "base64"},
	Algorithms: []string{"sha1", "sha2-256", "sha2-512", "sha3"},
}

// SetupFlags adds multihash related options to given flagset.
func SetupFlags(f *flag.FlagSet) *Options {
	// TODO: add arg for adding opt prefix and/or overriding opts

	o := new(Options)
	algoStr := "one of: " + strings.Join(FlagValues.Algorithms, ", ")
	f.StringVar(&o.Algorithm, "algorithm", "sha2-256", algoStr)
	f.StringVar(&o.Algorithm, "a", "sha2-256", algoStr+" (shorthand)")

	encStr := "one of: " + strings.Join(FlagValues.Encodings, ", ")
	f.StringVar(&o.Encoding, "encoding", "base58", encStr)
	f.StringVar(&o.Encoding, "e", "base58", encStr+" (shorthand)")

	lengthStr := "checksums length in bits (truncate). -1 is default"
	f.IntVar(&o.Length, "length", -1, lengthStr)
	f.IntVar(&o.Length, "l", -1, lengthStr+" (shorthand)")
	return o
}

// Parse parses the values of flags from given argument slice.
// It is equivalent to flags.Parse(args)
func (o *Options) Parse(args []string) error {
	if err := o.fs.Parse(args); err != nil {
		return err
	}
	return o.ParseError()
}

// ParseError checks the parsed options for errors.
func (o *Options) ParseError() error {
	if !strIn(o.Encoding, FlagValues.Encodings) {
		return fmt.Errorf("encoding '%s' not %s", o.Encoding, FlagValues.Encodings)
	}

	if !strIn(o.Algorithm, FlagValues.Algorithms) {
		return fmt.Errorf("algorithm '%s' not %s", o.Algorithm, FlagValues.Algorithms)
	}

	var found bool
	o.AlgorithmCode, found = mh.Names[o.Algorithm]
	if !found {
		return fmt.Errorf("algorithm '%s' not found (lib error, pls report).", o.Algorithm)
	}

	if o.Length >= 0 {
		if o.Length%8 != 0 {
			return fmt.Errorf("length must be multiple of 8")
		}
		o.Length = o.Length / 8

		if o.Length > mh.DefaultLengths[o.AlgorithmCode] {
			o.Length = mh.DefaultLengths[o.AlgorithmCode]
		}
	}
	return nil
}

// strIn checks wither string a is in set.
func strIn(a string, set []string) bool {
	for _, s := range set {
		if s == a {
			return true
		}
	}
	return false
}

// Check reads all the data in r, calculates its multihash,
// and checks it matches h1
func (o *Options) Check(r io.Reader, h1 mh.Multihash) error {
	h2, err := o.Multihash(r)
	if err != nil {
		return err
	}

	if !bytes.Equal(h1, h2) {
		return fmt.Errorf("computed checksum did not match")
	}

	return nil
}

// Multihash reads all the data in r and calculates its multihash.
func (o *Options) Multihash(r io.Reader) (mh.Multihash, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}

	return mh.Sum(b, o.AlgorithmCode, o.Length)
}