about summary refs log tree commit diff
path: root/tvix/store/src/proto.rs
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2022-12-29T20·37+0100
committerflokli <flokli@flokli.de>2023-01-03T13·03+0000
commit0b56d9f21bb5a857ee17ecc539c173926441a3fb (patch)
treead7c5cb3f66f7a57b4e18c01df8a4b209d4cca0b /tvix/store/src/proto.rs
parentceb2c0ba895554c7cabb0ac20d3a80ea2aba1ab1 (diff)
feat(src/proto): add PathInfo.validate() r/5570
This provides validation of PathInfo messages, and ensures the output
hashes are properly parsed from the root node names.

NixPath already has a more extensive test suite for various wrong
NixPaths, so it's omitted from here.

Change-Id: I5d69118df5816daabb521ddb19d178bddd1caacf
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7684
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/store/src/proto.rs')
-rw-r--r--tvix/store/src/proto.rs99
1 files changed, 99 insertions, 0 deletions
diff --git a/tvix/store/src/proto.rs b/tvix/store/src/proto.rs
index 75e5c34ab137..f2af69ab0dbb 100644
--- a/tvix/store/src/proto.rs
+++ b/tvix/store/src/proto.rs
@@ -5,6 +5,8 @@ use thiserror::Error;
 
 use prost::Message;
 
+use crate::nixpath::{NixPath, ParseNixPathError};
+
 tonic::include_proto!("tvix.store.v1");
 
 #[cfg(feature = "reflection")]
@@ -30,6 +32,27 @@ pub enum ValidateDirectoryError {
     InvalidDigestLen(usize),
 }
 
+/// Errors that can occur during the validation of PathInfo messages.
+#[derive(Debug, Error, PartialEq)]
+pub enum ValidatePathInfoError {
+    /// No node present
+    #[error("No node present")]
+    NoNodePresent(),
+
+    /// Invalid node name encountered.
+    #[error("{0} is an invalid node name: {1}")]
+    InvalidNodeName(String, ParseNixPathError),
+
+    /// The digest the (root) node refers to has invalid length.
+    #[error("Invalid Digest length: {0}")]
+    InvalidDigestLen(usize),
+
+    /// The number of references in the narinfo.reference_names field does not match
+    /// the number of references in the .references field.
+    #[error("Inconsistent Number of References: {0} (references) vs {0} (narinfo)")]
+    InconsistentNumberOfReferences(usize, usize),
+}
+
 /// Checks a Node name for validity as an intermediate node, and returns an
 /// error that's generated from the supplied constructor.
 ///
@@ -51,6 +74,82 @@ fn validate_digest<E>(digest: &Vec<u8>, err: fn(usize) -> E) -> Result<(), E> {
     Ok(())
 }
 
+/// Parses a root node name.
+///
+/// On success, this returns the parsed [NixPath].
+/// On error, it returns an error generated from the supplied constructor.
+fn parse_node_name_root<E>(
+    name: &str,
+    err: fn(String, ParseNixPathError) -> E,
+) -> Result<NixPath, E> {
+    match NixPath::from_string(name) {
+        Ok(np) => Ok(np),
+        Err(e) => Err(err(name.to_string(), e)),
+    }
+}
+
+impl PathInfo {
+    /// validate performs some checks on the PathInfo struct,
+    /// Returning either a [NixPath] of the root node, or a
+    /// [ValidatePathInfoError].
+    pub fn validate(&self) -> Result<NixPath, ValidatePathInfoError> {
+        // If there is a narinfo field populated, ensure the number of references there
+        // matches PathInfo.references count.
+        if let Some(narinfo) = &self.narinfo {
+            if narinfo.reference_names.len() != self.references.len() {
+                return Err(ValidatePathInfoError::InconsistentNumberOfReferences(
+                    narinfo.reference_names.len(),
+                    self.references.len(),
+                ));
+            }
+        }
+        // FUTUREWORK: parse references in reference_names. ensure they start
+        // with storeDir, and use the same digest as in self.references.
+
+        // Ensure there is a (root) node present, and it properly parses to a NixPath.
+        let root_nix_path = match &self.node {
+            None => {
+                return Err(ValidatePathInfoError::NoNodePresent());
+            }
+            Some(Node { node }) => match node {
+                None => {
+                    return Err(ValidatePathInfoError::NoNodePresent());
+                }
+                Some(node::Node::Directory(directory_node)) => {
+                    // ensure the digest has the appropriate size.
+                    validate_digest(
+                        &directory_node.digest,
+                        ValidatePathInfoError::InvalidDigestLen,
+                    )?;
+
+                    // parse the name
+                    parse_node_name_root(
+                        &directory_node.name,
+                        ValidatePathInfoError::InvalidNodeName,
+                    )?
+                }
+                Some(node::Node::File(file_node)) => {
+                    // ensure the digest has the appropriate size.
+                    validate_digest(&file_node.digest, ValidatePathInfoError::InvalidDigestLen)?;
+
+                    // parse the name
+                    parse_node_name_root(&file_node.name, ValidatePathInfoError::InvalidNodeName)?
+                }
+                Some(node::Node::Symlink(symlink_node)) => {
+                    // parse the name
+                    parse_node_name_root(
+                        &symlink_node.name,
+                        ValidatePathInfoError::InvalidNodeName,
+                    )?
+                }
+            },
+        };
+
+        // return the root nix path
+        Ok(root_nix_path)
+    }
+}
+
 /// Accepts a name, and a mutable reference to the previous name.
 /// If the passed name is larger than the previous one, the reference is updated.
 /// If it's not, an error is returned.