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 nar_from_path(
107 &self,
108 path: &StorePath,
109 ) -> impl std::future::Future<Output = Result<Box<dyn AsyncBufRead + Unpin + Send>>> + Send;
110
111 fn build_paths(
112 &self,
113 derived_paths: Vec<DerivedPath>,
114 mode: BuildMode,
115 ) -> impl std::future::Future<Output = Result<()>> + Send;
116
117 fn build_paths_with_results(
118 &self,
119 derived_paths: Vec<DerivedPath>,
120 mode: BuildMode,
121 ) -> impl std::future::Future<Output = Result<Vec<KeyedBuildResult>>> + Send;
122
123 fn build_derivation(
124 &self,
125 drv_path: StorePath,
126 derivation: UnverifiedDerivation,
127 mode: BuildMode,
128 ) -> impl std::future::Future<Output = Result<BuildResult>> + Send;
129}
130
131#[cfg(test)]
132mod tests {
133
134 use std::collections::BTreeMap;
135
136 use crate::{
137 derivation::OutputName,
138 derived_path::DerivedPath,
139 nix_daemon::types::{NarHash, QueryValidPaths},
140 store_path::StorePath,
141 };
142
143 use super::{NixDaemonIO, types::UnkeyedValidPathInfo};
144
145 pub struct MockNixDaemonIO {
148 query_path_info_result: Option<UnkeyedValidPathInfo>,
149 }
150
151 impl NixDaemonIO for MockNixDaemonIO {
152 async fn query_path_info(
153 &self,
154 _path: &StorePath,
155 ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
156 Ok(self.query_path_info_result.clone())
157 }
158
159 async fn query_path_from_hash_part(
160 &self,
161 _hash: &[u8],
162 ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
163 Ok(None)
164 }
165
166 async fn add_to_store_nar<R>(
167 &self,
168 _info: super::types::ValidPathInfo,
169 _reader: &mut R,
170 _repair: bool,
171 _dont_check_sigs: bool,
172 ) -> std::io::Result<()>
173 where
174 R: tokio::io::AsyncRead + Send + Unpin,
175 {
176 Ok(())
177 }
178
179 async fn nar_from_path(
180 &self,
181 _path: &StorePath,
182 ) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
183 Err(std::io::Error::other(
184 "Operation NarFromPath is not implemented",
185 ))
186 }
187
188 async fn build_paths(
189 &self,
190 _derived_paths: Vec<DerivedPath>,
191 _mode: super::types::BuildMode,
192 ) -> std::io::Result<()> {
193 Ok(())
194 }
195
196 async fn build_paths_with_results(
197 &self,
198 _derived_paths: Vec<DerivedPath>,
199 _mode: super::types::BuildMode,
200 ) -> std::io::Result<Vec<super::types::KeyedBuildResult>> {
201 Err(std::io::Error::other(
202 "Operation BuildPathsWithResults is not implemented",
203 ))
204 }
205
206 async fn build_derivation(
207 &self,
208 _drv_path: StorePath,
209 _derivation: crate::derivation::UnverifiedDerivation,
210 _mode: super::types::BuildMode,
211 ) -> std::io::Result<super::types::BuildResult> {
212 Err(std::io::Error::other(
213 "Operation BuildDerivation is not implemented",
214 ))
215 }
216
217 async fn query_missing(
218 &self,
219 _derived_paths: Vec<DerivedPath>,
220 ) -> std::io::Result<super::types::QueryMissingResult> {
221 Err(std::io::Error::other(
222 "Operation QueryMissing is not implemented",
223 ))
224 }
225
226 async fn query_derivation_output_map(
227 &self,
228 _drv_path: &StorePath,
229 ) -> std::io::Result<BTreeMap<OutputName, Option<StorePath>>> {
230 Err(std::io::Error::other(
231 "Operation QueryDerivationOutputMap is not implemented",
232 ))
233 }
234 }
235
236 #[tokio::test]
237 async fn test_is_valid_path_returns_true() {
238 let path =
239 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
240 let io = MockNixDaemonIO {
241 query_path_info_result: Some(UnkeyedValidPathInfo {
242 deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
243 nar_hash: NarHash::from_digest([0u8; 32]),
244 references: Vec::new(),
245 registration_time: 0,
246 nar_size: 0,
247 ultimate: true,
248 signatures: Vec::new(),
249 ca: None,
250 }),
251 };
252
253 let result = io
254 .is_valid_path(&path)
255 .await
256 .expect("expected to get a non-empty response");
257 assert!(result, "expected to get true");
258 }
259
260 #[tokio::test]
261 async fn test_is_valid_path_returns_false() {
262 let path =
263 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
264 let io = MockNixDaemonIO {
265 query_path_info_result: None,
266 };
267
268 let result = io
269 .is_valid_path(&path)
270 .await
271 .expect("expected to get a non-empty response");
272 assert!(!result, "expected to get false");
273 }
274
275 #[tokio::test]
276 async fn test_query_valid_paths_returns_empty_response() {
277 let path =
278 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
279 let io = MockNixDaemonIO {
280 query_path_info_result: None,
281 };
282
283 let result = io
284 .query_valid_paths(&QueryValidPaths {
285 paths: vec![path],
286 substitute: false,
287 })
288 .await
289 .expect("expected to get a non-empty response");
290 assert_eq!(result, vec![], "expected to get empty response");
291 }
292
293 #[tokio::test]
294 async fn test_query_valid_paths_returns_non_empty_response() {
295 let path =
296 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
297 let io = MockNixDaemonIO {
298 query_path_info_result: Some(UnkeyedValidPathInfo {
299 deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
300 nar_hash: NarHash::from_digest([0u8; 32]),
301 references: Vec::new(),
302 registration_time: 0,
303 nar_size: 0,
304 ultimate: true,
305 signatures: Vec::new(),
306 ca: None,
307 }),
308 };
309
310 let result = io
311 .query_valid_paths(&QueryValidPaths {
312 paths: vec![path.clone()],
313 substitute: false,
314 })
315 .await
316 .expect("expected to get a non-empty response");
317 assert_eq!(result, vec![path], "expected to get non empty response");
318 }
319
320 #[tokio::test]
321 async fn test_query_valid_derivers_returns_empty_response() {
322 let path =
323 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
324 let io = MockNixDaemonIO {
325 query_path_info_result: None,
326 };
327
328 let result = io
329 .query_valid_derivers(&path)
330 .await
331 .expect("expected to get a non-empty response");
332 assert_eq!(result, vec![], "expected to get empty response");
333 }
334
335 #[tokio::test]
336 async fn test_query_valid_derivers_returns_non_empty_response() {
337 let path =
338 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
339 let deriver =
340 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes()).unwrap();
341 let io = MockNixDaemonIO {
342 query_path_info_result: Some(UnkeyedValidPathInfo {
343 deriver: Some(deriver.clone()),
344 nar_hash: NarHash::from_digest([0u8; 32]),
345 references: vec![],
346 registration_time: 0,
347 nar_size: 1,
348 ultimate: true,
349 signatures: vec![],
350 ca: None,
351 }),
352 };
353
354 let result = io
355 .query_valid_derivers(&path)
356 .await
357 .expect("expected to get a non-empty response");
358 assert_eq!(result, vec![deriver], "expected to get non empty response");
359 }
360}