// Package opts helps to write commands which may take multihash // options. package opts import ( "bytes" "errors" "flag" "fmt" "io" "io/ioutil" "sort" "strings" mh "github.com/multiformats/go-multihash" ) // 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 uint64 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: func() []string { names := make([]string, 0, len(mh.Names)) for n := range mh.Names { // There are too many of these for now. // We can figure something better out later. if strings.HasPrefix(n, "blake2") { switch n { case "blake2s-256": case "blake2b-128": case "blake2b-224": case "blake2b-256": case "blake2b-384": case "blake2b-512": default: continue } } names = append(names, n) } sort.Strings(names) return names }(), } // 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) }