blob: c090e2bf8eee5b76c68b364319b6cbd63bd833ee [file] [edit]
// Copyright 2020-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bufplugin
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"github.com/bufbuild/buf/private/bufpkg/bufparse"
"github.com/bufbuild/buf/private/pkg/cas"
"github.com/bufbuild/buf/private/pkg/syserror"
)
const (
// DigestTypeP1 represents the p1 plugin digest type.
//
// The string value of this is "p1".
DigestTypeP1 DigestType = iota + 1
)
var (
// AllDigestTypes are all known DigestTypes.
AllDigestTypes = []DigestType{
DigestTypeP1,
}
digestTypeToString = map[DigestType]string{
DigestTypeP1: "p1",
}
stringToDigestType = map[string]DigestType{
"p1": DigestTypeP1,
}
)
// DigestType is a type of digest.
type DigestType int
// ParseDigestType parses a DigestType from its string representation.
//
// This reverses DigestType.String().
//
// Returns an error of type *bufparse.ParseError if the string could not be parsed.
func ParseDigestType(s string) (DigestType, error) {
d, ok := stringToDigestType[s]
if !ok {
return 0, bufparse.NewParseError(
"plugin digest type",
s,
fmt.Errorf("unknown type: %q", s),
)
}
return d, nil
}
// String prints the string representation of the DigestType.
func (d DigestType) String() string {
s, ok := digestTypeToString[d]
if !ok {
return strconv.Itoa(int(d))
}
return s
}
// Digest is a digest of some content.
//
// It consists of a DigestType and a digest value.
type Digest interface {
// String() prints typeString:hexValue.
fmt.Stringer
// Type returns the type of digest.
//
// Always a valid value.
Type() DigestType
// Value returns the digest value.
//
// Always non-empty.
Value() []byte
isDigest()
}
// NewDigest creates a new Digest.
func NewDigest(digestType DigestType, casDigest cas.Digest) (Digest, error) {
switch digestType {
case DigestTypeP1:
if casDigest.Type() != cas.DigestTypeShake256 {
return nil, syserror.Newf(
"trying to create a %v Digest for a cas Digest of type %v",
digestType,
casDigest.Type(),
)
}
return newDigest(digestType, casDigest), nil
default:
// This is a system error.
return nil, syserror.Newf("unknown DigestType: %v", digestType)
}
}
// ParseDigest parses a Digest from its string representation.
//
// A Digest string is of the form typeString:hexValue.
// The string is expected to be non-empty, If not, an error is returned.
//
// This reverses Digest.String().
//
// Returns an error of type *bufparse.ParseError if the string could not be parsed.
func ParseDigest(s string) (Digest, error) {
if s == "" {
// This should be considered a system error.
return nil, errors.New("empty string passed to ParseDigest")
}
digestTypeString, hexValue, ok := strings.Cut(s, ":")
if !ok {
return nil, bufparse.NewParseError(
"plugin digest",
s,
errors.New(`must be in the form "digest_type:digest_hex_value"`),
)
}
digestType, err := ParseDigestType(digestTypeString)
if err != nil {
return nil, bufparse.NewParseError(
"plugin digest",
digestTypeString,
err,
)
}
value, err := hex.DecodeString(hexValue)
if err != nil {
return nil, bufparse.NewParseError(
"plugin digest",
s,
errors.New(`could not parse hex: must in the form "digest_type:digest_hex_value"`),
)
}
switch digestType {
case DigestTypeP1:
casDigest, err := cas.NewDigest(value)
if err != nil {
return nil, err
}
return NewDigest(digestType, casDigest)
default:
return nil, syserror.Newf("unknown DigestType: %v", digestType)
}
}
// DigestEqual returns true if the given Digests are considered equal.
//
// If both Digests are nil, this returns true.
//
// This checks both the DigestType and Digest value.
func DigestEqual(a Digest, b Digest) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
if a.Type() != b.Type() {
return false
}
return bytes.Equal(a.Value(), b.Value())
}
/// *** PRIVATE ***
type digest struct {
digestType DigestType
casDigest cas.Digest
// Cache as we call String pretty often.
// We could do this lazily but not worth it.
stringValue string
}
// validation should occur outside of this function.
func newDigest(digestType DigestType, casDigest cas.Digest) *digest {
return &digest{
digestType: digestType,
casDigest: casDigest,
stringValue: digestType.String() + ":" + hex.EncodeToString(casDigest.Value()),
}
}
func (d *digest) Type() DigestType {
return d.digestType
}
func (d *digest) Value() []byte {
return d.casDigest.Value()
}
func (d *digest) String() string {
return d.stringValue
}
func (*digest) isDigest() {}