1use nom::bytes::streaming::tag;
7use nom::character::streaming::char as nomchar;
8use nom::combinator::{all_consuming, consumed, map_res};
9use nom::multi::{separated_list0, separated_list1};
10use nom::sequence::{delimited, preceded, separated_pair, terminated};
11use nom::Parser;
12use std::collections::{btree_map, BTreeMap, BTreeSet};
13use thiserror;
14
15use crate::derivation::parse_error::{into_nomerror, ErrorKind, NomError, NomResult};
16use crate::derivation::{write, CAHash, Derivation, Output};
17use crate::store_path::{self, StorePath};
18use crate::{aterm, nixhash};
19
20#[derive(Debug, thiserror::Error)]
21pub enum Error<I> {
22 #[error("parsing error: {0}")]
23 Parser(#[from] NomError<I>),
24 #[error("premature EOF")]
25 Incomplete,
26 #[error("validation error: {0}")]
27 Validation(super::DerivationError),
28}
29
30impl From<Error<&[u8]>> for Error<Vec<u8>> {
32 fn from(value: Error<&[u8]>) -> Self {
33 match value {
34 Error::Parser(nom_error) => Error::Parser(NomError {
35 input: nom_error.input.to_vec(),
36 code: nom_error.code,
37 }),
38 Error::Incomplete => Error::Incomplete,
39 Error::Validation(e) => Error::Validation(e),
40 }
41 }
42}
43
44pub(crate) fn parse(i: &[u8]) -> Result<Derivation, Error<&[u8]>> {
45 match all_consuming(parse_derivation).parse(i) {
46 Ok((rest, derivation)) => {
47 debug_assert!(rest.is_empty());
49
50 derivation.validate(true).map_err(Error::Validation)?;
52
53 Ok(derivation)
54 }
55 Err(nom::Err::Incomplete(_)) => Err(Error::Incomplete),
56 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(e.into()),
57 }
58}
59
60#[allow(dead_code)]
65pub fn parse_streaming(i: &[u8]) -> (Result<Derivation, Error<&[u8]>>, &[u8]) {
66 match consumed(parse_derivation).parse(i) {
67 Ok((_, (rest, derivation))) => {
68 if let Err(e) = derivation.validate(true).map_err(Error::Validation) {
70 return (Err(e), i);
71 }
72
73 (Ok(derivation), rest)
74 }
75 Err(nom::Err::Incomplete(_)) => (Err(Error::Incomplete), i),
76 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => (Err(e.into()), i),
77 }
78}
79
80fn from_algo_and_mode_and_digest<B: AsRef<[u8]>>(
83 algo_and_mode: &str,
84 digest: B,
85) -> crate::nixhash::NixHashResult<CAHash> {
86 Ok(match algo_and_mode.strip_prefix("r:") {
87 Some(algo) => nixhash::CAHash::Nar(nixhash::from_algo_and_digest(
88 algo.try_into()?,
89 digest.as_ref(),
90 )?),
91 None => nixhash::CAHash::Flat(nixhash::from_algo_and_digest(
92 algo_and_mode.try_into()?,
93 digest.as_ref(),
94 )?),
95 })
96}
97
98fn parse_output(i: &[u8]) -> NomResult<&[u8], (String, Output)> {
102 delimited(
103 nomchar('('),
104 map_res(
105 |i| {
106 (
107 terminated(aterm::parse_string_field, nomchar(',')),
108 terminated(aterm::parse_string_field, nomchar(',')),
109 terminated(aterm::parse_string_field, nomchar(',')),
110 aterm::parse_bytes_field,
111 )
112 .parse(i)
113 .map_err(into_nomerror)
114 },
115 |(output_name, output_path, algo_and_mode, encoded_digest)| {
116 let ca_hash_res = {
118 if algo_and_mode.is_empty() && encoded_digest.is_empty() {
119 None
120 } else {
121 match data_encoding::HEXLOWER.decode(&encoded_digest) {
122 Ok(digest) => {
123 Some(from_algo_and_mode_and_digest(&algo_and_mode, digest))
124 }
125 Err(e) => Some(Err(nixhash::Error::InvalidBase64Encoding(e))),
126 }
127 }
128 }
129 .transpose();
130
131 match ca_hash_res {
132 Ok(hash_with_mode) => Ok((
133 output_name,
134 Output {
135 path: if output_path.is_empty() {
138 None
139 } else {
140 Some(string_to_store_path(i, &output_path)?)
141 },
142 ca_hash: hash_with_mode,
143 },
144 )),
145 Err(e) => Err(nom::Err::Failure(NomError {
146 input: i,
147 code: ErrorKind::NixHashError(e),
148 })),
149 }
150 },
151 ),
152 nomchar(')'),
153 )
154 .parse(i)
155}
156
157fn parse_outputs(i: &[u8]) -> NomResult<&[u8], BTreeMap<String, Output>> {
163 let res = delimited(
164 nomchar('['),
165 separated_list1(tag(","), parse_output),
166 nomchar(']'),
167 )
168 .parse(i);
169
170 match res {
171 Ok((rst, outputs_lst)) => {
172 let mut outputs = BTreeMap::default();
173 for (output_name, output) in outputs_lst.into_iter() {
174 if outputs.contains_key(&output_name) {
175 return Err(nom::Err::Failure(NomError {
176 input: i,
177 code: ErrorKind::DuplicateMapKey(output_name.to_string()),
178 }));
179 }
180 outputs.insert(output_name, output);
181 }
182 Ok((rst, outputs))
183 }
184 Err(e) => Err(e),
186 }
187}
188
189fn parse_input_derivations(
190 i: &[u8],
191) -> NomResult<&[u8], BTreeMap<StorePath<String>, BTreeSet<String>>> {
192 let (i, input_derivations_list) = parse_kv(aterm::parse_string_list)(i)?;
193
194 let mut input_derivations: BTreeMap<StorePath<String>, BTreeSet<_>> = BTreeMap::new();
196
197 for (input_derivation, output_names) in input_derivations_list {
198 let mut new_output_names = BTreeSet::new();
199 for output_name in output_names.into_iter() {
200 if new_output_names.contains(&output_name) {
201 return Err(nom::Err::Failure(NomError {
202 input: i,
203 code: ErrorKind::DuplicateInputDerivationOutputName(
204 input_derivation.to_string(),
205 output_name.to_string(),
206 ),
207 }));
208 }
209 new_output_names.insert(output_name);
210 }
211
212 let input_derivation = string_to_store_path(i, input_derivation.as_str())?;
213
214 input_derivations.insert(input_derivation, new_output_names);
215 }
216
217 Ok((i, input_derivations))
218}
219
220fn parse_input_sources(i: &[u8]) -> NomResult<&[u8], BTreeSet<StorePath<String>>> {
221 let (i, input_sources_lst) = aterm::parse_string_list(i).map_err(into_nomerror)?;
222
223 let mut input_sources: BTreeSet<_> = BTreeSet::new();
224 for input_source in input_sources_lst.into_iter() {
225 let input_source = string_to_store_path(i, input_source.as_str())?;
226 if input_sources.contains(&input_source) {
227 return Err(nom::Err::Failure(NomError {
228 input: i,
229 code: ErrorKind::DuplicateInputSource(input_source.to_owned()),
230 }));
231 } else {
232 input_sources.insert(input_source);
233 }
234 }
235
236 Ok((i, input_sources))
237}
238
239fn string_to_store_path<'a, 'i, S>(
240 i: &'i [u8],
241 path_str: &'a str,
242) -> Result<StorePath<S>, nom::Err<NomError<&'i [u8]>>>
243where
244 S: std::clone::Clone + AsRef<str> + std::convert::From<&'a str>,
245{
246 let path =
247 StorePath::from_absolute_path(path_str.as_bytes()).map_err(|e: store_path::Error| {
248 nom::Err::Failure(NomError {
249 input: i,
250 code: e.into(),
251 })
252 })?;
253
254 #[cfg(debug_assertions)]
255 assert_eq!(path_str, path.to_absolute_path());
256
257 Ok(path)
258}
259
260pub fn parse_derivation(i: &[u8]) -> NomResult<&[u8], Derivation> {
261 use nom::Parser;
262 preceded(
263 tag(write::DERIVATION_PREFIX),
264 delimited(
265 nomchar('('),
267 (
270 terminated(parse_outputs, nomchar(',')),
272 terminated(parse_input_derivations, nomchar(',')),
274 terminated(parse_input_sources, nomchar(',')),
276 |i| {
278 terminated(aterm::parse_string_field, nomchar(','))
279 .parse(i)
280 .map_err(into_nomerror)
281 },
282 |i| {
284 terminated(aterm::parse_string_field, nomchar(','))
285 .parse(i)
286 .map_err(into_nomerror)
287 },
288 |i| {
290 terminated(aterm::parse_string_list, nomchar(','))
291 .parse(i)
292 .map_err(into_nomerror)
293 },
294 parse_kv(aterm::parse_bytes_field),
296 ),
297 nomchar(')'),
298 )
299 .map(
300 |(
301 outputs,
302 input_derivations,
303 input_sources,
304 system,
305 builder,
306 arguments,
307 environment,
308 )| {
309 Derivation {
310 arguments,
311 builder,
312 environment,
313 input_derivations,
314 input_sources,
315 outputs,
316 system,
317 }
318 },
319 ),
320 )
321 .parse(i)
322}
323
324pub(crate) fn parse_kv<'a, V, VF>(
330 vf: VF,
331) -> impl FnMut(&'a [u8]) -> NomResult<&'a [u8], BTreeMap<String, V>> + 'static
332where
333 VF: FnMut(&'a [u8]) -> nom::IResult<&'a [u8], V, nom::error::Error<&'a [u8]>> + Clone + 'static,
334{
335 move |i|
336 delimited(
338 nomchar('['),
339 |ii| {
340 let res = separated_list0(
341 nomchar(','),
342 delimited(
344 nomchar('('),
345 separated_pair(
346 aterm::parse_string_field,
347 nomchar(','),
348 vf.clone(),
349 ),
350 nomchar(')'),
351 ),
352 ).parse(ii).map_err(into_nomerror);
353
354 match res {
355 Ok((rest, pairs)) => {
356 let mut kvs: BTreeMap<String, V> = BTreeMap::new();
357 for (k, v) in pairs.into_iter() {
358 match kvs.entry(k) {
361 btree_map::Entry::Vacant(e) => { e.insert(v); },
362 btree_map::Entry::Occupied(e) => {
363 return Err(nom::Err::Failure(NomError {
364 input: i,
365 code: ErrorKind::DuplicateMapKey(e.key().clone()),
366 }));
367 }
368 }
369 }
370 Ok((rest, kvs))
371 }
372 Err(e) => Err(e),
373 }
374 },
375 nomchar(']'),
376 ).parse(i)
377}
378
379#[cfg(test)]
380mod tests {
381 use crate::store_path::StorePathRef;
382 use std::collections::{BTreeMap, BTreeSet};
383 use std::sync::LazyLock;
384
385 use crate::{
386 derivation::{
387 parse_error::ErrorKind, parser::from_algo_and_mode_and_digest, CAHash, NixHash, Output,
388 },
389 store_path::StorePath,
390 };
391 use bstr::{BString, ByteSlice};
392 use hex_literal::hex;
393 use rstest::rstest;
394
395 const DIGEST_SHA256: [u8; 32] =
396 hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
397
398 static NIXHASH_SHA256: NixHash = NixHash::Sha256(DIGEST_SHA256);
399 static EXP_MULTI_OUTPUTS: LazyLock<BTreeMap<String, Output>> = LazyLock::new(|| {
400 let mut b = BTreeMap::new();
401 b.insert(
402 "lib".to_string(),
403 Output {
404 path: Some(
405 StorePath::from_bytes(b"2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib")
406 .unwrap(),
407 ),
408 ca_hash: None,
409 },
410 );
411 b.insert(
412 "out".to_string(),
413 Output {
414 path: Some(
415 StorePath::from_bytes(
416 b"55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".as_bytes(),
417 )
418 .unwrap(),
419 ),
420 ca_hash: None,
421 },
422 );
423 b
424 });
425
426 static EXP_AB_MAP: LazyLock<BTreeMap<String, BString>> = LazyLock::new(|| {
427 let mut b = BTreeMap::new();
428 b.insert("a".to_string(), b"1".into());
429 b.insert("b".to_string(), b"2".into());
430 b
431 });
432
433 static EXP_INPUT_DERIVATIONS_SIMPLE: LazyLock<BTreeMap<StorePath<String>, BTreeSet<String>>> =
434 LazyLock::new(|| {
435 let mut b = BTreeMap::new();
436 b.insert(
437 StorePath::from_bytes(b"8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv")
438 .unwrap(),
439 {
440 let mut output_names = BTreeSet::new();
441 output_names.insert("out".to_string());
442 output_names
443 },
444 );
445 b.insert(
446 StorePath::from_bytes(b"p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv")
447 .unwrap(),
448 {
449 let mut output_names = BTreeSet::new();
450 output_names.insert("out".to_string());
451 output_names.insert("lib".to_string());
452 output_names
453 },
454 );
455 b
456 });
457
458 static EXP_INPUT_DERIVATIONS_SIMPLE_ATERM: LazyLock<String> = LazyLock::new(|| {
459 format!(
460 "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"lib\"])]",
461 "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
462 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
463 )
464 });
465
466 static EXP_INPUT_SOURCES_SIMPLE: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
467 let mut b = BTreeSet::new();
468 b.insert("/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".to_string());
469 b.insert("/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib".to_string());
470 b
471 });
472
473 #[rstest]
475 #[case::empty(b"[]", &BTreeMap::new(), b"")]
476 #[case::simple(b"[(\"a\",\"1\"),(\"b\",\"2\")]", &EXP_AB_MAP, b"")]
477 fn parse_kv(
478 #[case] input: &'static [u8],
479 #[case] expected: &BTreeMap<String, BString>,
480 #[case] exp_rest: &[u8],
481 ) {
482 let (rest, parsed) =
483 super::parse_kv(crate::aterm::parse_bytes_field)(input).expect("must parse");
484 assert_eq!(exp_rest, rest, "expected remainder");
485 assert_eq!(*expected, parsed);
486 }
487
488 #[rstest]
489 #[case::incomplete_empty(b"[")]
490 #[case::incomplete_simple(b"[(\"a\",\"1\")")]
491 #[case::incomplete_complicated_escape(b"[(\"a")]
492 #[case::incomplete_complicated_sep(b"[(\"a\",")]
493 #[case::incomplete_complicated_multi_escape(b"[(\"a\",\"")]
494 #[case::incomplete_complicated_multi_outer_sep(b"[(\"a\",\"b\"),")]
495 fn parse_kv_incomplete(#[case] input: &'static [u8]) {
496 assert!(matches!(
497 super::parse_kv(crate::aterm::parse_bytes_field)(input),
498 Err(nom::Err::Incomplete(_))
499 ));
500 }
501
502 #[test]
504 fn parse_kv_fail_dup_keys() {
505 let input: &'static [u8] = b"[(\"a\",\"1\"),(\"a\",\"2\")]";
506 let e = super::parse_kv(crate::aterm::parse_bytes_field)(input).expect_err("must fail");
507
508 match e {
509 nom::Err::Failure(e) => {
510 assert_eq!(ErrorKind::DuplicateMapKey("a".to_string()), e.code);
511 }
512 _ => panic!("unexpected error"),
513 }
514 }
515
516 #[rstest]
518 #[case::empty(b"[]", &BTreeMap::new())]
519 #[case::simple(EXP_INPUT_DERIVATIONS_SIMPLE_ATERM.as_bytes(), &EXP_INPUT_DERIVATIONS_SIMPLE)]
520 fn parse_input_derivations(
521 #[case] input: &'static [u8],
522 #[case] expected: &BTreeMap<StorePath<String>, BTreeSet<String>>,
523 ) {
524 let (rest, parsed) = super::parse_input_derivations(input).expect("must parse");
525
526 assert_eq!(expected, &parsed, "parsed mismatch");
527 assert!(rest.is_empty(), "rest must be empty");
528 }
529
530 #[test]
532 fn parse_input_derivations_fail_dup_output_names() {
533 let input_str = format!(
534 "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"out\"])]",
535 "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
536 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
537 );
538 let e = super::parse_input_derivations(input_str.as_bytes()).expect_err("must fail");
539
540 match e {
541 nom::Err::Failure(e) => {
542 assert_eq!(
543 ErrorKind::DuplicateInputDerivationOutputName(
544 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv".to_string(),
545 "out".to_string()
546 ),
547 e.code
548 );
549 }
550 _ => panic!("unexpected error"),
551 }
552 }
553
554 #[rstest]
556 #[case::empty(b"[]", &BTreeSet::new())]
557 #[case::simple(b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out\",\"/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib\"]", &EXP_INPUT_SOURCES_SIMPLE)]
558 fn parse_input_sources(#[case] input: &'static [u8], #[case] expected: &BTreeSet<String>) {
559 let (rest, parsed) = super::parse_input_sources(input).expect("must parse");
560
561 assert_eq!(
562 expected,
563 &parsed
564 .iter()
565 .map(StorePath::to_absolute_path)
566 .collect::<BTreeSet<_>>(),
567 "parsed mismatch"
568 );
569 assert!(rest.is_empty(), "rest must be empty");
570 }
571
572 #[test]
574 fn parse_input_sources_fail_dup_keys() {
575 let input: &'static [u8] = b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\",\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\"]";
576 let e = super::parse_input_sources(input).expect_err("must fail");
577
578 match e {
579 nom::Err::Failure(e) => {
580 assert_eq!(
581 ErrorKind::DuplicateInputSource(
582 StorePathRef::from_absolute_path(
583 "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo".as_bytes()
584 )
585 .unwrap()
586 .to_owned()
587 ),
588 e.code
589 );
590 }
591 _ => panic!("unexpected error"),
592 }
593 }
594
595 #[rstest]
596 #[case::simple(
597 br#"("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")"#,
598 ("out".to_string(), Output {
599 path: Some(
600 StorePathRef::from_absolute_path("/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo".as_bytes()).unwrap().to_owned()),
601 ca_hash: None
602 })
603 )]
604 #[case::fod(
605 br#"("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")"#,
606 ("out".to_string(), Output {
607 path: Some(
608 StorePathRef::from_absolute_path(
609 "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".as_bytes()).unwrap().to_owned()),
610 ca_hash: Some(from_algo_and_mode_and_digest("r:sha256",
611 data_encoding::HEXLOWER.decode(b"08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba").unwrap() ).unwrap()),
612 })
613 )]
614 fn parse_output(#[case] input: &[u8], #[case] expected: (String, Output)) {
615 let (rest, parsed) = super::parse_output(input).expect("must parse");
616 assert!(rest.is_empty());
617 assert_eq!(expected, parsed);
618 }
619
620 #[rstest]
621 #[case::multi_out(
622 br#"[("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")]"#,
623 &EXP_MULTI_OUTPUTS
624 )]
625 fn parse_outputs(#[case] input: &[u8], #[case] expected: &BTreeMap<String, Output>) {
626 let (rest, parsed) = super::parse_outputs(input).expect("must parse");
627 assert!(rest.is_empty());
628 assert_eq!(*expected, parsed);
629 }
630
631 #[rstest]
632 #[case::sha256_flat("sha256", &DIGEST_SHA256, CAHash::Flat(NIXHASH_SHA256.clone()))]
633 #[case::sha256_recursive("r:sha256", &DIGEST_SHA256, CAHash::Nar(NIXHASH_SHA256.clone()))]
634 fn test_from_algo_and_mode_and_digest(
635 #[case] algo_and_mode: &str,
636 #[case] digest: &[u8],
637 #[case] expected: CAHash,
638 ) {
639 assert_eq!(
640 expected,
641 from_algo_and_mode_and_digest(algo_and_mode, digest).unwrap()
642 );
643 }
644
645 #[test]
646 fn from_algo_and_mode_and_digest_failure() {
647 assert!(from_algo_and_mode_and_digest("r:sha256", []).is_err());
648 assert!(from_algo_and_mode_and_digest("ha256", DIGEST_SHA256).is_err());
649 }
650}