1use std::fmt::Display;
2
3use super::{STORE_DIR, StorePathRef};
4use crate::derivation::OutputName;
5use crate::nixhash::{HashAlgo, NixHash, Sha256};
6use crate::store_path::StorePathName;
7use crate::{format_sha256, nixbase32};
8
9pub fn compress_hash<const OUTPUT_SIZE: usize>(input: &[u8]) -> [u8; OUTPUT_SIZE] {
20 let mut output = [0; OUTPUT_SIZE];
21
22 for (ii, ch) in input.iter().enumerate() {
23 output[ii % OUTPUT_SIZE] ^= ch;
24 }
25
26 output
27}
28
29pub fn build_text_path<'r, 'name>(
33 name: &'name StorePathName,
34 content: impl AsRef<[u8]>,
35 references: impl IntoIterator<Item = StorePathRef<'r>> + 'r,
36) -> StorePathRef<'name> {
37 build_text_path_from_content_digest(name, Sha256::digest_bytes(content.as_ref()), references)
38}
39
40pub fn build_text_path_from_content_digest<'r, 'n>(
44 name: &'n StorePathName,
45 content_digest: impl Into<Sha256>,
46 references: impl IntoIterator<Item = StorePathRef<'r>> + 'r,
47) -> StorePathRef<'n> {
48 let ty = format_references("text", references, false);
51
52 build_store_path_from_fingerprint_parts(ty, &content_digest.into(), name)
53}
54
55pub fn build_ca_path<'r, 'n>(
57 name: &'n StorePathName,
58 is_recursive: bool,
59 hash: &NixHash,
60 references: impl IntoIterator<Item = StorePathRef<'r>> + 'r,
61 has_self_ref: bool,
62) -> StorePathRef<'n> {
63 let inner_digest = if let NixHash::Sha256(digest) = hash
64 && is_recursive
65 {
66 Sha256::new(*digest)
67 } else {
68 fod_digest(is_recursive, hash, None)
69 };
70
71 if hash.algo() == HashAlgo::Sha256 && is_recursive {
72 build_store_path_from_fingerprint_parts(
73 format_references("source", references, has_self_ref),
74 &inner_digest,
75 name,
76 )
77 } else {
78 build_store_path_from_fingerprint_parts("output:out", &inner_digest, name)
81 }
82}
83
84pub fn build_output_path<'n>(
89 name: &'n StorePathName,
90 hash_derivation_modulo: &Sha256,
91 output_name: &OutputName,
92) -> StorePathRef<'n> {
93 build_store_path_from_fingerprint_parts(
94 format_args!("output:{output_name}"),
95 hash_derivation_modulo,
96 name,
97 )
98}
99
100fn build_store_path_from_fingerprint_parts<'n>(
111 ty: impl Display,
112 inner_digest: &Sha256,
113 name: &'n StorePathName,
114) -> StorePathRef<'n> {
115 let fingerprint_hash = format_sha256!("{ty}:sha256:{inner_digest:x}:{STORE_DIR}:{name}");
116 StorePathRef::from_name_and_digest_fixed(name, compress_hash(&fingerprint_hash))
118}
119
120pub(crate) fn fod_digest(
121 is_recursive: bool,
122 hash: &NixHash,
123 out_output_path: Option<StorePathRef<'_>>,
124) -> Sha256 {
125 let absolute_sp_optional = std::fmt::from_fn(|f| {
126 if let Some(sp) = &out_output_path {
127 write!(f, "{}", sp.as_absolute_path_fmt())?
128 }
129 Ok(())
130 });
131
132 if is_recursive {
133 format_sha256!(
134 "fixed:out:r:{}:{}",
135 hash.as_nix_lowerhex_string_fmt(),
136 absolute_sp_optional
137 )
138 } else {
139 format_sha256!(
140 "fixed:out:{}:{}",
141 hash.as_nix_lowerhex_string_fmt(),
142 absolute_sp_optional
143 )
144 }
145}
146
147fn format_references<'a, R>(ty: &'a str, references: R, has_self_ref: bool) -> impl Display + 'a
150where
151 R: IntoIterator<Item = StorePathRef<'a>> + 'a,
152{
153 struct ReferencesFormatter<'a, R> {
154 ty: &'a str,
155 references: std::cell::RefCell<R>,
156 has_self_ref: bool,
157 }
158
159 impl<'a, R> Display for ReferencesFormatter<'a, R>
160 where
161 R: Iterator<Item = StorePathRef<'a>> + 'a,
162 {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 write!(f, "{}", self.ty)?;
165 while let Some(reference) = self.references.borrow_mut().next() {
166 write!(f, ":{}", reference.as_absolute_path_fmt()).unwrap();
167 }
168
169 if self.has_self_ref {
170 write!(f, ":self")?;
171 }
172
173 Ok(())
174 }
175 }
176
177 ReferencesFormatter {
178 ty,
179 references: std::cell::RefCell::new(references.into_iter()),
180 has_self_ref,
181 }
182}
183
184pub fn hash_placeholder(name: &str) -> String {
191 format!(
192 "/{}",
193 nixbase32::encode(&format_sha256!("nix-output:{name}"))
194 )
195}
196
197#[cfg(test)]
198mod test {
199 use hex_literal::hex;
200
201 use super::*;
202 use crate::{nixhash::NixHash, store_path::StorePathRef};
203
204 fn into_name(name: &'static str) -> &'static StorePathName {
205 StorePathName::from_str(name).expect("valid name {name}")
206 }
207
208 #[test]
209 fn build_text_path_with_zero_references() {
210 let store_path: StorePathRef = build_text_path(into_name("foo"), "bar", []);
216 assert_eq!(
217 store_path.to_absolute_path().as_str(),
218 "/nix/store/vxjiwkjkn7x4079qvh1jkl5pn05j2aw0-foo"
219 );
220 }
221
222 #[test]
223 fn build_text_path_with_non_zero_references() {
224 let inner: StorePathRef = build_text_path(into_name("foo"), "bar", []);
230 let outer: StorePathRef =
231 build_text_path(into_name("baz"), inner.to_absolute_path(), [inner]);
232 assert_eq!(
233 outer.to_absolute_path().as_str(),
234 "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
235 );
236 }
237
238 #[test]
239 fn build_sha1_path() {
240 let outer: StorePathRef = build_ca_path(
241 into_name("bar"),
242 true,
243 &NixHash::Sha1(hex!("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")),
244 [],
245 false,
246 );
247 assert_eq!(
248 outer.to_absolute_path().as_str(),
249 "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"
250 );
251 }
252
253 #[test]
254 fn build_store_path_with_non_zero_references() {
255 let outer: StorePathRef = build_ca_path(
263 into_name("baz"),
264 true,
265 &NixHash::Sha256(
266 nixbase32::decode(b"1xqkzcb3909fp07qngljr4wcdnrh1gdam1m2n29i6hhrxlmkgkv1")
267 .expect("nixbase32 should decode")
268 .try_into()
269 .expect("should have right len"),
270 ),
271 [
272 StorePathRef::from_bytes(b"dxwkwjzdaq7ka55pkk252gh32bgpmql4-foo")
273 .expect("to parse"),
274 ],
275 false,
276 );
277 assert_eq!(
278 outer.to_absolute_path().as_str(),
279 "/nix/store/s89y431zzhmdn3k8r96rvakryddkpv2v-baz"
280 );
281 }
282}