Skip to main content

nix_compat/nix_daemon/
mod.rs

1pub mod worker_protocol;
2
3use std::io::Result;
4
5use tokio::io::AsyncRead;
6use tracing::warn;
7use types::{QueryValidPaths, UnkeyedValidPathInfo, ValidPathInfo};
8
9use crate::store_path::StorePath;
10
11pub mod framing;
12pub mod handler;
13pub mod types;
14
15#[cfg(test)]
16use mockall::automock;
17
18/// Represents all possible operations over the nix-daemon protocol.
19#[cfg_attr(test, automock)]
20pub trait NixDaemonIO: Sync {
21    fn is_valid_path(
22        &self,
23        path: &StorePath<String>,
24    ) -> impl std::future::Future<Output = Result<bool>> + Send {
25        async move { Ok(self.query_path_info(path).await?.is_some()) }
26    }
27
28    fn query_path_info(
29        &self,
30        path: &StorePath<String>,
31    ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
32
33    fn query_path_from_hash_part(
34        &self,
35        hash: &[u8],
36    ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
37
38    fn query_valid_paths(
39        &self,
40        request: &QueryValidPaths,
41    ) -> impl std::future::Future<Output = Result<Vec<StorePath<String>>>> + Send {
42        async move {
43            if request.substitute {
44                warn!("snix does not yet support substitution, ignoring the 'substitute' flag...");
45            }
46
47            let mut results: Vec<StorePath<String>> = Vec::with_capacity(request.paths.len());
48
49            for path in request.paths.iter() {
50                if self.is_valid_path(path).await? {
51                    results.push(path.clone());
52                }
53            }
54
55            Ok(results)
56        }
57    }
58
59    fn query_valid_derivers(
60        &self,
61        path: &StorePath<String>,
62    ) -> impl std::future::Future<Output = Result<Vec<StorePath<String>>>> + Send {
63        async move {
64            let result = self.query_path_info(path).await?;
65            let result: Vec<_> = result.into_iter().filter_map(|info| info.deriver).collect();
66            Ok(result)
67        }
68    }
69
70    #[cfg_attr(test, mockall::concretize)]
71    fn add_to_store_nar<R>(
72        &self,
73        info: ValidPathInfo,
74        reader: &mut R,
75        repair: bool,
76        dont_check_sigs: bool,
77    ) -> impl std::future::Future<Output = Result<()>> + Send
78    where
79        R: AsyncRead + Send + Unpin;
80}
81
82#[cfg(test)]
83mod tests {
84
85    use crate::{
86        nix_daemon::types::{NarHash, QueryValidPaths},
87        store_path::StorePath,
88    };
89
90    use super::{NixDaemonIO, types::UnkeyedValidPathInfo};
91
92    // Very simple mock
93    // Unable to use mockall as it does not support unboxed async traits.
94    pub struct MockNixDaemonIO {
95        query_path_info_result: Option<UnkeyedValidPathInfo>,
96    }
97
98    impl NixDaemonIO for MockNixDaemonIO {
99        async fn query_path_info(
100            &self,
101            _path: &StorePath<String>,
102        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
103            Ok(self.query_path_info_result.clone())
104        }
105
106        async fn query_path_from_hash_part(
107            &self,
108            _hash: &[u8],
109        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
110            Ok(None)
111        }
112
113        async fn add_to_store_nar<R>(
114            &self,
115            _info: super::types::ValidPathInfo,
116            _reader: &mut R,
117            _repair: bool,
118            _dont_check_sigs: bool,
119        ) -> std::io::Result<()>
120        where
121            R: tokio::io::AsyncRead + Send + Unpin,
122        {
123            Ok(())
124        }
125    }
126
127    #[tokio::test]
128    async fn test_is_valid_path_returns_true() {
129        let path =
130            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
131                .unwrap();
132        let io = MockNixDaemonIO {
133            query_path_info_result: Some(UnkeyedValidPathInfo {
134                deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
135                nar_hash: NarHash::from_digest([0u8; 32]),
136                references: Vec::new(),
137                registration_time: 0,
138                nar_size: 0,
139                ultimate: true,
140                signatures: Vec::new(),
141                ca: None,
142            }),
143        };
144
145        let result = io
146            .is_valid_path(&path)
147            .await
148            .expect("expected to get a non-empty response");
149        assert!(result, "expected to get true");
150    }
151
152    #[tokio::test]
153    async fn test_is_valid_path_returns_false() {
154        let path =
155            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
156                .unwrap();
157        let io = MockNixDaemonIO {
158            query_path_info_result: None,
159        };
160
161        let result = io
162            .is_valid_path(&path)
163            .await
164            .expect("expected to get a non-empty response");
165        assert!(!result, "expected to get false");
166    }
167
168    #[tokio::test]
169    async fn test_query_valid_paths_returns_empty_response() {
170        let path =
171            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
172                .unwrap();
173        let io = MockNixDaemonIO {
174            query_path_info_result: None,
175        };
176
177        let result = io
178            .query_valid_paths(&QueryValidPaths {
179                paths: vec![path],
180                substitute: false,
181            })
182            .await
183            .expect("expected to get a non-empty response");
184        assert_eq!(result, vec![], "expected to get empty response");
185    }
186
187    #[tokio::test]
188    async fn test_query_valid_paths_returns_non_empty_response() {
189        let path =
190            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
191                .unwrap();
192        let io = MockNixDaemonIO {
193            query_path_info_result: Some(UnkeyedValidPathInfo {
194                deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
195                nar_hash: NarHash::from_digest([0u8; 32]),
196                references: Vec::new(),
197                registration_time: 0,
198                nar_size: 0,
199                ultimate: true,
200                signatures: Vec::new(),
201                ca: None,
202            }),
203        };
204
205        let result = io
206            .query_valid_paths(&QueryValidPaths {
207                paths: vec![path.clone()],
208                substitute: false,
209            })
210            .await
211            .expect("expected to get a non-empty response");
212        assert_eq!(result, vec![path], "expected to get non empty response");
213    }
214
215    #[tokio::test]
216    async fn test_query_valid_derivers_returns_empty_response() {
217        let path =
218            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
219                .unwrap();
220        let io = MockNixDaemonIO {
221            query_path_info_result: None,
222        };
223
224        let result = io
225            .query_valid_derivers(&path)
226            .await
227            .expect("expected to get a non-empty response");
228        assert_eq!(result, vec![], "expected to get empty response");
229    }
230
231    #[tokio::test]
232    async fn test_query_valid_derivers_returns_non_empty_response() {
233        let path =
234            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
235                .unwrap();
236        let deriver = StorePath::<String>::from_bytes(
237            "z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes(),
238        )
239        .unwrap();
240        let io = MockNixDaemonIO {
241            query_path_info_result: Some(UnkeyedValidPathInfo {
242                deriver: Some(deriver.clone()),
243                nar_hash: NarHash::from_digest([0u8; 32]),
244                references: vec![],
245                registration_time: 0,
246                nar_size: 1,
247                ultimate: true,
248                signatures: vec![],
249                ca: None,
250            }),
251        };
252
253        let result = io
254            .query_valid_derivers(&path)
255            .await
256            .expect("expected to get a non-empty response");
257        assert_eq!(result, vec![deriver], "expected to get non empty response");
258    }
259}