refactor(nix-compat/nixhash): move from_ and to_ to NixHash struct
It was a bit confusing to construct NixHash, having them as separate functions in the module itself, rather than in the NixHash impl. Also the names were very inconsistent. This renames parsers to `from_$format_$encoding` and format methods to `to_$format_$encoding`. It also adds / moves around a few docstrings, explaining the formats and encodings in the struct docstring itself. from_str is changed to accept Option<HashAlgo>, not Option<&str>, and the otherwise unused `from_nix_hash_string` is folded into from_str. We also simply use from_sri in from_str, as the error path there doesn't allocate anymore. Similarly, the from_nix_str function was only a helper function used to parse a subset of the formats supported in the NixHash::from_str method. We shouldn't be using it outside of there, all usages (only in tests) have been replaced with NixHash::from_algo_and_digest. Change-Id: I36128839dbef19c58b55d5dc5817e38e37a483cc Reviewed-on: https://cl.snix.dev/c/snix/+/30554 Reviewed-by: Ilan Joselevich <personal@ilanjoselevich.com> Reviewed-by: edef <edef@edef.eu> Tested-by: besadii
This commit is contained in:
parent
6c1bfd778e
commit
f6c66af33d
14 changed files with 280 additions and 265 deletions
|
|
@ -4,7 +4,7 @@ use crate::known_paths::KnownPaths;
|
|||
use crate::snix_store_io::SnixStoreIO;
|
||||
use bstr::BString;
|
||||
use nix_compat::derivation::{Derivation, Output};
|
||||
use nix_compat::nixhash;
|
||||
use nix_compat::nixhash::{CAHash, HashAlgo, NixHash};
|
||||
use nix_compat::store_path::{StorePath, StorePathRef};
|
||||
use snix_eval::builtin_macros::builtins;
|
||||
use snix_eval::generators::{self, GenCo, emit_warning_kind};
|
||||
|
|
@ -132,9 +132,14 @@ fn handle_fixed_output(
|
|||
None => None,
|
||||
};
|
||||
|
||||
// construct a NixHash.
|
||||
let nixhash = nixhash::from_str(&hash_str, hash_algo_str.as_deref())
|
||||
let hash_algo = hash_algo_str
|
||||
.map(|s| HashAlgo::try_from(s.as_str()))
|
||||
.transpose()
|
||||
.map_err(DerivationError::InvalidOutputHash)?;
|
||||
|
||||
// construct a NixHash.
|
||||
let nixhash =
|
||||
NixHash::from_str(&hash_str, hash_algo).map_err(DerivationError::InvalidOutputHash)?;
|
||||
let algo = nixhash.algo();
|
||||
|
||||
// construct the fixed output.
|
||||
|
|
@ -143,8 +148,8 @@ fn handle_fixed_output(
|
|||
Output {
|
||||
path: None,
|
||||
ca_hash: match hash_mode_str.as_deref() {
|
||||
None | Some("flat") => Some(nixhash::CAHash::Flat(nixhash)),
|
||||
Some("recursive") => Some(nixhash::CAHash::Nar(nixhash)),
|
||||
None | Some("flat") => Some(CAHash::Flat(nixhash)),
|
||||
Some("recursive") => Some(CAHash::Nar(nixhash)),
|
||||
Some(other) => {
|
||||
return Err(DerivationError::InvalidOutputHashMode(other.to_string()))?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
fetchers::{Fetch, url_basename},
|
||||
snix_store_io::SnixStoreIO,
|
||||
};
|
||||
use nix_compat::nixhash;
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
use snix_eval::builtin_macros::builtins;
|
||||
use snix_eval::generators::Gen;
|
||||
use snix_eval::generators::GenCo;
|
||||
|
|
@ -70,15 +70,13 @@ async fn extract_fetch_args(
|
|||
}
|
||||
|
||||
// parse the sha256 string into a digest.
|
||||
let sha256 = match sha256_str {
|
||||
Some(sha256_str) => {
|
||||
let nixhash = nixhash::from_str(&sha256_str, Some("sha256"))
|
||||
.map_err(|e| ErrorKind::InvalidHash(e.to_string()))?;
|
||||
|
||||
Some(nixhash.digest_as_bytes().try_into().expect("is sha256"))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let sha256 = sha256_str
|
||||
.map(|x| {
|
||||
NixHash::from_str(&x, Some(HashAlgo::Sha256))
|
||||
.map(|x| x.digest_as_bytes().try_into().expect("is sha256"))
|
||||
.map_err(|e| ErrorKind::InvalidHash(e.to_string()))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// Parse the URL.
|
||||
let url = Url::parse(&url_str).map_err(|e| ErrorKind::SnixError(Rc::new(e)))?;
|
||||
|
|
@ -90,7 +88,7 @@ async fn extract_fetch_args(
|
|||
#[builtins(state = "Rc<SnixStoreIO>")]
|
||||
pub(crate) mod fetcher_builtins {
|
||||
use bstr::ByteSlice;
|
||||
use nix_compat::flakeref;
|
||||
use nix_compat::{flakeref, nixhash::NixHash};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
|
|
@ -154,7 +152,7 @@ pub(crate) mod fetcher_builtins {
|
|||
name,
|
||||
Fetch::URL {
|
||||
url: args.url,
|
||||
exp_hash: args.sha256.map(nixhash::NixHash::Sha256),
|
||||
exp_hash: args.sha256.map(NixHash::Sha256),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ mod import_builtins {
|
|||
use crate::builtins::ImportError;
|
||||
use crate::snix_store_io::SnixStoreIO;
|
||||
use bstr::ByteSlice;
|
||||
use nix_compat::nixhash::{CAHash, NixHash};
|
||||
use nix_compat::nixhash::{CAHash, HashAlgo, NixHash};
|
||||
use nix_compat::store_path::{StorePath, StorePathRef, build_ca_path};
|
||||
use sha2::Digest;
|
||||
use snix_castore::blobservice::BlobService;
|
||||
|
|
@ -368,7 +368,7 @@ mod import_builtins {
|
|||
.select("sha256")
|
||||
.map(|h| {
|
||||
h.to_str().and_then(|expected| {
|
||||
match nix_compat::nixhash::from_str(expected.to_str()?, Some("sha256")) {
|
||||
match NixHash::from_str(expected.to_str()?, Some(HashAlgo::Sha256)) {
|
||||
Ok(NixHash::Sha256(digest)) => Ok(digest),
|
||||
Ok(_) => unreachable!(),
|
||||
Err(e) => Err(ErrorKind::InvalidHash(e.to_string())),
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ mod tests {
|
|||
mod fetch {
|
||||
use super::super::*;
|
||||
use crate::fetchers::Fetch;
|
||||
use nix_compat::{nixbase32, nixhash};
|
||||
use nix_compat::{nixbase32, nixhash::NixHash};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
|
|
@ -618,7 +618,7 @@ mod tests {
|
|||
#[case::url_sha256(
|
||||
Fetch::URL{
|
||||
url: Url::parse("https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch").unwrap(),
|
||||
exp_hash: Some(nixhash::from_sri_str("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap()),
|
||||
exp_hash: Some(NixHash::from_sri("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap()),
|
||||
},
|
||||
Some(StorePathRef::from_bytes(b"06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch").unwrap()),
|
||||
"notmuch-extract-patch"
|
||||
|
|
@ -626,7 +626,7 @@ mod tests {
|
|||
#[case::url_custom_name(
|
||||
Fetch::URL{
|
||||
url: Url::parse("https://test.example/owo").unwrap(),
|
||||
exp_hash: Some(nixhash::from_sri_str("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap()),
|
||||
exp_hash: Some(NixHash::from_sri("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap()),
|
||||
},
|
||||
Some(StorePathRef::from_bytes(b"06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch").unwrap()),
|
||||
"notmuch-extract-patch"
|
||||
|
|
@ -634,7 +634,7 @@ mod tests {
|
|||
#[case::nar_sha256(
|
||||
Fetch::NAR{
|
||||
url: Url::parse("https://cache.nixos.org/nar/0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz").unwrap(),
|
||||
hash: nixhash::from_sri_str("sha256-oj6yfWKbcEerK8D9GdPJtIAOveNcsH1ztGeSARGypRA=").unwrap(),
|
||||
hash: NixHash::from_sri("sha256-oj6yfWKbcEerK8D9GdPJtIAOveNcsH1ztGeSARGypRA=").unwrap(),
|
||||
},
|
||||
Some(StorePathRef::from_bytes(b"b40vjphshq4fdgv8s3yrp0bdlafi4920-0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz").unwrap()),
|
||||
"0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz"
|
||||
|
|
@ -642,7 +642,7 @@ mod tests {
|
|||
#[case::nar_sha1(
|
||||
Fetch::NAR{
|
||||
url: Url::parse("https://cache.nixos.org/nar/0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz").unwrap(),
|
||||
hash: nixhash::from_sri_str("sha1-F/fMsgwkXF8fPCg1v9zPZ4yOFIA=").unwrap(),
|
||||
hash: NixHash::from_sri("sha1-F/fMsgwkXF8fPCg1v9zPZ4yOFIA=").unwrap(),
|
||||
},
|
||||
Some(StorePathRef::from_bytes(b"8kx7fdkdbzs4fkfb57xq0cbhs20ymq2n-0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz").unwrap()),
|
||||
"0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz"
|
||||
|
|
@ -650,7 +650,7 @@ mod tests {
|
|||
#[case::nar_sha1(
|
||||
Fetch::Executable{
|
||||
url: Url::parse("https://cache.nixos.org/nar/0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz").unwrap(),
|
||||
hash: nixhash::from_sri_str("sha1-NKNeU1csW5YJ4lCeWH3Z/apppNU=").unwrap(),
|
||||
hash: NixHash::from_sri("sha1-NKNeU1csW5YJ4lCeWH3Z/apppNU=").unwrap(),
|
||||
},
|
||||
Some(StorePathRef::from_bytes(b"y92hm2xfk1009hrq0ix80j4m5k4j4w21-0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz").unwrap()),
|
||||
"0r8nqa1klm5v17ifc6z96m9wywxkjvgbnqq9pmy0sgqj53wj3n12.nar.xz"
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ mod tests {
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use hex_literal::hex;
|
||||
use nix_compat::{derivation::Derivation, nixbase32, nixhash, store_path::StorePath};
|
||||
use nix_compat::{derivation::Derivation, nixbase32, nixhash::NixHash, store_path::StorePath};
|
||||
use url::Url;
|
||||
|
||||
use super::KnownPaths;
|
||||
|
|
@ -186,7 +186,7 @@ mod tests {
|
|||
static FETCH_URL: LazyLock<Fetch> = LazyLock::new(|| {
|
||||
Fetch::URL {
|
||||
url: Url::parse("https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch").unwrap(),
|
||||
exp_hash: Some(nixhash::from_sri_str("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap())
|
||||
exp_hash: Some(NixHash::from_sri("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap())
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ impl Derivation {
|
|||
Sha256::new_with_prefix(format!(
|
||||
"fixed:out:{}{}:{}",
|
||||
ca_kind_prefix(ca_hash),
|
||||
ca_hash.hash().to_nix_hex_string(),
|
||||
ca_hash.hash().to_nix_lowerhex_string(),
|
||||
out_output
|
||||
.path
|
||||
.as_ref()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use thiserror;
|
|||
use crate::derivation::parse_error::{into_nomerror, ErrorKind, NomError, NomResult};
|
||||
use crate::derivation::{write, CAHash, Derivation, Output};
|
||||
use crate::store_path::{self, StorePath};
|
||||
use crate::{aterm, nixhash};
|
||||
use crate::{aterm, nixhash, nixhash::NixHash};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error<I> {
|
||||
|
|
@ -84,11 +84,11 @@ fn from_algo_and_mode_and_digest<B: AsRef<[u8]>>(
|
|||
digest: B,
|
||||
) -> crate::nixhash::NixHashResult<CAHash> {
|
||||
Ok(match algo_and_mode.strip_prefix("r:") {
|
||||
Some(algo) => nixhash::CAHash::Nar(nixhash::from_algo_and_digest(
|
||||
Some(algo) => nixhash::CAHash::Nar(NixHash::from_algo_and_digest(
|
||||
algo.try_into()?,
|
||||
digest.as_ref(),
|
||||
)?),
|
||||
None => nixhash::CAHash::Flat(nixhash::from_algo_and_digest(
|
||||
None => nixhash::CAHash::Flat(NixHash::from_algo_and_digest(
|
||||
algo_and_mode.try_into()?,
|
||||
digest.as_ref(),
|
||||
)?),
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ fn output_path_construction() {
|
|||
Output {
|
||||
path: None, // will be calculated
|
||||
ca_hash: Some(crate::nixhash::CAHash::Nar(
|
||||
crate::nixhash::from_algo_and_digest(
|
||||
crate::nixhash::NixHash::from_algo_and_digest(
|
||||
crate::nixhash::HashAlgo::Sha256,
|
||||
&data_encoding::HEXLOWER
|
||||
.decode(
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ impl TryFrom<&str> for HashAlgo {
|
|||
"sha1" => Ok(Self::Sha1),
|
||||
"sha256" => Ok(Self::Sha256),
|
||||
"sha512" => Ok(Self::Sha512),
|
||||
_ => Err(Error::InvalidAlgo(algo_str.to_string())),
|
||||
_ => Err(Error::InvalidAlgo),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ impl CAHash {
|
|||
}
|
||||
"fixed" => {
|
||||
if let Some(s) = s.strip_prefix("r:") {
|
||||
NixHash::from_nix_nixbase32_str(s).map(CAHash::Nar)
|
||||
NixHash::from_nix_nixbase32(s).map(CAHash::Nar)
|
||||
} else {
|
||||
NixHash::from_nix_nixbase32_str(s).map(CAHash::Flat)
|
||||
NixHash::from_nix_nixbase32(s).map(CAHash::Flat)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
|
|
@ -218,7 +218,12 @@ impl<'de> Deserialize<'de> for CAHash {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{derivation::CAHash, nixhash};
|
||||
use hex_literal::hex;
|
||||
|
||||
use crate::{
|
||||
derivation::CAHash,
|
||||
nixhash::{HashAlgo, NixHash},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn serialize_flat() {
|
||||
|
|
@ -227,8 +232,9 @@ mod tests {
|
|||
"hashAlgo": "sha256"
|
||||
}"#;
|
||||
let hash = CAHash::Flat(
|
||||
nixhash::from_nix_str(
|
||||
"sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
|
||||
NixHash::from_algo_and_digest(
|
||||
HashAlgo::Sha256,
|
||||
&hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
|
@ -243,8 +249,9 @@ mod tests {
|
|||
"hashAlgo": "r:sha256"
|
||||
}"#;
|
||||
let hash = CAHash::Nar(
|
||||
nixhash::from_nix_str(
|
||||
"sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
|
||||
NixHash::from_algo_and_digest(
|
||||
HashAlgo::Sha256,
|
||||
&hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
|
@ -264,8 +271,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
hash,
|
||||
CAHash::Flat(
|
||||
nixhash::from_nix_str(
|
||||
"sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
|
||||
NixHash::from_algo_and_digest(
|
||||
HashAlgo::Sha256,
|
||||
&hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
|
|
@ -284,8 +292,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
hash,
|
||||
CAHash::Nar(
|
||||
nixhash::from_nix_str(
|
||||
"sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
|
||||
NixHash::from_algo_and_digest(
|
||||
HashAlgo::Sha256,
|
||||
&hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
|
|
@ -304,8 +313,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
hash,
|
||||
CAHash::Nar(
|
||||
nixhash::from_nix_str(
|
||||
"sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
|
||||
NixHash::from_algo_and_digest(
|
||||
HashAlgo::Sha256,
|
||||
&hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
|
|
@ -324,8 +334,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
hash,
|
||||
CAHash::Nar(
|
||||
nixhash::from_nix_str(
|
||||
"sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
|
||||
NixHash::from_algo_and_digest(
|
||||
HashAlgo::Sha256,
|
||||
&hex!("08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,44 @@ pub use algos::HashAlgo;
|
|||
pub use ca_hash::CAHash;
|
||||
pub use ca_hash::HashMode as CAHashMode;
|
||||
|
||||
/// NixHash represents hashes known by Nix.
|
||||
/// NixHash represents hashes known by Nix (md5/sha1/sha256/sha512).
|
||||
///
|
||||
/// Internally, these are represented as an enum of 4 kinds (the latter being
|
||||
/// boxed for size reasons, as we rarely use sha512, having a pointer there
|
||||
/// is fine).
|
||||
///
|
||||
/// There's [Self::algo] and [Self::digest_as_bytes] accessors,
|
||||
/// as well as a [Self::from_algo_and_digest] constructor.
|
||||
///
|
||||
/// A few methods to parse (`from_$format_$encoding`) and emit
|
||||
/// (`to_$format_$encoding`) various formats and encodings Nix uses.
|
||||
///
|
||||
/// # Formats
|
||||
/// The following formats exist:
|
||||
///
|
||||
/// ## Nix Format
|
||||
/// Lowercase algo, followed by a colon, then the digest.
|
||||
///
|
||||
/// ## SRI Format
|
||||
/// Uses the lowercase algo, followed by a `-`, then the digest (base64-encoded).
|
||||
/// This is also used in the Display implementation.
|
||||
///
|
||||
/// Contrary to the SRI spec, Nix doesn't have an understanding of passing
|
||||
/// multiple hashes (with different algos) in SRI hashes.
|
||||
/// It instead simply cuts everything off after the expected length for the
|
||||
/// specified algo, and tries to parse the rest in permissive base64 (allowing
|
||||
/// missing padding).
|
||||
///
|
||||
/// ## Digest only
|
||||
/// It's possible to not specify the algo at all. In that case, the expected
|
||||
/// NixHash algo MUST be provided externally.
|
||||
///
|
||||
/// # Encodings
|
||||
/// For "Nix" and "Digest only" formats, the following encodings are supported:
|
||||
///
|
||||
/// - lowerhex,
|
||||
/// - nixbase32,
|
||||
/// - base64 (StdEncoding)
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum NixHash {
|
||||
Md5([u8; 16]),
|
||||
|
|
@ -72,9 +109,25 @@ impl NixHash {
|
|||
}
|
||||
}
|
||||
|
||||
/// Constructs a new [NixHash] by specifying [HashAlgo] and digest.
|
||||
/// It can fail if the passed digest length doesn't match what's expected for
|
||||
/// the passed algo.
|
||||
pub fn from_algo_and_digest(algo: HashAlgo, digest: &[u8]) -> NixHashResult<NixHash> {
|
||||
if digest.len() != algo.digest_length() {
|
||||
return Err(Error::InvalidDigestLength(digest.len(), algo));
|
||||
}
|
||||
|
||||
Ok(match algo {
|
||||
HashAlgo::Md5 => NixHash::Md5(digest.try_into().unwrap()),
|
||||
HashAlgo::Sha1 => NixHash::Sha1(digest.try_into().unwrap()),
|
||||
HashAlgo::Sha256 => NixHash::Sha256(digest.try_into().unwrap()),
|
||||
HashAlgo::Sha512 => NixHash::Sha512(Box::new(digest.try_into().unwrap())),
|
||||
})
|
||||
}
|
||||
|
||||
/// Constructs a new [NixHash] from the Nix default hash format,
|
||||
/// the inverse of [Self::to_nix_nixbase32_string].
|
||||
pub fn from_nix_nixbase32_str(s: &str) -> Option<Self> {
|
||||
/// the inverse of [Self::to_nix_nixbase32].
|
||||
pub fn from_nix_nixbase32(s: &str) -> Option<Self> {
|
||||
let (tag, digest) = s.split_once(':')?;
|
||||
|
||||
(match tag {
|
||||
|
|
@ -89,19 +142,8 @@ impl NixHash {
|
|||
.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_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 fn to_nix_nixbase32_string(&self) -> String {
|
||||
/// Formats a [NixHash] in the Nix nixbase32 format.
|
||||
pub fn to_nix_nixbase32(&self) -> String {
|
||||
format!(
|
||||
"{}:{}",
|
||||
self.algo(),
|
||||
|
|
@ -109,150 +151,11 @@ impl NixHash {
|
|||
)
|
||||
}
|
||||
|
||||
/// Writes a [NixHash] in SRI format to a [std::fmt::Write].
|
||||
pub fn write_sri_str(&self, w: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
w,
|
||||
"{}-{}",
|
||||
self.algo(),
|
||||
BASE64.encode(self.digest_as_bytes())
|
||||
)
|
||||
}
|
||||
|
||||
/// Formats a [NixHash] to an SRI string.
|
||||
pub fn to_sri_string(&self) -> String {
|
||||
let mut s = String::new();
|
||||
self.write_sri_str(&mut s).unwrap();
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new [NixHash] by specifying [HashAlgo] and digest.
|
||||
/// It can fail if the passed digest length doesn't match what's expected for
|
||||
/// the passed algo.
|
||||
pub fn from_algo_and_digest(algo: HashAlgo, digest: &[u8]) -> NixHashResult<NixHash> {
|
||||
if digest.len() != algo.digest_length() {
|
||||
return Err(Error::InvalidDigestLength(digest.len(), algo));
|
||||
}
|
||||
|
||||
Ok(match algo {
|
||||
HashAlgo::Md5 => NixHash::Md5(digest.try_into().unwrap()),
|
||||
HashAlgo::Sha1 => NixHash::Sha1(digest.try_into().unwrap()),
|
||||
HashAlgo::Sha256 => NixHash::Sha256(digest.try_into().unwrap()),
|
||||
HashAlgo::Sha512 => NixHash::Sha512(Box::new(digest.try_into().unwrap())),
|
||||
})
|
||||
}
|
||||
|
||||
/// Errors related to NixHash construction.
|
||||
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("invalid hash algo: {0}")]
|
||||
InvalidAlgo(String),
|
||||
#[error("invalid SRI string: {0}")]
|
||||
InvalidSRI(String),
|
||||
#[error("invalid encoded digest length '{0}' for algo {1}")]
|
||||
InvalidDigestLength(usize, HashAlgo),
|
||||
#[error("invalid base16 encoding: {0}")]
|
||||
InvalidBase16Encoding(data_encoding::DecodeError),
|
||||
#[error("invalid base32 encoding: {0}")]
|
||||
InvalidBase32Encoding(data_encoding::DecodeError),
|
||||
#[error("invalid base64 encoding: {0}")]
|
||||
InvalidBase64Encoding(data_encoding::DecodeError),
|
||||
#[error("conflicting hash algo: {0} (hash_algo) vs {1} (inline)")]
|
||||
ConflictingHashAlgos(HashAlgo, HashAlgo),
|
||||
#[error("missing inline hash algo, but no externally-specified algo: {0}")]
|
||||
MissingInlineHashAlgo(String),
|
||||
}
|
||||
|
||||
/// Nix allows specifying hashes in various encodings, and magically just
|
||||
/// derives the encoding.
|
||||
/// This function parses strings to a NixHash.
|
||||
///
|
||||
/// Hashes can be:
|
||||
/// - Nix hash strings
|
||||
/// - SRI hashes
|
||||
/// - bare digests
|
||||
///
|
||||
/// Encoding for Nix hash strings or bare digests can be:
|
||||
/// - base16 (lowerhex),
|
||||
/// - nixbase32,
|
||||
/// - base64 (StdEncoding)
|
||||
/// - sri string
|
||||
///
|
||||
/// The encoding is derived from the length of the string and the hash type.
|
||||
/// The hash is communicated out-of-band, but might also be in-band (in the
|
||||
/// case of a nix hash string or SRI), in which it needs to be consistent with the
|
||||
/// one communicated out-of-band.
|
||||
pub fn from_str(s: &str, algo_str: Option<&str>) -> NixHashResult<NixHash> {
|
||||
// if algo_str is some, parse or bail out
|
||||
let algo: Option<HashAlgo> = algo_str.map(HashAlgo::try_from).transpose()?;
|
||||
|
||||
// Peek at the beginning of the string to detect SRI hashes.
|
||||
if s.starts_with("sha1-")
|
||||
|| s.starts_with("sha256-")
|
||||
|| s.starts_with("sha512-")
|
||||
|| s.starts_with("md5-")
|
||||
{
|
||||
let parsed_nixhash = from_sri_str(s)?;
|
||||
|
||||
// ensure the algo matches with what has been passed externally, if so.
|
||||
if let Some(algo) = algo {
|
||||
if algo != parsed_nixhash.algo() {
|
||||
return Err(Error::ConflictingHashAlgos(algo, parsed_nixhash.algo()));
|
||||
}
|
||||
}
|
||||
return Ok(parsed_nixhash);
|
||||
}
|
||||
|
||||
// Peek at the beginning again to see if it's a Nix Hash
|
||||
if s.starts_with("sha1:")
|
||||
|| s.starts_with("sha256:")
|
||||
|| s.starts_with("sha512:")
|
||||
|| s.starts_with("md5:")
|
||||
{
|
||||
let parsed_nixhash = from_nix_str(s)?;
|
||||
|
||||
// ensure the algo matches with what has been passed externally, if so.
|
||||
if let Some(algo) = algo {
|
||||
if algo != parsed_nixhash.algo() {
|
||||
return Err(Error::ConflictingHashAlgos(algo, parsed_nixhash.algo()));
|
||||
}
|
||||
}
|
||||
return Ok(parsed_nixhash);
|
||||
}
|
||||
|
||||
// Neither of these, assume a bare digest, so there MUST be an externally-passed algo.
|
||||
let algo = algo.ok_or_else(|| Error::MissingInlineHashAlgo(s.to_string()))?;
|
||||
decode_digest(s.as_bytes(), algo)
|
||||
}
|
||||
|
||||
/// Parses a Nix hash string ($algo:$digest) to a NixHash.
|
||||
pub fn from_nix_str(s: &str) -> NixHashResult<NixHash> {
|
||||
if let Some(rest) = s.strip_prefix("sha1:") {
|
||||
decode_digest(rest.as_bytes(), HashAlgo::Sha1)
|
||||
} else if let Some(rest) = s.strip_prefix("sha256:") {
|
||||
decode_digest(rest.as_bytes(), HashAlgo::Sha256)
|
||||
} else if let Some(rest) = s.strip_prefix("sha512:") {
|
||||
decode_digest(rest.as_bytes(), HashAlgo::Sha512)
|
||||
} else if let Some(rest) = s.strip_prefix("md5:") {
|
||||
decode_digest(rest.as_bytes(), HashAlgo::Md5)
|
||||
} else {
|
||||
Err(Error::InvalidAlgo(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a Nix SRI string to a NixHash.
|
||||
/// Contrary to the SRI spec, Nix doesn't have an understanding of passing
|
||||
/// multiple hashes (with different algos) in SRI hashes.
|
||||
/// It instead simply cuts everything off after the expected length for the
|
||||
/// specified algo, and tries to parse the rest in permissive base64 (allowing
|
||||
/// missing padding).
|
||||
pub fn from_sri_str(s: &str) -> NixHashResult<NixHash> {
|
||||
/// Parses a Nix SRI string to a NixHash.
|
||||
/// (See caveats in [Self] on the deviations from the SRI spec)
|
||||
pub fn from_sri(s: &str) -> NixHashResult<NixHash> {
|
||||
// split at the first occurence of "-"
|
||||
let (algo_str, digest_str) = s
|
||||
.split_once('-')
|
||||
.ok_or_else(|| Error::InvalidSRI(s.to_string()))?;
|
||||
let (algo_str, digest_str) = s.split_once('-').ok_or(Error::InvalidSRI)?;
|
||||
|
||||
// try to map the part before that `-` to a supported hash algo:
|
||||
let algo: HashAlgo = algo_str.try_into()?;
|
||||
|
|
@ -286,7 +189,103 @@ pub fn from_sri_str(s: &str) -> NixHashResult<NixHash> {
|
|||
.decode(digest_str.trim_end_with(|c| c == '='))
|
||||
.map_err(Error::InvalidBase64Encoding)?;
|
||||
|
||||
from_algo_and_digest(algo, &digest)
|
||||
Self::from_algo_and_digest(algo, &digest)
|
||||
}
|
||||
|
||||
/// Writes a [NixHash] in SRI format to a [std::fmt::Write].
|
||||
pub fn write_sri_str(&self, w: &mut impl std::fmt::Write) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
w,
|
||||
"{}-{}",
|
||||
self.algo(),
|
||||
BASE64.encode(self.digest_as_bytes())
|
||||
)
|
||||
}
|
||||
|
||||
/// Formats a [NixHash] to an SRI string.
|
||||
pub fn to_sri_string(&self) -> String {
|
||||
let mut s = String::new();
|
||||
self.write_sri_str(&mut s).unwrap();
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Formats a [NixHash] in the Nix lowerhex format.
|
||||
pub fn to_nix_lowerhex_string(&self) -> String {
|
||||
format!(
|
||||
"{}:{}",
|
||||
self.algo(),
|
||||
HEXLOWER.encode(self.digest_as_bytes())
|
||||
)
|
||||
}
|
||||
|
||||
/// This parses all known output formats for NixHash.
|
||||
/// See [NixHash] for a list.
|
||||
/// An optional algo needs to be provided, which is mandatory to be specified if
|
||||
/// the "digest only" format is used.
|
||||
/// In other cases, consistency of an optionally externally configured algo
|
||||
/// with the one parsed is ensured.
|
||||
pub fn from_str(s: &str, want_algo: Option<HashAlgo>) -> NixHashResult<NixHash> {
|
||||
// Check for SRI hashes.
|
||||
if let Ok(parsed_nixhash) = Self::from_sri(s) {
|
||||
// ensure the algo matches with what has been passed externally, if so.
|
||||
if let Some(algo) = want_algo {
|
||||
if algo != parsed_nixhash.algo() {
|
||||
return Err(Error::ConflictingHashAlgos(algo, parsed_nixhash.algo()));
|
||||
}
|
||||
}
|
||||
return Ok(parsed_nixhash);
|
||||
}
|
||||
|
||||
// Check for $algo:$digest style NixHash.
|
||||
if let Some(parsed_nixhash) = {
|
||||
if let Some(rest) = s.strip_prefix("sha1:") {
|
||||
Some(decode_digest(rest.as_bytes(), HashAlgo::Sha1)?)
|
||||
} else if let Some(rest) = s.strip_prefix("sha256:") {
|
||||
Some(decode_digest(rest.as_bytes(), HashAlgo::Sha256)?)
|
||||
} else if let Some(rest) = s.strip_prefix("sha512:") {
|
||||
Some(decode_digest(rest.as_bytes(), HashAlgo::Sha512)?)
|
||||
} else if let Some(rest) = s.strip_prefix("md5:") {
|
||||
Some(decode_digest(rest.as_bytes(), HashAlgo::Md5)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} {
|
||||
// ensure the algo matches with what has been passed externally, if so.
|
||||
if let Some(algo) = want_algo {
|
||||
if algo != parsed_nixhash.algo() {
|
||||
return Err(Error::ConflictingHashAlgos(algo, parsed_nixhash.algo()));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(parsed_nixhash);
|
||||
}
|
||||
|
||||
// We're left with the bare digest case, so there MUST be an externally-passed algo.
|
||||
let algo = want_algo.ok_or_else(|| Error::MissingInlineHashAlgo(s.to_string()))?;
|
||||
decode_digest(s.as_bytes(), algo)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors related to NixHash construction.
|
||||
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("invalid hash algo")]
|
||||
InvalidAlgo,
|
||||
#[error("invalid SRI string")]
|
||||
InvalidSRI,
|
||||
#[error("invalid encoded digest length '{0}' for algo {1}")]
|
||||
InvalidDigestLength(usize, HashAlgo),
|
||||
#[error("invalid base16 encoding: {0}")]
|
||||
InvalidBase16Encoding(data_encoding::DecodeError),
|
||||
#[error("invalid base32 encoding: {0}")]
|
||||
InvalidBase32Encoding(data_encoding::DecodeError),
|
||||
#[error("invalid base64 encoding: {0}")]
|
||||
InvalidBase64Encoding(data_encoding::DecodeError),
|
||||
#[error("conflicting hash algo: {0} (hash_algo) vs {1} (inline)")]
|
||||
ConflictingHashAlgos(HashAlgo, HashAlgo),
|
||||
#[error("missing inline hash algo, but no externally-specified algo: {0:?}")]
|
||||
MissingInlineHashAlgo(String),
|
||||
}
|
||||
|
||||
/// Decode a plain digest depending on the hash algo specified externally.
|
||||
|
|
@ -309,14 +308,14 @@ fn decode_digest(s: &[u8], algo: HashAlgo) -> NixHashResult<NixHash> {
|
|||
Err(Error::InvalidDigestLength(s.len(), algo))?
|
||||
};
|
||||
|
||||
Ok(from_algo_and_digest(algo, &digest).unwrap())
|
||||
Ok(NixHash::from_algo_and_digest(algo, &digest).unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
nixbase32,
|
||||
nixhash::{self, HashAlgo, NixHash},
|
||||
nixhash::{HashAlgo, NixHash},
|
||||
};
|
||||
use data_encoding::{BASE64, BASE64_NOPAD, HEXLOWER};
|
||||
use hex_literal::hex;
|
||||
|
|
@ -345,68 +344,67 @@ mod tests {
|
|||
}
|
||||
|
||||
// TODO
|
||||
fn make_nixhash(algo: &HashAlgo, digest_encoded: String) -> String {
|
||||
fn make_nixhash(algo: HashAlgo, digest_encoded: String) -> String {
|
||||
format!("{}:{}", algo, digest_encoded)
|
||||
}
|
||||
fn make_sri_string(algo: &HashAlgo, digest_encoded: String) -> String {
|
||||
fn make_sri_string(algo: HashAlgo, digest_encoded: String) -> String {
|
||||
format!("{}-{}", algo, digest_encoded)
|
||||
}
|
||||
|
||||
/// Test parsing a hash string in various formats, and also when/how the out-of-band algo is needed.
|
||||
#[rstest]
|
||||
#[case::sha1(&NixHash::Sha1(DIGEST_SHA1))]
|
||||
#[case::sha256(&NixHash::Sha256(DIGEST_SHA256))]
|
||||
#[case::sha512(&NixHash::Sha512(Box::new(DIGEST_SHA512)))]
|
||||
#[case::md5(&NixHash::Md5(DIGEST_MD5))]
|
||||
fn from_str(#[case] expected_hash: &NixHash) {
|
||||
let algo = &expected_hash.algo();
|
||||
#[case::sha1(NixHash::Sha1(DIGEST_SHA1))]
|
||||
#[case::sha256(NixHash::Sha256(DIGEST_SHA256))]
|
||||
#[case::sha512(NixHash::Sha512(Box::new(DIGEST_SHA512)))]
|
||||
#[case::md5(NixHash::Md5(DIGEST_MD5))]
|
||||
fn from_str(#[case] expected_hash: NixHash) {
|
||||
let algo = expected_hash.algo();
|
||||
let digest = expected_hash.digest_as_bytes();
|
||||
// parse SRI
|
||||
{
|
||||
// base64 without out-of-band algo
|
||||
let s = make_sri_string(algo, to_base64(digest));
|
||||
let h = nixhash::from_str(&s, None).expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
let h = NixHash::from_str(&s, None).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
|
||||
// base64 with out-of-band-algo
|
||||
let s = make_sri_string(algo, to_base64(digest));
|
||||
let h = nixhash::from_str(&s, Some(&expected_hash.algo().to_string()))
|
||||
.expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
let h = NixHash::from_str(&s, Some(expected_hash.algo())).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
|
||||
// base64_nopad without out-of-band algo
|
||||
let s = make_sri_string(algo, to_base64_nopad(digest));
|
||||
let h = nixhash::from_str(&s, None).expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
let h = NixHash::from_str(&s, None).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
|
||||
// base64_nopad with out-of-band-algo
|
||||
let s = make_sri_string(algo, to_base64_nopad(digest));
|
||||
let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
let h = NixHash::from_str(&s, Some(algo)).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
}
|
||||
|
||||
// parse plain base16. should succeed with algo out-of-band, but fail without.
|
||||
{
|
||||
let s = to_base16(digest);
|
||||
nixhash::from_str(&s, None).expect_err("must fail");
|
||||
let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
NixHash::from_str(&s, None).expect_err("must fail");
|
||||
let h = NixHash::from_str(&s, Some(algo)).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
}
|
||||
|
||||
// parse plain nixbase32. should succeed with algo out-of-band, but fail without.
|
||||
{
|
||||
let s = to_nixbase32(digest);
|
||||
nixhash::from_str(&s, None).expect_err("must fail");
|
||||
let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
NixHash::from_str(&s, None).expect_err("must fail");
|
||||
let h = NixHash::from_str(&s, Some(algo)).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
}
|
||||
|
||||
// parse plain base64. should succeed with algo out-of-band, but fail without.
|
||||
{
|
||||
let s = to_base64(digest);
|
||||
nixhash::from_str(&s, None).expect_err("must fail");
|
||||
let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
|
||||
assert_eq!(expected_hash, &h);
|
||||
NixHash::from_str(&s, None).expect_err("must fail");
|
||||
let h = NixHash::from_str(&s, Some(algo)).expect("must succeed");
|
||||
assert_eq!(expected_hash, h);
|
||||
}
|
||||
|
||||
// parse Nix hash strings
|
||||
|
|
@ -416,11 +414,11 @@ mod tests {
|
|||
let s = make_nixhash(algo, to_base16(digest));
|
||||
assert_eq!(
|
||||
expected_hash,
|
||||
&nixhash::from_str(&s, None).expect("must succeed")
|
||||
NixHash::from_str(&s, None).expect("must succeed")
|
||||
);
|
||||
assert_eq!(
|
||||
expected_hash,
|
||||
&nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed")
|
||||
NixHash::from_str(&s, Some(algo)).expect("must succeed")
|
||||
);
|
||||
}
|
||||
// nixbase32. should succeed with both algo out-of-band and in-band.
|
||||
|
|
@ -428,11 +426,11 @@ mod tests {
|
|||
let s = make_nixhash(algo, to_nixbase32(digest));
|
||||
assert_eq!(
|
||||
expected_hash,
|
||||
&nixhash::from_str(&s, None).expect("must succeed")
|
||||
NixHash::from_str(&s, None).expect("must succeed")
|
||||
);
|
||||
assert_eq!(
|
||||
expected_hash,
|
||||
&nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed")
|
||||
NixHash::from_str(&s, Some(algo)).expect("must succeed")
|
||||
);
|
||||
}
|
||||
// base64. should succeed with both algo out-of-band and in-band.
|
||||
|
|
@ -440,11 +438,11 @@ mod tests {
|
|||
let s = make_nixhash(algo, to_base64(digest));
|
||||
assert_eq!(
|
||||
expected_hash,
|
||||
&nixhash::from_str(&s, None).expect("must succeed")
|
||||
NixHash::from_str(&s, None).expect("must succeed")
|
||||
);
|
||||
assert_eq!(
|
||||
expected_hash,
|
||||
&nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed")
|
||||
NixHash::from_str(&s, Some(algo)).expect("must succeed")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -453,7 +451,7 @@ mod tests {
|
|||
/// Test parsing an SRI hash via the [nixhash::from_sri_str] method.
|
||||
#[test]
|
||||
fn from_sri_str() {
|
||||
let nix_hash = nixhash::from_sri_str("sha256-pc6cFV7Qk5dhRkbJcX/HzZSxAj17drYY1Ank/v1unTk=")
|
||||
let nix_hash = NixHash::from_sri("sha256-pc6cFV7Qk5dhRkbJcX/HzZSxAj17drYY1Ank/v1unTk=")
|
||||
.expect("must succeed");
|
||||
|
||||
assert_eq!(HashAlgo::Sha256, nix_hash.algo());
|
||||
|
|
@ -471,7 +469,7 @@ mod tests {
|
|||
#[case::too_much_padding("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ===")]
|
||||
#[case::additional_suffix_ignored("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ== cheesecake")]
|
||||
fn from_sri_str_sha512_paddings(#[case] sri_str: &str) {
|
||||
let nix_hash = nixhash::from_sri_str(sri_str).expect("must succeed");
|
||||
let nix_hash = NixHash::from_sri(sri_str).expect("must succeed");
|
||||
|
||||
assert_eq!(HashAlgo::Sha512, nix_hash.algo());
|
||||
assert_eq!(
|
||||
|
|
@ -484,14 +482,13 @@ mod tests {
|
|||
/// doesn't match what's expected from that hash function.
|
||||
#[test]
|
||||
fn from_sri_str_truncated() {
|
||||
nixhash::from_sri_str("sha256-pc6cFV7Qk5dhRkbJcX/HzZSxAj17drYY1Ank")
|
||||
.expect_err("must fail");
|
||||
NixHash::from_sri("sha256-pc6cFV7Qk5dhRkbJcX/HzZSxAj17drYY1Ank").expect_err("must fail");
|
||||
}
|
||||
|
||||
/// Ensure we fail on SRI hashes that Nix doesn't support.
|
||||
#[test]
|
||||
fn from_sri_str_unsupported() {
|
||||
nixhash::from_sri_str(
|
||||
NixHash::from_sri(
|
||||
"sha384-o4UVSl89mIB0sFUK+3jQbG+C9Zc9dRlV/Xd3KAvXEbhqxu0J5OAdg6b6VHKHwQ7U",
|
||||
)
|
||||
.expect_err("must fail");
|
||||
|
|
@ -500,7 +497,7 @@ mod tests {
|
|||
/// Ensure we reject invalid base64 encoding
|
||||
#[test]
|
||||
fn from_sri_str_invalid_base64() {
|
||||
nixhash::from_sri_str("sha256-invalid=base64").expect_err("must fail");
|
||||
NixHash::from_sri("sha256-invalid=base64").expect_err("must fail");
|
||||
}
|
||||
|
||||
/// Nix also accepts SRI strings with missing padding, but only in case the
|
||||
|
|
@ -519,17 +516,20 @@ mod tests {
|
|||
hex!("7e022bdd3c851830173f9faaa006a230a0e0fdad4c953e85bff4bf0da036e12f");
|
||||
|
||||
// passing hash algo out of band should succeed
|
||||
let nix_hash = nixhash::from_str(&format!("sha256-{}", &broken_base64), Some("sha256"))
|
||||
let nix_hash = NixHash::from_str(
|
||||
&format!("sha256-{}", &broken_base64),
|
||||
Some(HashAlgo::Sha256),
|
||||
)
|
||||
.expect("must succeed");
|
||||
assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
|
||||
|
||||
// not passing hash algo out of band should succeed
|
||||
let nix_hash =
|
||||
nixhash::from_str(&format!("sha256-{}", &broken_base64), None).expect("must succeed");
|
||||
NixHash::from_str(&format!("sha256-{}", &broken_base64), None).expect("must succeed");
|
||||
assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
|
||||
|
||||
// not passing SRI, but hash algo out of band should fail
|
||||
nixhash::from_str(broken_base64, Some("sha256")).expect_err("must fail");
|
||||
NixHash::from_str(broken_base64, Some(HashAlgo::Sha256)).expect_err("must fail");
|
||||
}
|
||||
|
||||
/// As we decided to pass our hashes by trimming `=` completely,
|
||||
|
|
@ -544,17 +544,18 @@ mod tests {
|
|||
let expected_digest =
|
||||
hex!("b3271e24c5049270430872bc786b3aad45372109fe1e741f5117c2ac3c583daf");
|
||||
|
||||
let nix_hash = nixhash::from_str(&format!("sha256-{}", &weird_base64), Some("sha256"))
|
||||
let nix_hash =
|
||||
NixHash::from_str(&format!("sha256-{}", &weird_base64), Some(HashAlgo::Sha256))
|
||||
.expect("must succeed");
|
||||
assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
|
||||
|
||||
// not passing hash algo out of band should succeed
|
||||
let nix_hash =
|
||||
nixhash::from_str(&format!("sha256-{}", &weird_base64), None).expect("must succeed");
|
||||
NixHash::from_str(&format!("sha256-{}", &weird_base64), None).expect("must succeed");
|
||||
assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
|
||||
|
||||
// not passing SRI, but hash algo out of band should fail
|
||||
nixhash::from_str(weird_base64, Some("sha256")).expect_err("must fail");
|
||||
NixHash::from_str(weird_base64, Some(HashAlgo::Sha256)).expect_err("must fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ impl<'de> Deserialize<'de> for NixHash {
|
|||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let str: &'de str = Deserialize::deserialize(deserializer)?;
|
||||
super::from_str(str, None).map_err(|_| {
|
||||
NixHash::from_str(str, None).map_err(|_| {
|
||||
serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"NixHash")
|
||||
})
|
||||
}
|
||||
|
|
@ -27,16 +27,16 @@ impl Serialize for NixHash {
|
|||
/// The length of a sha256 digest, nixbase32-encoded.
|
||||
const NIXBASE32_SHA256_ENCODE_LEN: usize = nixbase32::encode_len(32);
|
||||
|
||||
pub fn from_nix_hash_string<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
|
||||
pub fn from_nix_nixbase32_or_sri<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let str: &'de str = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(digest_str) = str.strip_prefix("sha256:") {
|
||||
return from_nix_nixbase32_string::<D>(digest_str);
|
||||
return from_nix_nixbase32::<D>(digest_str);
|
||||
}
|
||||
if let Some(digest_str) = str.strip_prefix("sha256-") {
|
||||
return from_sri_string::<D>(digest_str);
|
||||
return from_sri::<D>(digest_str);
|
||||
}
|
||||
Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Str(str),
|
||||
|
|
@ -44,7 +44,7 @@ where
|
|||
))
|
||||
}
|
||||
|
||||
pub fn from_sri_string<'de, D>(str: &str) -> Result<[u8; 32], D::Error>
|
||||
pub fn from_sri<'de, D>(str: &str) -> Result<[u8; 32], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
|
|
@ -64,7 +64,7 @@ where
|
|||
Ok(digest)
|
||||
}
|
||||
|
||||
pub fn from_nix_nixbase32_string<'de, D>(str: &str) -> Result<[u8; 32], D::Error>
|
||||
pub fn from_nix_nixbase32<'de, D>(str: &str) -> Result<[u8; 32], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
|
|
@ -80,10 +80,10 @@ where
|
|||
Ok(digest)
|
||||
}
|
||||
|
||||
pub fn to_nix_nixbase32_string<S>(v: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
|
||||
pub fn to_nix_nixbase32<S>(v: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let string = NixHash::Sha256(*v).to_nix_nixbase32_string();
|
||||
let string = NixHash::Sha256(*v).to_nix_nixbase32();
|
||||
string.serialize(serializer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ pub struct ExportedPathInfo<'a> {
|
|||
|
||||
#[serde(
|
||||
rename = "narHash",
|
||||
serialize_with = "nixhash::serde::to_nix_nixbase32_string",
|
||||
deserialize_with = "nixhash::serde::from_nix_hash_string"
|
||||
serialize_with = "nixhash::serde::to_nix_nixbase32",
|
||||
deserialize_with = "nixhash::serde::from_nix_nixbase32_or_sri"
|
||||
)]
|
||||
pub nar_sha256: [u8; 32],
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ where
|
|||
|
||||
/// Helper function, used for the non-sha256 [CAHash::Nar] and all [CAHash::Flat].
|
||||
fn fixed_out_digest(prefix: &str, hash: &NixHash) -> [u8; 32] {
|
||||
Sha256::new_with_prefix(format!("{}:{}:", prefix, hash.to_nix_hex_string()))
|
||||
Sha256::new_with_prefix(format!("{}:{}:", prefix, hash.to_nix_lowerhex_string()))
|
||||
.finalize()
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue