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