1use crate::nixbase32;
2use crate::nixhash::{CAHash, NixHash};
3use crate::store_path::{Error, STORE_DIR, StorePath};
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::digest(content.as_ref()).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!("{}:{}:", prefix, hash.to_nix_lowerhex_string())
83 }
84
85 let (ty, inner_digest) = match &ca_hash {
86 CAHash::Text(digest) => (make_references_string("text", references, false), *digest),
87 CAHash::Nar(NixHash::Sha256(digest)) => (
88 make_references_string("source", references, self_reference),
89 *digest,
90 ),
91
92 CAHash::Nar(hash) => {
94 if references.into_iter().next().is_some() {
95 return Err(BuildStorePathError::InvalidReference());
96 }
97
98 (
99 "output:out".to_string(),
100 fixed_out_digest("fixed:out:r", hash),
101 )
102 }
103 CAHash::Flat(hash) => {
105 if references.into_iter().next().is_some() {
106 return Err(BuildStorePathError::InvalidReference());
107 }
108
109 (
110 "output:out".to_string(),
111 fixed_out_digest("fixed:out", hash),
112 )
113 }
114 };
115
116 build_store_path_from_fingerprint_parts(&ty, &inner_digest, name)
117 .map_err(BuildStorePathError::InvalidStorePath)
118}
119
120pub fn build_output_path<'a, SP>(
125 drv_sha256: &[u8; 32],
126 output_name: &str,
127 output_path_name: &'a str,
128) -> Result<StorePath<SP>, Error>
129where
130 SP: AsRef<str> + std::convert::From<&'a str>,
131{
132 build_store_path_from_fingerprint_parts(
133 &(String::from("output:") + output_name),
134 drv_sha256,
135 output_path_name,
136 )
137}
138
139fn build_store_path_from_fingerprint_parts<'a, SP>(
150 ty: &str,
151 inner_digest: &[u8; 32],
152 name: &'a str,
153) -> Result<StorePath<SP>, Error>
154where
155 SP: AsRef<str> + std::convert::From<&'a str>,
156{
157 let fingerprint_hash = sha256!(
158 "{ty}:sha256:{}:{STORE_DIR}:{name}",
159 HEXLOWER.encode(inner_digest)
160 );
161 StorePath::from_name_and_digest_fixed(name, compress_hash(&fingerprint_hash))
163}
164
165fn make_references_string<S: AsRef<str>, I: IntoIterator<Item = S>>(
176 ty: &str,
177 references: I,
178 self_ref: bool,
179) -> String {
180 let mut s = String::from(ty);
181
182 for reference in references {
183 s.push(':');
184 s.push_str(reference.as_ref());
185 }
186
187 if self_ref {
188 s.push_str(":self");
189 }
190
191 s
192}
193
194pub fn hash_placeholder(name: &str) -> String {
201 format!("/{}", nixbase32::encode(&sha256!("nix-output:{name}")))
202}
203
204#[cfg(test)]
205mod test {
206 use hex_literal::hex;
207
208 use super::*;
209 use crate::{
210 nixhash::{CAHash, NixHash},
211 store_path::StorePathRef,
212 };
213
214 #[test]
215 fn build_text_path_with_zero_references() {
216 let store_path: StorePathRef = build_text_path("foo", "bar", Vec::<String>::new())
222 .expect("build_store_path() should succeed");
223
224 assert_eq!(
225 store_path.to_absolute_path().as_str(),
226 "/nix/store/vxjiwkjkn7x4079qvh1jkl5pn05j2aw0-foo"
227 );
228 }
229
230 #[test]
231 fn build_text_path_with_non_zero_references() {
232 let inner: StorePathRef = build_text_path("foo", "bar", Vec::<String>::new())
238 .expect("path_with_references() should succeed");
239 let inner_path = inner.to_absolute_path();
240
241 let outer: StorePathRef = build_text_path("baz", &inner_path, vec![inner_path.as_str()])
242 .expect("path_with_references() should succeed");
243
244 assert_eq!(
245 outer.to_absolute_path().as_str(),
246 "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
247 );
248 }
249
250 #[test]
251 fn build_sha1_path() {
252 let outer: StorePathRef = build_ca_path(
253 "bar",
254 &CAHash::Nar(NixHash::Sha1(hex!(
255 "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
256 ))),
257 Vec::<String>::new(),
258 false,
259 )
260 .expect("path_with_references() should succeed");
261
262 assert_eq!(
263 outer.to_absolute_path().as_str(),
264 "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"
265 );
266 }
267
268 #[test]
269 fn build_store_path_with_non_zero_references() {
270 let outer: StorePathRef = build_ca_path(
278 "baz",
279 &CAHash::Nar(NixHash::Sha256(
280 nixbase32::decode(b"1xqkzcb3909fp07qngljr4wcdnrh1gdam1m2n29i6hhrxlmkgkv1")
281 .expect("nixbase32 should decode")
282 .try_into()
283 .expect("should have right len"),
284 )),
285 vec!["/nix/store/dxwkwjzdaq7ka55pkk252gh32bgpmql4-foo"],
286 false,
287 )
288 .expect("path_with_references() should succeed");
289
290 assert_eq!(
291 outer.to_absolute_path().as_str(),
292 "/nix/store/s89y431zzhmdn3k8r96rvakryddkpv2v-baz"
293 );
294 }
295}