Skip to content

Commit

Permalink
Add bufplugin pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
emcfarlane committed Oct 29, 2024
1 parent ee44f6e commit 1cfeb74
Show file tree
Hide file tree
Showing 12 changed files with 951 additions and 1 deletion.
2 changes: 1 addition & 1 deletion private/bufpkg/bufmodule/module_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func ParseModuleRef(moduleRefString string) (ModuleRef, error) {
if err != nil {
return nil, err
}
// We don't rely on constructors for ParseErrors.
// We don't rely on constructors for bufparse.ParseErrors.
return NewModuleRef(registry, owner, name, ref)
}

Expand Down
16 changes: 16 additions & 0 deletions private/bufpkg/bufplugin/bufplugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2020-2024 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 contains the core primitives for working with Buf plugins.
package bufplugin
72 changes: 72 additions & 0 deletions private/bufpkg/bufplugin/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2020-2024 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 (
"sync"
"time"
)

// Commit represents a Commit for a Plugin on the BSR.
type Commit interface {
// PluginKey returns the PluginKey for the Commit.
PluginKey() PluginKey
// CreateTime returns the time the Commit was created on the BSR.
CreateTime() (time.Time, error)

isCommit()
}

// NewCommit returns a new Commit.
func NewCommit(
pluginKey PluginKey,
getCreateTime func() (time.Time, error),
) Commit {
return newCommit(
pluginKey,
getCreateTime,
)
}

// *** PRIVATE ***

type commit struct {
pluginKey PluginKey
getCreateTime func() (time.Time, error)
}

func newCommit(
pluginKey PluginKey,
getCreateTime func() (time.Time, error),
) *commit {
return &commit{
pluginKey: pluginKey,
getCreateTime: sync.OnceValues(getCreateTime),
}
}

func (c *commit) PluginKey() PluginKey {
return c.pluginKey
}

func (c *commit) CreateTime() (time.Time, error) {
// This may invoke tamper-proofing per newCommit construction.
if _, err := c.pluginKey.Digest(); err != nil {
return time.Time{}, err
}
return c.getCreateTime()
}

func (*commit) isCommit() {}
211 changes: 211 additions & 0 deletions private/bufpkg/bufplugin/digest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright 2020-2024 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/bufcas"
"github.com/bufbuild/buf/private/bufpkg/bufparse"
"github.com/bufbuild/buf/private/pkg/syserror"
)

const (
// DigestTypeP1 represents the p4 plugin digest type.
//
// This is the only digest type and should be used. The string value of
// this is "p1".
DigestTypeP1 = 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 thie 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, bufcasDigest bufcas.Digest) (Digest, error) {
switch digestType {
case DigestTypeP1:
if bufcasDigest.Type() != bufcas.DigestTypeShake256 {
return nil, syserror.Newf(
"trying to create a %v Digest for a cas Digest of type %v",
digestType,
bufcasDigest.Type(),
)
}
return newDigest(digestType, bufcasDigest), nil
default:
// This is a system error.
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())
}

// 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 treutned.
//
// This reverses Digest.String().
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:
bufcasDigest, err := bufcas.NewDigest(value)
if err != nil {
return nil, err
}
return NewDigest(digestType, bufcasDigest)
default:
return nil, syserror.Newf("unknown DigestType: %v", digestType)
}
}

/// *** PRIVATE ***

type digest struct {
digestType DigestType
bufcasDigest bufcas.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, bufcasDigest bufcas.Digest) *digest {
return &digest{
digestType: digestType,
bufcasDigest: bufcasDigest,
stringValue: digestType.String() + ":" + hex.EncodeToString(bufcasDigest.Value()),
}
}

func (d *digest) Type() DigestType {
return d.digestType
}

func (d *digest) Value() []byte {
return d.bufcasDigest.Value()
}

func (d *digest) String() string {
return d.stringValue
}

func (*digest) isDigest() {}
69 changes: 69 additions & 0 deletions private/bufpkg/bufplugin/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2020-2024 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 (
"strings"

"github.com/bufbuild/buf/private/pkg/uuidutil"
"github.com/google/uuid"
)

// DigestMismatchError is the error returned if the Digest of a downloaded Module or Commit
// does not match the expected digest in a buf.lock file.
type DigestMismatchError struct {
PluginFullName PluginFullName
CommitID uuid.UUID
ExpectedDigest Digest
ActualDigest Digest
}

// Error implements the error interface.
func (m *DigestMismatchError) Error() string {
if m == nil {
return ""
}
var builder strings.Builder
_, _ = builder.WriteString(`*** Digest verification failed`)
if m.PluginFullName != nil {
_, _ = builder.WriteString(` for "`)
_, _ = builder.WriteString(m.PluginFullName.String())
if m.CommitID != uuid.Nil {
_, _ = builder.WriteString(`:`)
_, _ = builder.WriteString(uuidutil.ToDashless(m.CommitID))
}
_, _ = builder.WriteString(`"`)
}
_, _ = builder.WriteString(` ***`)
_, _ = builder.WriteString("\n")
if m.ExpectedDigest != nil && m.ActualDigest != nil {
_, _ = builder.WriteString("\t")
_, _ = builder.WriteString(`Expected digest (from buf.lock): "`)
_, _ = builder.WriteString(m.ExpectedDigest.String())
_, _ = builder.WriteString(`"`)
_, _ = builder.WriteString("\n")
_, _ = builder.WriteString("\t")
_, _ = builder.WriteString(`Actual digest: "`)
_, _ = builder.WriteString(m.ActualDigest.String())
_, _ = builder.WriteString(`"`)
_, _ = builder.WriteString("\n")
}
_, _ = builder.WriteString("\t")
_, _ = builder.WriteString(`This may be the result of a hand-edited or corrupted buf.lock file, a corrupted local cache, and/or an attack.`)
_, _ = builder.WriteString("\n")
_, _ = builder.WriteString("\t")
_, _ = builder.WriteString(`To clear your local cache, run "buf registry cc".`)
return builder.String()
}
Loading

0 comments on commit 1cfeb74

Please sign in to comment.