1pub mod worker_protocol;
2
3use std::{collections::BTreeMap, io::Result};
4
5use tokio::io::{AsyncBufRead, AsyncRead};
6use tracing::warn;
7use types::{QueryMissingResult, QueryValidPaths, UnkeyedValidPathInfo, ValidPathInfo};
8
9use crate::{
10 derivation::{OutputName, UnverifiedDerivation},
11 derived_path::DerivedPath,
12 nix_daemon::types::{BuildMode, BuildResult, KeyedBuildResult},
13 store_path::StorePath,
14};
15
16pub mod framing;
17pub mod handler;
18pub mod types;
19
20#[cfg_attr(test, mockall::automock)]
22pub trait NixDaemonIO: Sync {
23 fn is_valid_path(
24 &self,
25 path: &StorePath,
26 ) -> impl std::future::Future<Output = Result<bool>> + Send {
27 async move { Ok(self.query_path_info(path).await?.is_some()) }
28 }
29
30 fn ensure_path(
31 &self,
32 path: &StorePath,
33 ) -> impl std::future::Future<Output = Result<()>> + Send {
34 async move {
35 if self.is_valid_path(path).await? {
36 Ok(())
37 } else {
38 Err(std::io::Error::other(format!("unknown path {}", path)))
39 }
40 }
41 }
42
43 fn query_path_info(
44 &self,
45 path: &StorePath,
46 ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
47
48 fn query_path_from_hash_part(
49 &self,
50 hash: &[u8],
51 ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
52
53 fn query_valid_paths(
54 &self,
55 request: &QueryValidPaths,
56 ) -> impl std::future::Future<Output = Result<Vec<StorePath>>> + Send {
57 async move {
58 if request.substitute {
59 warn!("snix does not yet support substitution, ignoring the 'substitute' flag...");
60 }
61
62 let mut results: Vec<StorePath> = Vec::with_capacity(request.paths.len());
63
64 for path in request.paths.iter() {
65 if self.is_valid_path(path).await? {
66 results.push(path.clone());
67 }
68 }
69
70 Ok(results)
71 }
72 }
73
74 fn query_valid_derivers(
75 &self,
76 path: &StorePath,
77 ) -> impl std::future::Future<Output = Result<Vec<StorePath>>> + Send {
78 async move {
79 let result = self.query_path_info(path).await?;
80 let result: Vec<_> = result.into_iter().filter_map(|info| info.deriver).collect();
81 Ok(result)
82 }
83 }
84
85 fn query_missing(
86 &self,
87 derived_paths: Vec<DerivedPath>,
88 ) -> impl std::future::Future<Output = Result<QueryMissingResult>> + Send;
89
90 fn query_derivation_output_map(
91 &self,
92 drv_path: &StorePath,
93 ) -> impl std::future::Future<Output = Result<BTreeMap<OutputName, Option<StorePath>>>> + Send;
94
95 #[cfg_attr(test, mockall::concretize)]
96 fn add_to_store_nar<R>(
97 &self,
98 info: ValidPathInfo,
99 reader: &mut R,
100 repair: bool,
101 dont_check_sigs: bool,
102 ) -> impl std::future::Future<Output = Result<()>> + Send
103 where
104 R: AsyncRead + Send + Unpin;
105
106 fn add_temp_root(
109 &self,
110 path: &StorePath,
111 ) -> impl std::future::Future<Output = Result<()>> + Send;
112
113 fn add_indirect_root(&self, path: &str)
116 -> impl std::future::Future<Output = Result<()>> + Send;
117
118 fn nar_from_path(
119 &self,
120 path: &StorePath,
121 ) -> impl std::future::Future<Output = Result<Box<dyn AsyncBufRead + Unpin + Send>>> + Send;
122
123 fn build_paths(
124 &self,
125 derived_paths: Vec<DerivedPath>,
126 mode: BuildMode,
127 ) -> impl std::future::Future<Output = Result<()>> + Send;
128
129 fn build_paths_with_results(
130 &self,
131 derived_paths: Vec<DerivedPath>,
132 mode: BuildMode,
133 ) -> impl std::future::Future<Output = Result<Vec<KeyedBuildResult>>> + Send;
134
135 fn build_derivation(
136 &self,
137 drv_path: StorePath,
138 derivation: UnverifiedDerivation,
139 mode: BuildMode,
140 ) -> impl std::future::Future<Output = Result<BuildResult>> + Send;
141}
142
143#[cfg(test)]
144mod tests {
145
146 use std::collections::BTreeMap;
147
148 use crate::{
149 derivation::OutputName,
150 derived_path::DerivedPath,
151 nix_daemon::types::{NarHash, QueryValidPaths},
152 store_path::StorePath,
153 };
154
155 use super::{NixDaemonIO, types::UnkeyedValidPathInfo};
156
157 pub struct MockNixDaemonIO {
160 query_path_info_result: Option<UnkeyedValidPathInfo>,
161 }
162
163 impl NixDaemonIO for MockNixDaemonIO {
164 async fn query_path_info(
165 &self,
166 _path: &StorePath,
167 ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
168 Ok(self.query_path_info_result.clone())
169 }
170
171 async fn query_path_from_hash_part(
172 &self,
173 _hash: &[u8],
174 ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
175 Ok(None)
176 }
177
178 async fn add_to_store_nar<R>(
179 &self,
180 _info: super::types::ValidPathInfo,
181 _reader: &mut R,
182 _repair: bool,
183 _dont_check_sigs: bool,
184 ) -> std::io::Result<()>
185 where
186 R: tokio::io::AsyncRead + Send + Unpin,
187 {
188 Ok(())
189 }
190
191 async fn nar_from_path(
192 &self,
193 _path: &StorePath,
194 ) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
195 Err(std::io::Error::other(
196 "Operation NarFromPath is not implemented",
197 ))
198 }
199
200 async fn build_paths(
201 &self,
202 _derived_paths: Vec<DerivedPath>,
203 _mode: super::types::BuildMode,
204 ) -> std::io::Result<()> {
205 Ok(())
206 }
207
208 async fn build_paths_with_results(
209 &self,
210 _derived_paths: Vec<DerivedPath>,
211 _mode: super::types::BuildMode,
212 ) -> std::io::Result<Vec<super::types::KeyedBuildResult>> {
213 Err(std::io::Error::other(
214 "Operation BuildPathsWithResults is not implemented",
215 ))
216 }
217
218 async fn build_derivation(
219 &self,
220 _drv_path: StorePath,
221 _derivation: crate::derivation::UnverifiedDerivation,
222 _mode: super::types::BuildMode,
223 ) -> std::io::Result<super::types::BuildResult> {
224 Err(std::io::Error::other(
225 "Operation BuildDerivation is not implemented",
226 ))
227 }
228
229 async fn query_missing(
230 &self,
231 _derived_paths: Vec<DerivedPath>,
232 ) -> std::io::Result<super::types::QueryMissingResult> {
233 Err(std::io::Error::other(
234 "Operation QueryMissing is not implemented",
235 ))
236 }
237
238 async fn query_derivation_output_map(
239 &self,
240 _drv_path: &StorePath,
241 ) -> std::io::Result<BTreeMap<OutputName, Option<StorePath>>> {
242 Err(std::io::Error::other(
243 "Operation QueryDerivationOutputMap is not implemented",
244 ))
245 }
246
247 async fn add_temp_root(&self, _path: &StorePath) -> std::io::Result<()> {
248 Ok(())
249 }
250
251 async fn add_indirect_root(&self, _path: &str) -> std::io::Result<()> {
252 Ok(())
253 }
254 }
255
256 #[tokio::test]
257 async fn test_is_valid_path_returns_true() {
258 let path =
259 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
260 let io = MockNixDaemonIO {
261 query_path_info_result: Some(UnkeyedValidPathInfo {
262 deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
263 nar_hash: NarHash::from_digest([0u8; 32]),
264 references: Vec::new(),
265 registration_time: 0,
266 nar_size: 0,
267 ultimate: true,
268 signatures: Vec::new(),
269 ca: None,
270 }),
271 };
272
273 let result = io
274 .is_valid_path(&path)
275 .await
276 .expect("expected to get a non-empty response");
277 assert!(result, "expected to get true");
278 }
279
280 #[tokio::test]
281 async fn test_is_valid_path_returns_false() {
282 let path =
283 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
284 let io = MockNixDaemonIO {
285 query_path_info_result: None,
286 };
287
288 let result = io
289 .is_valid_path(&path)
290 .await
291 .expect("expected to get a non-empty response");
292 assert!(!result, "expected to get false");
293 }
294
295 #[tokio::test]
296 async fn test_query_valid_paths_returns_empty_response() {
297 let path =
298 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
299 let io = MockNixDaemonIO {
300 query_path_info_result: None,
301 };
302
303 let result = io
304 .query_valid_paths(&QueryValidPaths {
305 paths: vec![path],
306 substitute: false,
307 })
308 .await
309 .expect("expected to get a non-empty response");
310 assert_eq!(result, vec![], "expected to get empty response");
311 }
312
313 #[tokio::test]
314 async fn test_query_valid_paths_returns_non_empty_response() {
315 let path =
316 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
317 let io = MockNixDaemonIO {
318 query_path_info_result: Some(UnkeyedValidPathInfo {
319 deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
320 nar_hash: NarHash::from_digest([0u8; 32]),
321 references: Vec::new(),
322 registration_time: 0,
323 nar_size: 0,
324 ultimate: true,
325 signatures: Vec::new(),
326 ca: None,
327 }),
328 };
329
330 let result = io
331 .query_valid_paths(&QueryValidPaths {
332 paths: vec![path.clone()],
333 substitute: false,
334 })
335 .await
336 .expect("expected to get a non-empty response");
337 assert_eq!(result, vec![path], "expected to get non empty response");
338 }
339
340 #[tokio::test]
341 async fn test_query_valid_derivers_returns_empty_response() {
342 let path =
343 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
344 let io = MockNixDaemonIO {
345 query_path_info_result: None,
346 };
347
348 let result = io
349 .query_valid_derivers(&path)
350 .await
351 .expect("expected to get a non-empty response");
352 assert_eq!(result, vec![], "expected to get empty response");
353 }
354
355 #[tokio::test]
356 async fn test_query_valid_derivers_returns_non_empty_response() {
357 let path =
358 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
359 let deriver =
360 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes()).unwrap();
361 let io = MockNixDaemonIO {
362 query_path_info_result: Some(UnkeyedValidPathInfo {
363 deriver: Some(deriver.clone()),
364 nar_hash: NarHash::from_digest([0u8; 32]),
365 references: vec![],
366 registration_time: 0,
367 nar_size: 1,
368 ultimate: true,
369 signatures: vec![],
370 ca: None,
371 }),
372 };
373
374 let result = io
375 .query_valid_derivers(&path)
376 .await
377 .expect("expected to get a non-empty response");
378 assert_eq!(result, vec![deriver], "expected to get non empty response");
379 }
380}