diff options
-rw-r--r-- | tvix/nix-compat/src/derivation/mod.rs | 2 | ||||
-rw-r--r-- | tvix/nix-compat/src/derivation/tests/mod.rs | 2 | ||||
-rw-r--r-- | tvix/nix-compat/src/narinfo/mod.rs | 86 | ||||
-rw-r--r-- | tvix/nix-compat/src/nixhash/ca_hash.rs | 48 | ||||
-rw-r--r-- | tvix/nix-compat/src/nixhash/mod.rs | 29 | ||||
-rw-r--r-- | tvix/nix-compat/src/store_path/utils.rs | 13 |
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() }); |