about summary refs log tree commit diff
path: root/tvix/store/protos/pathinfo.go
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/store/protos/pathinfo.go')
-rw-r--r--tvix/store/protos/pathinfo.go93
1 files changed, 93 insertions, 0 deletions
diff --git a/tvix/store/protos/pathinfo.go b/tvix/store/protos/pathinfo.go
new file mode 100644
index 000000000000..6607b5c879fb
--- /dev/null
+++ b/tvix/store/protos/pathinfo.go
@@ -0,0 +1,93 @@
+package storev1
+
+import (
+	"bytes"
+	"encoding/base64"
+	"fmt"
+
+	"github.com/nix-community/go-nix/pkg/storepath"
+)
+
+// Validate performs some checks on the PathInfo struct, returning either the
+// StorePath of the root node, or an error.
+func (p *PathInfo) Validate() (*storepath.StorePath, error) {
+	// ensure References has the right number of bytes.
+	for i, reference := range p.GetReferences() {
+		if len(reference) != storepath.PathHashSize {
+			return nil, fmt.Errorf("invalid length of digest at position %d, expected %d, got %d", i, storepath.PathHashSize, len(reference))
+		}
+	}
+
+	// If there's a Narinfo field populated..
+	if narInfo := p.GetNarinfo(); narInfo != nil {
+		// ensure the number of references matches len(References).
+		if len(narInfo.GetReferenceNames()) != len(p.GetReferences()) {
+			return nil, fmt.Errorf("inconsistent number of references: %d (references) vs %d (narinfo)", len(narInfo.GetReferenceNames()), len(p.GetReferences()))
+		}
+
+		// for each ReferenceName…
+		for i, referenceName := range narInfo.GetReferenceNames() {
+			// ensure it parses to a store path
+			storePath, err := storepath.FromString(referenceName)
+			if err != nil {
+				return nil, fmt.Errorf("invalid ReferenceName at position %d: %w", i, err)
+			}
+
+			// ensure the digest matches the one at References[i]
+			if !bytes.Equal(p.GetReferences()[i], storePath.Digest) {
+				return nil, fmt.Errorf(
+					"digest in ReferenceName at position %d does not match digest in PathInfo, expected %s, got %s",
+					i,
+					base64.StdEncoding.EncodeToString(p.GetReferences()[i]),
+					base64.StdEncoding.EncodeToString(storePath.Digest),
+				)
+			}
+		}
+	}
+
+	// ensure there is a (root) node present
+	rootNode := p.GetNode()
+	if rootNode == nil {
+		return nil, fmt.Errorf("root node must be set")
+	}
+
+	// ensure it properly parses to a store path, and in case it refers to a digest, ensure it has the right length.
+	if node := rootNode.GetDirectory(); node != nil {
+		if len(node.Digest) != 32 {
+			return nil, fmt.Errorf("invalid digest size for %s, expected %d, got %d", node.Name, 32, len(node.Digest))
+		}
+
+		storePath, err := storepath.FromString(string(node.GetName()))
+
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse %s as StorePath: %w", node.Name, err)
+		}
+
+		return storePath, nil
+
+	} else if node := rootNode.GetFile(); node != nil {
+		if len(node.Digest) != 32 {
+			return nil, fmt.Errorf("invalid digest size for %s, expected %d, got %d", node.Name, 32, len(node.Digest))
+		}
+
+		storePath, err := storepath.FromString(string(node.GetName()))
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse %s as StorePath: %w", node.Name, err)
+		}
+
+		return storePath, nil
+
+	} else if node := rootNode.GetSymlink(); node != nil {
+		storePath, err := storepath.FromString(string(node.GetName()))
+
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse %s as StorePath: %w", node.Name, err)
+		}
+
+		return storePath, nil
+
+	} else {
+		// this would only happen if we introduced a new type
+		panic("unreachable")
+	}
+}