1use crate::nixbase32;
2use crate::nixhash::{CAHash, NixHash};
3use crate::store_path::{Error, STORE_DIR, StorePath, StorePathRef};
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, SP, C>(
47 name: &'a str,
48 content: C,
49 references: impl IntoIterator<Item = StorePathRef<'a>>,
50) -> Result<StorePath<SP>, BuildStorePathError>
51where
52 SP: AsRef<str> + std::convert::From<&'a str>,
53 C: AsRef<[u8]>,
54{
55 let content_digest = Sha256::digest(content.as_ref()).into();
57
58 build_ca_path(name, &CAHash::Text(content_digest), references, false)
59}
60
61pub fn build_ca_path<'a, SP>(
63 name: &'a str,
64 ca_hash: &CAHash,
65 references: impl IntoIterator<Item = StorePathRef<'a>>,
66 self_reference: bool,
67) -> Result<StorePath<SP>, BuildStorePathError>
68where
69 SP: AsRef<str> + std::convert::From<&'a str>,
70{
71 if self_reference && matches!(ca_hash, CAHash::Nar(NixHash::Sha256(_))) {
73 return Err(BuildStorePathError::InvalidReference());
74 }
75
76 fn fixed_out_digest(prefix: &str, hash: &NixHash) -> [u8; 32] {
78 sha256!("{}:{}:", prefix, hash.to_nix_lowerhex_string())
79 }
80
81 let (ty, inner_digest) = match &ca_hash {
82 CAHash::Text(digest) => (make_references_string("text", references, false), *digest),
83 CAHash::Nar(NixHash::Sha256(digest)) => (
84 make_references_string("source", references, self_reference),
85 *digest,
86 ),
87
88 CAHash::Nar(hash) => {
90 if references.into_iter().next().is_some() {
91 return Err(BuildStorePathError::InvalidReference());
92 }
93
94 (
95 "output:out".to_string(),
96 fixed_out_digest("fixed:out:r", hash),
97 )
98 }
99 CAHash::Flat(hash) => {
101 if references.into_iter().next().is_some() {
102 return Err(BuildStorePathError::InvalidReference());
103 }
104
105 (
106 "output:out".to_string(),
107 fixed_out_digest("fixed:out", hash),
108 )
109 }
110 };
111
112 build_store_path_from_fingerprint_parts(&ty, &inner_digest, name)
113 .map_err(BuildStorePathError::InvalidStorePath)
114}
115
116pub fn build_output_path<'a, SP>(
121 drv_sha256: &[u8; 32],
122 output_name: &str,
123 output_path_name: &'a str,
124) -> Result<StorePath<SP>, Error>
125where
126 SP: AsRef<str> + std::convert::From<&'a str>,
127{
128 build_store_path_from_fingerprint_parts(
129 &(String::from("output:") + output_name),
130 drv_sha256,
131 output_path_name,
132 )
133}
134
135fn build_store_path_from_fingerprint_parts<'a, SP>(
146 ty: &str,
147 inner_digest: &[u8; 32],
148 name: &'a str,
149) -> Result<StorePath<SP>, Error>
150where
151 SP: AsRef<str> + std::convert::From<&'a str>,
152{
153 let fingerprint_hash = sha256!(
154 "{ty}:sha256:{}:{STORE_DIR}:{name}",
155 HEXLOWER.encode(inner_digest)
156 );
157 StorePath::from_name_and_digest_fixed(name, compress_hash(&fingerprint_hash))
159}
160
161fn make_references_string<'a>(
172 ty: &str,
173 references: impl IntoIterator<Item = StorePathRef<'a>>,
174 self_ref: bool,
175) -> String {
176 let mut s = String::from(ty);
177 use std::fmt::Write;
178
179 for reference in references {
180 s.push(':');
181 write!(&mut s, "{}", reference.as_absolute_path_fmt()).unwrap();
182 }
183
184 if self_ref {
185 s.push_str(":self");
186 }
187
188 s
189}
190
191pub fn hash_placeholder(name: &str) -> String {
198 format!("/{}", nixbase32::encode(&sha256!("nix-output:{name}")))
199}
200
201#[cfg(test)]
202mod test {
203 use hex_literal::hex;
204
205 use super::*;
206 use crate::{
207 nixhash::{CAHash, NixHash},
208 store_path::StorePathRef,
209 };
210
211 #[test]
212 fn build_text_path_with_zero_references() {
213 let store_path: StorePathRef =
219 build_text_path("foo", "bar", []).expect("build_store_path() should succeed");
220
221 assert_eq!(
222 store_path.to_absolute_path().as_str(),
223 "/nix/store/vxjiwkjkn7x4079qvh1jkl5pn05j2aw0-foo"
224 );
225 }
226
227 #[test]
228 fn build_text_path_with_non_zero_references() {
229 let inner: StorePathRef =
235 build_text_path("foo", "bar", []).expect("path_with_references() should succeed");
236
237 let outer: StorePathRef = build_text_path("baz", inner.to_absolute_path(), [inner])
238 .expect("path_with_references() should succeed");
239
240 assert_eq!(
241 outer.to_absolute_path().as_str(),
242 "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
243 );
244 }
245
246 #[test]
247 fn build_sha1_path() {
248 let outer: StorePathRef = build_ca_path(
249 "bar",
250 &CAHash::Nar(NixHash::Sha1(hex!(
251 "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
252 ))),
253 [],
254 false,
255 )
256 .expect("path_with_references() should succeed");
257
258 assert_eq!(
259 outer.to_absolute_path().as_str(),
260 "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"
261 );
262 }
263
264 #[test]
265 fn build_store_path_with_non_zero_references() {
266 let outer: StorePathRef = build_ca_path(
274 "baz",
275 &CAHash::Nar(NixHash::Sha256(
276 nixbase32::decode(b"1xqkzcb3909fp07qngljr4wcdnrh1gdam1m2n29i6hhrxlmkgkv1")
277 .expect("nixbase32 should decode")
278 .try_into()
279 .expect("should have right len"),
280 )),
281 [
282 StorePathRef::from_bytes(b"dxwkwjzdaq7ka55pkk252gh32bgpmql4-foo")
283 .expect("to parse"),
284 ],
285 false,
286 )
287 .expect("path_with_references() should succeed");
288
289 assert_eq!(
290 outer.to_absolute_path().as_str(),
291 "/nix/store/s89y431zzhmdn3k8r96rvakryddkpv2v-baz"
292 );
293 }
294}