From 6b685ec4a562693f56dee27624074b7cc38e70c8 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Wed, 22 Nov 2023 21:19:18 +0200 Subject: feat(tvix/store): add as_narinfo() for PathInfo This allows seeing a PathInfo as a nix_compat::narinfo::NarInfo<'_>. It doesn't allocate any new data, but the NarInfo<'_> view allows us to access things like signature verification, or rendering out (alternations of this) as strings. Change-Id: Id0d8d7feeb626ee02c3d8a4932f24ace77022619 Reviewed-on: https://cl.tvl.fyi/c/depot/+/10108 Reviewed-by: edef Autosubmit: flokli Tested-by: BuildkiteCI --- tvix/store/src/proto/mod.rs | 64 +++++++++++++++++++++++++++++++++- tvix/store/src/proto/tests/pathinfo.rs | 44 ++++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/tvix/store/src/proto/mod.rs b/tvix/store/src/proto/mod.rs index b6d0f35bfc56..fb8e172a6c37 100644 --- a/tvix/store/src/proto/mod.rs +++ b/tvix/store/src/proto/mod.rs @@ -3,8 +3,9 @@ use bytes::Bytes; use data_encoding::BASE64; // https://github.com/hyperium/tonic/issues/1056 use nix_compat::{ + narinfo::Flags, nixhash::{CAHash, NixHash}, - store_path, + store_path::{self, StorePathRef}, }; use thiserror::Error; use tvix_castore::proto::{self as castorepb, NamedNode, ValidateNodeError}; @@ -174,6 +175,67 @@ impl PathInfo { // return the root nix path Ok(root_nix_path) } + + /// With self and a given StorePathRef, this reconstructs a + /// [nix_compat::narinfo::NarInfo<'_>]. + /// It can be used to validate Signatures, or get back a (sparse) NarInfo + /// struct to prepare writing it out. + /// + /// This doesn't allocate any new data. + /// + /// Keep in mind this is not able to reconstruct all data present in the + /// NarInfo<'_>, as some of it is not stored at all: + /// - the `system`, `file_hash` and `file_size` fields are set to `None`. + /// - the URL is set to an empty string. + /// - Compression is set to "none" + /// + /// If you want to render it out to a string and be able to parse it back + /// in, at least URL *must* be set again. + pub fn as_narinfo<'a>( + &'a self, + store_path: store_path::StorePathRef<'a>, + ) -> Option> { + let narinfo = &self.narinfo.as_ref()?; + + Some(nix_compat::narinfo::NarInfo { + flags: Flags::empty(), + store_path, + nar_hash: narinfo.nar_sha256.to_vec().try_into().unwrap(), + nar_size: narinfo.nar_size, + references: narinfo + .reference_names + .iter() + .map(|ref_name| { + // This shouldn't pass validation + StorePathRef::from_bytes(ref_name.as_bytes()).expect("invalid reference") + }) + .collect(), + signatures: narinfo + .signatures + .iter() + .map(|sig| { + nix_compat::narinfo::Signature::new( + &sig.name, + // This shouldn't pass validation + sig.data[..].try_into().expect("invalid signature len"), + ) + }) + .collect(), + ca: narinfo + .ca + .as_ref() + .map(|ca| ca.try_into().expect("invalid ca")), + system: None, + deriver: narinfo.deriver.as_ref().map(|deriver| { + StorePathRef::from_name_and_digest(&deriver.name, &deriver.digest) + .expect("invalid deriver") + }), + url: "", + compression: Some("none"), + file_hash: None, + file_size: None, + }) + } } /// Errors that can occur when converting from a [&nar_info::Ca] to a (stricter) diff --git a/tvix/store/src/proto/tests/pathinfo.rs b/tvix/store/src/proto/tests/pathinfo.rs index 31f4790b73ee..7c1b69c2a376 100644 --- a/tvix/store/src/proto/tests/pathinfo.rs +++ b/tvix/store/src/proto/tests/pathinfo.rs @@ -3,7 +3,7 @@ use crate::tests::fixtures::*; use bytes::Bytes; use data_encoding::BASE64; use nix_compat::nixbase32; -use nix_compat::store_path::{self, StorePath}; +use nix_compat::store_path::{self, StorePath, StorePathRef}; use std::str::FromStr; use test_case::test_case; use tvix_castore::proto as castorepb; @@ -404,3 +404,45 @@ CA: fixed:sha256:086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd"# (&narinfo_parsed).into() ); } + +/// Exercise .as_narinfo() on a PathInfo and ensure important fields are preserved.. +#[test] +fn as_narinfo() { + let narinfo_parsed = nix_compat::narinfo::NarInfo::parse( + r#"StorePath: /nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz +URL: nar/1zjrhzhaizsrlsvdkqfl073vivmxcqnzkff4s50i0cdf541ary1r.nar.xz +Compression: xz +FileHash: sha256:1zjrhzhaizsrlsvdkqfl073vivmxcqnzkff4s50i0cdf541ary1r +FileSize: 1033524 +NarHash: sha256:1lvqpbk2k1sb39z8jfxixf7p7v8sj4z6mmpa44nnmff3w1y6h8lh +NarSize: 1033416 +References: +Deriver: dyivpmlaq2km6c11i0s6bi6mbsx0ylqf-hello-2.12.1.tar.gz.drv +Sig: cache.nixos.org-1:ywnIG629nQZQhEr6/HLDrLT/mUEp5J1LC6NmWSlJRWL/nM7oGItJQUYWGLvYGhSQvHrhIuvMpjNmBNh/WWqCDg== +CA: fixed:sha256:086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd"# + ).expect("must parse"); + + let path_info: PathInfo = (&narinfo_parsed).into(); + + let mut narinfo_returned = path_info + .as_narinfo( + StorePathRef::from_bytes(b"pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz") + .expect("invalid storepath"), + ) + .expect("must be some"); + narinfo_returned.url = "some.nar"; + + assert_eq!( + r#"StorePath: /nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz +URL: some.nar +Compression: none +NarHash: sha256:1lvqpbk2k1sb39z8jfxixf7p7v8sj4z6mmpa44nnmff3w1y6h8lh +NarSize: 1033416 +References: +Deriver: dyivpmlaq2km6c11i0s6bi6mbsx0ylqf-hello-2.12.1.tar.gz.drv +Sig: cache.nixos.org-1:ywnIG629nQZQhEr6/HLDrLT/mUEp5J1LC6NmWSlJRWL/nM7oGItJQUYWGLvYGhSQvHrhIuvMpjNmBNh/WWqCDg== +CA: fixed:sha256:086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd +"#, + narinfo_returned.to_string(), + ); +} -- cgit 1.4.1