about summary refs log tree commit diff
path: root/tvix/nix-compat/src/nixhash
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/nix-compat/src/nixhash')
-rw-r--r--tvix/nix-compat/src/nixhash/ca_hash.rs48
-rw-r--r--tvix/nix-compat/src/nixhash/mod.rs29
2 files changed, 70 insertions, 7 deletions
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 {