1use crate::nixbase32;
2use crate::nixhash::{CAHash, NixHash};
3use crate::store_path::{Error, StorePath, STORE_DIR};
4use data_encoding::HEXLOWER;
5use sha2::{Digest, Sha256};
6use thiserror;
7
8#[derive(Debug, PartialEq, Eq, thiserror::Error)]
12pub enum BuildStorePathError {
13 #[error("Invalid Store Path: {0}")]
14 InvalidStorePath(Error),
15 #[error("References were not supported as much as requested")]
19 InvalidReference(),
20}
21
22pub fn compress_hash<const OUTPUT_SIZE: usize>(input: &[u8]) -> [u8; OUTPUT_SIZE] {
33 let mut output = [0; OUTPUT_SIZE];
34
35 for (ii, ch) in input.iter().enumerate() {
36 output[ii % OUTPUT_SIZE] ^= ch;
37 }
38
39 output
40}
41
42pub fn build_text_path<'a, S, SP, I, C>(
47 name: &'a str,
48 content: C,
49 references: I,
50) -> Result<StorePath<SP>, BuildStorePathError>
51where
52 S: AsRef<str>,
53 SP: AsRef<str> + std::convert::From<&'a str>,
54 I: IntoIterator<Item = S>,
55 C: AsRef<[u8]>,
56{
57 let content_digest = Sha256::new_with_prefix(content).finalize().into();
59
60 build_ca_path(name, &CAHash::Text(content_digest), references, false)
61}
62
63pub fn build_ca_path<'a, S, SP, I>(
65 name: &'a str,
66 ca_hash: &CAHash,
67 references: I,
68 self_reference: bool,
69) -> Result<StorePath<SP>, BuildStorePathError>
70where
71 S: AsRef<str>,
72 SP: AsRef<str> + std::convert::From<&'a str>,
73 I: IntoIterator<Item = S>,
74{
75 if self_reference && matches!(ca_hash, CAHash::Nar(NixHash::Sha256(_))) {
77 return Err(BuildStorePathError::InvalidReference());
78 }
79
80 fn fixed_out_digest(prefix: &str, hash: &NixHash) -> [u8; 32] {
82 Sha256::new_with_prefix(format!("{}:{}:", prefix, hash.to_nix_hex_string()))
83 .finalize()
84 .into()
85 }
86
87 let (ty, inner_digest) = match &ca_hash {
88 CAHash::Text(ref digest) => (make_references_string("text", references, false), *digest),
89 CAHash::Nar(NixHash::Sha256(ref digest)) => (
90 make_references_string("source", references, self_reference),
91 *digest,
92 ),
93
94 CAHash::Nar(ref hash) => {
96 if references.into_iter().next().is_some() {
97 return Err(BuildStorePathError::InvalidReference());
98 }
99
100 (
101 "output:out".to_string(),
102 fixed_out_digest("fixed:out:r", hash),
103 )
104 }
105 CAHash::Flat(ref hash) => {
107 if references.into_iter().next().is_some() {
108 return Err(BuildStorePathError::InvalidReference());
109 }
110
111 (
112 "output:out".to_string(),
113 fixed_out_digest("fixed:out", hash),
114 )
115 }
116 };
117
118 build_store_path_from_fingerprint_parts(&ty, &inner_digest, name)
119 .map_err(BuildStorePathError::InvalidStorePath)
120}
121
122pub fn build_output_path<'a, SP>(
127 drv_sha256: &[u8; 32],
128 output_name: &str,
129 output_path_name: &'a str,
130) -> Result<StorePath<SP>, Error>
131where
132 SP: AsRef<str> + std::convert::From<&'a str>,
133{
134 build_store_path_from_fingerprint_parts(
135 &(String::from("output:") + output_name),
136 drv_sha256,
137 output_path_name,
138 )
139}
140
141fn build_store_path_from_fingerprint_parts<'a, SP>(
152 ty: &str,
153 inner_digest: &[u8; 32],
154 name: &'a str,
155) -> Result<StorePath<SP>, Error>
156where
157 SP: AsRef<str> + std::convert::From<&'a str>,
158{
159 let fingerprint = format!(
160 "{ty}:sha256:{}:{STORE_DIR}:{name}",
161 HEXLOWER.encode(inner_digest)
162 );
163 StorePath::from_name_and_digest_fixed(
165 name,
166 compress_hash(&Sha256::new_with_prefix(fingerprint).finalize()),
167 )
168}
169
170fn make_references_string<S: AsRef<str>, I: IntoIterator<Item = S>>(
181 ty: &str,
182 references: I,
183 self_ref: bool,
184) -> String {
185 let mut s = String::from(ty);
186
187 for reference in references {
188 s.push(':');
189 s.push_str(reference.as_ref());
190 }
191
192 if self_ref {
193 s.push_str(":self");
194 }
195
196 s
197}
198
199pub fn hash_placeholder(name: &str) -> String {
206 let digest = Sha256::new_with_prefix(format!("nix-output:{}", name)).finalize();
207
208 format!("/{}", nixbase32::encode(&digest))
209}
210
211#[cfg(test)]
212mod test {
213 use hex_literal::hex;
214
215 use super::*;
216 use crate::{
217 nixhash::{CAHash, NixHash},
218 store_path::StorePathRef,
219 };
220
221 #[test]
222 fn build_text_path_with_zero_references() {
223 let store_path: StorePathRef = build_text_path("foo", "bar", Vec::<String>::new())
229 .expect("build_store_path() should succeed");
230
231 assert_eq!(
232 store_path.to_absolute_path().as_str(),
233 "/nix/store/vxjiwkjkn7x4079qvh1jkl5pn05j2aw0-foo"
234 );
235 }
236
237 #[test]
238 fn build_text_path_with_non_zero_references() {
239 let inner: StorePathRef = build_text_path("foo", "bar", Vec::<String>::new())
245 .expect("path_with_references() should succeed");
246 let inner_path = inner.to_absolute_path();
247
248 let outer: StorePathRef = build_text_path("baz", &inner_path, vec![inner_path.as_str()])
249 .expect("path_with_references() should succeed");
250
251 assert_eq!(
252 outer.to_absolute_path().as_str(),
253 "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
254 );
255 }
256
257 #[test]
258 fn build_sha1_path() {
259 let outer: StorePathRef = build_ca_path(
260 "bar",
261 &CAHash::Nar(NixHash::Sha1(hex!(
262 "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
263 ))),
264 Vec::<String>::new(),
265 false,
266 )
267 .expect("path_with_references() should succeed");
268
269 assert_eq!(
270 outer.to_absolute_path().as_str(),
271 "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"
272 );
273 }
274
275 #[test]
276 fn build_store_path_with_non_zero_references() {
277 let outer: StorePathRef = build_ca_path(
285 "baz",
286 &CAHash::Nar(NixHash::Sha256(
287 nixbase32::decode(b"1xqkzcb3909fp07qngljr4wcdnrh1gdam1m2n29i6hhrxlmkgkv1")
288 .expect("nixbase32 should decode")
289 .try_into()
290 .expect("should have right len"),
291 )),
292 vec!["/nix/store/dxwkwjzdaq7ka55pkk252gh32bgpmql4-foo"],
293 false,
294 )
295 .expect("path_with_references() should succeed");
296
297 assert_eq!(
298 outer.to_absolute_path().as_str(),
299 "/nix/store/s89y431zzhmdn3k8r96rvakryddkpv2v-baz"
300 );
301 }
302}