about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/nix-compat/src/derivation/mod.rs2
-rw-r--r--tvix/nix-compat/src/derivation/tests/mod.rs2
-rw-r--r--tvix/nix-compat/src/narinfo/mod.rs86
-rw-r--r--tvix/nix-compat/src/nixhash/ca_hash.rs48
-rw-r--r--tvix/nix-compat/src/nixhash/mod.rs29
-rw-r--r--tvix/nix-compat/src/store_path/utils.rs13
6 files changed, 83 insertions, 97 deletions
diff --git a/tvix/nix-compat/src/derivation/mod.rs b/tvix/nix-compat/src/derivation/mod.rs
index a765e343096e..d62a339b77a9 100644
--- a/tvix/nix-compat/src/derivation/mod.rs
+++ b/tvix/nix-compat/src/derivation/mod.rs
@@ -135,7 +135,7 @@ impl Derivation {
             Sha256::new_with_prefix(format!(
                 "fixed:out:{}{}:{}",
                 ca_kind_prefix(ca_hash),
-                ca_hash.digest().to_nix_hash_string(),
+                ca_hash.digest().to_nix_hex_string(),
                 out_output.path
             ))
             .finalize()
diff --git a/tvix/nix-compat/src/derivation/tests/mod.rs b/tvix/nix-compat/src/derivation/tests/mod.rs
index a14ca0938837..d6770bdbaca8 100644
--- a/tvix/nix-compat/src/derivation/tests/mod.rs
+++ b/tvix/nix-compat/src/derivation/tests/mod.rs
@@ -193,7 +193,7 @@ fn derivation_or_fod_hash(drv_path: &str, expected_nix_hash_string: &str) {
 
     let actual = drv.derivation_or_fod_hash(|_| panic!("must not be called"));
 
-    assert_eq!(expected_nix_hash_string, actual.to_nix_hash_string());
+    assert_eq!(expected_nix_hash_string, actual.to_nix_hex_string());
 }
 
 /// This reads a Derivation (in A-Term), trims out all fields containing
diff --git a/tvix/nix-compat/src/narinfo/mod.rs b/tvix/nix-compat/src/narinfo/mod.rs
index e64fb10d7ac8..3fa73103bde6 100644
--- a/tvix/nix-compat/src/narinfo/mod.rs
+++ b/tvix/nix-compat/src/narinfo/mod.rs
@@ -24,11 +24,7 @@ use std::{
     mem,
 };
 
-use crate::{
-    nixbase32,
-    nixhash::{CAHash, NixHash},
-    store_path::StorePathRef,
-};
+use crate::{nixbase32, nixhash::CAHash, store_path::StorePathRef};
 
 mod fingerprint;
 mod signature;
@@ -249,8 +245,8 @@ impl<'a> NarInfo<'a> {
                     signatures.push(val);
                 }
                 "CA" => {
-                    let val =
-                        parse_ca(val).ok_or_else(|| Error::UnableToParseCA(val.to_string()))?;
+                    let val = CAHash::from_nix_hex_str(val)
+                        .ok_or_else(|| Error::UnableToParseCA(val.to_string()))?;
 
                     if ca.replace(val).is_some() {
                         return Err(Error::DuplicateField(tag.to_string()));
@@ -308,14 +304,14 @@ impl Display for NarInfo<'_> {
         }
 
         if let Some(file_hash) = self.file_hash {
-            writeln!(w, "FileHash: {}", fmt_hash(&NixHash::Sha256(file_hash)))?;
+            writeln!(w, "FileHash: sha256:{}", nixbase32::encode(&file_hash),)?;
         }
 
         if let Some(file_size) = self.file_size {
             writeln!(w, "FileSize: {file_size}")?;
         }
 
-        writeln!(w, "NarHash: {}", fmt_hash(&NixHash::Sha256(self.nar_hash)))?;
+        writeln!(w, "NarHash: sha256:{}", nixbase32::encode(&self.nar_hash),)?;
         writeln!(w, "NarSize: {}", self.nar_size)?;
 
         write!(w, "References:")?;
@@ -341,83 +337,13 @@ impl Display for NarInfo<'_> {
         }
 
         if let Some(ca) = &self.ca {
-            writeln!(w, "CA: {}", fmt_ca(ca))?;
+            writeln!(w, "CA: {}", ca.to_nix_nixbase32_string())?;
         }
 
         Ok(())
     }
 }
 
-pub fn parse_ca(s: &str) -> Option<CAHash> {
-    let (tag, s) = s.split_once(':')?;
-
-    match tag {
-        "text" => {
-            let digest = s.strip_prefix("sha256:")?;
-            let digest = nixbase32::decode_fixed(digest).ok()?;
-            Some(CAHash::Text(digest))
-        }
-        "fixed" => {
-            if let Some(s) = s.strip_prefix("r:") {
-                parse_hash(s).map(CAHash::Nar)
-            } else {
-                parse_hash(s).map(CAHash::Flat)
-            }
-        }
-        _ => None,
-    }
-}
-
-#[allow(non_camel_case_types)]
-struct fmt_ca<'a>(&'a CAHash);
-
-impl Display for fmt_ca<'_> {
-    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
-        match self.0 {
-            CAHash::Flat(h) => {
-                write!(w, "fixed:{}", fmt_hash(h))
-            }
-            &CAHash::Text(d) => {
-                write!(w, "text:{}", fmt_hash(&NixHash::Sha256(d)))
-            }
-            CAHash::Nar(h) => {
-                write!(w, "fixed:r:{}", fmt_hash(h))
-            }
-        }
-    }
-}
-
-fn parse_hash(s: &str) -> Option<NixHash> {
-    let (tag, digest) = s.split_once(':')?;
-
-    (match tag {
-        "md5" => nixbase32::decode_fixed(digest).map(NixHash::Md5),
-        "sha1" => nixbase32::decode_fixed(digest).map(NixHash::Sha1),
-        "sha256" => nixbase32::decode_fixed(digest).map(NixHash::Sha256),
-        "sha512" => nixbase32::decode_fixed(digest)
-            .map(Box::new)
-            .map(NixHash::Sha512),
-        _ => return None,
-    })
-    .ok()
-}
-
-#[allow(non_camel_case_types)]
-struct fmt_hash<'a>(&'a NixHash);
-
-impl Display for fmt_hash<'_> {
-    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
-        let (tag, digest) = match self.0 {
-            NixHash::Md5(d) => ("md5", &d[..]),
-            NixHash::Sha1(d) => ("sha1", &d[..]),
-            NixHash::Sha256(d) => ("sha256", &d[..]),
-            NixHash::Sha512(d) => ("sha512", &d[..]),
-        };
-
-        write!(w, "{tag}:{}", nixbase32::encode(digest))
-    }
-}
-
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
     #[error("duplicate field: {0}")]
diff --git a/tvix/nix-compat/src/nixhash/ca_hash.rs b/tvix/nix-compat/src/nixhash/ca_hash.rs
index 93ef52cc63ab..0d6c68d7fa7f 100644
--- a/tvix/nix-compat/src/nixhash/ca_hash.rs
+++ b/tvix/nix-compat/src/nixhash/ca_hash.rs
@@ -10,13 +10,11 @@ use super::algos::SUPPORTED_ALGOS;
 use super::from_algo_and_digest;
 
 /// A Nix CAHash describes a content-addressed hash of a path.
-/// Semantically, it can be split into the following components:
 ///
-///  - "content address prefix". Currently, "fixed" and "text" are supported.
-///  - "hash mode". Currently, "flat" and "recursive" are supported.
-///  - "hash type". The underlying hash function used.
-///    Currently, sha1, md5, sha256, sha512.
-///  - "digest". The digest itself.
+/// The way Nix prints it as a string is a bit confusing, but there's essentially
+/// three modes, `Flat`, `Nar` and `Text`.
+/// `Flat` and `Nar` support all 4 algos that [NixHash] supports
+/// (sha1, md5, sha256, sha512), `Text` only supports sha256.
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub enum CAHash {
     Flat(NixHash),  // "fixed flat"
@@ -33,6 +31,44 @@ impl CAHash {
         }
     }
 
+    /// Constructs a [CAHash] from the textual representation,
+    /// which is one of the three:
+    /// - `text:sha256:$nixbase32sha256digest`
+    /// - `fixed:r:$algo:$nixbase32digest`
+    /// - `fixed:$algo:$nixbase32digest`
+    /// which is the format that's used in the NARInfo for example.
+    pub fn from_nix_hex_str(s: &str) -> Option<Self> {
+        let (tag, s) = s.split_once(':')?;
+
+        match tag {
+            "text" => {
+                let digest = s.strip_prefix("sha256:")?;
+                let digest = nixbase32::decode_fixed(digest).ok()?;
+                Some(CAHash::Text(digest))
+            }
+            "fixed" => {
+                if let Some(s) = s.strip_prefix("r:") {
+                    NixHash::from_nix_hex_str(s).map(CAHash::Nar)
+                } else {
+                    NixHash::from_nix_hex_str(s).map(CAHash::Flat)
+                }
+            }
+            _ => None,
+        }
+    }
+
+    /// Formats a [CAHash] in the Nix default hash format, which is the format
+    /// that's used in NARInfos for example.
+    pub fn to_nix_nixbase32_string(&self) -> String {
+        match self {
+            CAHash::Flat(nh) => format!("fixed:{}", nh.to_nix_nixbase32_string()),
+            CAHash::Nar(nh) => format!("fixed:r:{}", nh.to_nix_nixbase32_string()),
+            CAHash::Text(digest) => {
+                format!("text:sha256:{}", nixbase32::encode(digest))
+            }
+        }
+    }
+
     /// This takes a serde_json::Map and turns it into this structure. This is necessary to do such
     /// shenigans because we have external consumers, like the Derivation parser, who would like to
     /// know whether we have a invalid or a missing NixHashWithMode structure in another structure,
diff --git a/tvix/nix-compat/src/nixhash/mod.rs b/tvix/nix-compat/src/nixhash/mod.rs
index f699a4cd9524..a93d2b68da94 100644
--- a/tvix/nix-compat/src/nixhash/mod.rs
+++ b/tvix/nix-compat/src/nixhash/mod.rs
@@ -41,15 +41,42 @@ impl NixHash {
         }
     }
 
+    /// Constructs a [NixHash] from the Nix default hash format,
+    /// the inverse of [to_nix_hex_string].
+    pub fn from_nix_hex_str(s: &str) -> Option<Self> {
+        let (tag, digest) = s.split_once(':')?;
+
+        (match tag {
+            "md5" => nixbase32::decode_fixed(digest).map(NixHash::Md5),
+            "sha1" => nixbase32::decode_fixed(digest).map(NixHash::Sha1),
+            "sha256" => nixbase32::decode_fixed(digest).map(NixHash::Sha256),
+            "sha512" => nixbase32::decode_fixed(digest)
+                .map(Box::new)
+                .map(NixHash::Sha512),
+            _ => return None,
+        })
+        .ok()
+    }
+
     /// Formats a [NixHash] in the Nix default hash format,
     /// which is the algo, followed by a colon, then the lower hex encoded digest.
-    pub fn to_nix_hash_string(&self) -> String {
+    pub fn to_nix_hex_string(&self) -> String {
         format!(
             "{}:{}",
             self.algo(),
             HEXLOWER.encode(self.digest_as_bytes())
         )
     }
+
+    /// Formats a [NixHash] in the format that's used inside CAHash,
+    /// which is the algo, followed by a colon, then the nixbase32-encoded digest.
+    pub(crate) fn to_nix_nixbase32_string(&self) -> String {
+        format!(
+            "{}:{}",
+            self.algo(),
+            nixbase32::encode(self.digest_as_bytes())
+        )
+    }
 }
 
 impl TryFrom<(HashAlgo, &[u8])> for NixHash {
diff --git a/tvix/nix-compat/src/store_path/utils.rs b/tvix/nix-compat/src/store_path/utils.rs
index a227056edc21..dd32135135db 100644
--- a/tvix/nix-compat/src/store_path/utils.rs
+++ b/tvix/nix-compat/src/store_path/utils.rs
@@ -93,7 +93,7 @@ pub fn build_ca_path<B: AsRef<[u8]>, S: AsRef<str>, I: IntoIterator<Item = S>>(
                     NixHash::Sha256(
                         Sha256::new_with_prefix(format!(
                             "fixed:out:r:{}:",
-                            hash.to_nix_hash_string()
+                            hash.to_nix_hex_string()
                         ))
                         .finalize()
                         .into(),
@@ -115,12 +115,9 @@ pub fn build_ca_path<B: AsRef<[u8]>, S: AsRef<str>, I: IntoIterator<Item = S>>(
                 "output:out",
                 &{
                     NixHash::Sha256(
-                        Sha256::new_with_prefix(format!(
-                            "fixed:out:{}:",
-                            hash.to_nix_hash_string()
-                        ))
-                        .finalize()
-                        .into(),
+                        Sha256::new_with_prefix(format!("fixed:out:{}:", hash.to_nix_hex_string()))
+                            .finalize()
+                            .into(),
                     )
                 },
                 name,
@@ -170,7 +167,7 @@ fn build_store_path_from_fingerprint_parts<B: AsRef<[u8]>>(
 
     let digest = compress_hash(&{
         let mut h = Sha256::new();
-        write!(h, "{ty}:{}:{STORE_DIR}:{name}", hash.to_nix_hash_string()).unwrap();
+        write!(h, "{ty}:{}:{STORE_DIR}:{name}", hash.to_nix_hex_string()).unwrap();
         h.finalize()
     });