diff options
author | Vova Kryachko <v.kryachko@gmail.com> | 2024-11-08T15·44-0500 |
---|---|---|
committer | Vladimir Kryachko <v.kryachko@gmail.com> | 2024-11-12T02·15+0000 |
commit | b564ed9d43f17c620439815b86d2940be197bd47 (patch) | |
tree | a828f081e0b9f3568366534b800c12d88d5cfff7 /tvix/nix-compat/src/nix_daemon/worker_protocol.rs | |
parent | 72bc4e0270891d72213989096ff1180adc07a578 (diff) |
feat(nix-daemon): Implement client handler. r/8907
This change includes only the basic nix handshake protocol handling and sets up a client session. The only supported operation at this point is SetOptions. Additional operations will be implemented in subsequent cls. Change-Id: I3eccd9e0ceb270c3865929543c702f1491768852 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12743 Autosubmit: Vladimir Kryachko <v.kryachko@gmail.com> Tested-by: BuildkiteCI Reviewed-by: flokli <flokli@flokli.de> Reviewed-by: edef <edef@edef.eu> Reviewed-by: Brian Olsen <me@griff.name>
Diffstat (limited to 'tvix/nix-compat/src/nix_daemon/worker_protocol.rs')
-rw-r--r-- | tvix/nix-compat/src/nix_daemon/worker_protocol.rs | 203 |
1 files changed, 33 insertions, 170 deletions
diff --git a/tvix/nix-compat/src/nix_daemon/worker_protocol.rs b/tvix/nix-compat/src/nix_daemon/worker_protocol.rs index 92259a0633a0..1ef9b9ab02d7 100644 --- a/tvix/nix-compat/src/nix_daemon/worker_protocol.rs +++ b/tvix/nix-compat/src/nix_daemon/worker_protocol.rs @@ -1,20 +1,21 @@ use std::{ cmp::min, - collections::HashMap, + collections::BTreeMap, io::{Error, ErrorKind}, }; -use enum_primitive_derive::Primitive; -use num_traits::{FromPrimitive, ToPrimitive}; +use nix_compat_derive::{NixDeserialize, NixSerialize}; +use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::wire; use crate::wire::ProtocolVersion; -static WORKER_MAGIC_1: u64 = 0x6e697863; // "nixc" -static WORKER_MAGIC_2: u64 = 0x6478696f; // "dxio" +pub(crate) static WORKER_MAGIC_1: u64 = 0x6e697863; // "nixc" +pub(crate) static WORKER_MAGIC_2: u64 = 0x6478696f; // "dxio" pub static STDERR_LAST: u64 = 0x616c7473; // "alts" +pub(crate) static STDERR_ERROR: u64 = 0x63787470; // "cxtp" /// | Nix version | Protocol | /// |-----------------|----------| @@ -55,7 +56,11 @@ pub static MAX_SETTING_SIZE: usize = 1024; /// Note: for now, we're using the Nix 2.20 operation description. The /// operations marked as obsolete are obsolete for Nix 2.20, not /// necessarily for Nix 2.3. We'll revisit this later on. -#[derive(Debug, PartialEq, Primitive)] +#[derive( + Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive, NixDeserialize, NixSerialize, +)] +#[nix(try_from = "u64", into = "u64")] +#[repr(u64)] pub enum Operation { IsValidPath = 1, HasSubstitutes = 3, @@ -106,8 +111,13 @@ pub enum Operation { /// Log verbosity. In the Nix wire protocol, the client requests a /// verbosity level to the daemon, which in turns does not produce any /// log below this verbosity. -#[derive(Debug, PartialEq, Primitive)] +#[derive( + Debug, PartialEq, FromPrimitive, IntoPrimitive, NixDeserialize, NixSerialize, Default, Clone, +)] +#[nix(from = "u64", into = "u64")] +#[repr(u64)] pub enum Verbosity { + #[default] LvlError = 0, LvlWarn = 1, LvlNotice = 2, @@ -120,7 +130,7 @@ pub enum Verbosity { /// Settings requested by the client. These settings are applied to a /// connection to between the daemon and a client. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, NixDeserialize, NixSerialize, Default)] pub struct ClientSettings { pub keep_failed: bool, pub keep_going: bool, @@ -128,70 +138,21 @@ pub struct ClientSettings { pub verbosity: Verbosity, pub max_build_jobs: u64, pub max_silent_time: u64, - pub verbose_build: bool, + pub use_build_hook: bool, + pub verbose_build: u64, + pub log_type: u64, + pub print_build_trace: u64, pub build_cores: u64, pub use_substitutes: bool, + /// Key/Value dictionary in charge of overriding the settings set /// by the Nix config file. /// /// Some settings can be safely overidden, /// some other require the user running the Nix client to be part /// of the trusted users group. - pub overrides: HashMap<String, String>, -} - -/// Reads the client settings from the wire. -/// -/// Note: this function **only** reads the settings. It does not -/// manage the log state with the daemon. You'll have to do that on -/// your own. A minimal log implementation will consist in sending -/// back [STDERR_LAST] to the client after reading the client -/// settings. -/// -/// FUTUREWORK: write serialization. -pub async fn read_client_settings<R: AsyncReadExt + Unpin>( - r: &mut R, - client_version: ProtocolVersion, -) -> std::io::Result<ClientSettings> { - let keep_failed = r.read_u64_le().await? != 0; - let keep_going = r.read_u64_le().await? != 0; - let try_fallback = r.read_u64_le().await? != 0; - let verbosity_uint = r.read_u64_le().await?; - let verbosity = Verbosity::from_u64(verbosity_uint).ok_or_else(|| { - Error::new( - ErrorKind::InvalidData, - format!("Can't convert integer {} to verbosity", verbosity_uint), - ) - })?; - let max_build_jobs = r.read_u64_le().await?; - let max_silent_time = r.read_u64_le().await?; - _ = r.read_u64_le().await?; // obsolete useBuildHook - let verbose_build = r.read_u64_le().await? != 0; - _ = r.read_u64_le().await?; // obsolete logType - _ = r.read_u64_le().await?; // obsolete printBuildTrace - let build_cores = r.read_u64_le().await?; - let use_substitutes = r.read_u64_le().await? != 0; - let mut overrides = HashMap::new(); - if client_version.minor() >= 12 { - let num_overrides = r.read_u64_le().await?; - for _ in 0..num_overrides { - let name = wire::read_string(r, 0..=MAX_SETTING_SIZE).await?; - let value = wire::read_string(r, 0..=MAX_SETTING_SIZE).await?; - overrides.insert(name, value); - } - } - Ok(ClientSettings { - keep_failed, - keep_going, - try_fallback, - verbosity, - max_build_jobs, - max_silent_time, - verbose_build, - build_cores, - use_substitutes, - overrides, - }) + #[nix(version = "12..")] + pub overrides: BTreeMap<String, String>, } /// Performs the initial handshake the server is sending to a connecting client. @@ -269,18 +230,17 @@ where /// Read a worker [Operation] from the wire. pub async fn read_op<R: AsyncReadExt + Unpin>(r: &mut R) -> std::io::Result<Operation> { let op_number = r.read_u64_le().await?; - Operation::from_u64(op_number).ok_or(Error::new( - ErrorKind::InvalidData, - format!("Invalid OP number {}", op_number), - )) + Operation::try_from(op_number).map_err(|_| { + Error::new( + ErrorKind::InvalidData, + format!("Invalid OP number {}", op_number), + ) + }) } /// Write a worker [Operation] to the wire. -pub async fn write_op<W: AsyncWriteExt + Unpin>(w: &mut W, op: &Operation) -> std::io::Result<()> { - let op = Operation::to_u64(op).ok_or(Error::new( - ErrorKind::Other, - format!("Can't convert the OP {:?} to u64", op), - ))?; +pub async fn write_op<W: AsyncWriteExt + Unpin>(w: &mut W, op: Operation) -> std::io::Result<()> { + let op: u64 = op.into(); w.write_u64(op).await } @@ -309,8 +269,6 @@ where #[cfg(test)] mod tests { use super::*; - use hex_literal::hex; - use tokio_test::io::Builder; #[tokio::test] async fn test_init_hanshake() { @@ -391,99 +349,4 @@ mod tests { assert_eq!(picked_version, ProtocolVersion::from_parts(1, 24)) } - - #[tokio::test] - async fn test_read_client_settings_without_overrides() { - // Client settings bits captured from a Nix 2.3.17 run w/ sockdump (protocol version 21). - let wire_bits = hex!( - "00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 02 00 00 00 00 00 00 00 \ - 10 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 01 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 01 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00" - ); - let mut mock = Builder::new().read(&wire_bits).build(); - let settings = read_client_settings(&mut mock, ProtocolVersion::from_parts(1, 21)) - .await - .expect("should parse"); - let expected = ClientSettings { - keep_failed: false, - keep_going: false, - try_fallback: false, - verbosity: Verbosity::LvlNotice, - max_build_jobs: 16, - max_silent_time: 0, - verbose_build: false, - build_cores: 0, - use_substitutes: true, - overrides: HashMap::new(), - }; - assert_eq!(settings, expected); - } - - #[tokio::test] - async fn test_read_client_settings_with_overrides() { - // Client settings bits captured from a Nix 2.3.17 run w/ sockdump (protocol version 21). - let wire_bits = hex!( - "00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 02 00 00 00 00 00 00 00 \ - 10 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 01 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 00 00 00 00 00 00 00 00 \ - 01 00 00 00 00 00 00 00 \ - 02 00 00 00 00 00 00 00 \ - 0c 00 00 00 00 00 00 00 \ - 61 6c 6c 6f 77 65 64 2d \ - 75 72 69 73 00 00 00 00 \ - 1e 00 00 00 00 00 00 00 \ - 68 74 74 70 73 3a 2f 2f \ - 62 6f 72 64 65 61 75 78 \ - 2e 67 75 69 78 2e 67 6e \ - 75 2e 6f 72 67 2f 00 00 \ - 0d 00 00 00 00 00 00 00 \ - 61 6c 6c 6f 77 65 64 2d \ - 75 73 65 72 73 00 00 00 \ - 0b 00 00 00 00 00 00 00 \ - 6a 65 61 6e 20 70 69 65 \ - 72 72 65 00 00 00 00 00" - ); - let mut mock = Builder::new().read(&wire_bits).build(); - let settings = read_client_settings(&mut mock, ProtocolVersion::from_parts(1, 21)) - .await - .expect("should parse"); - let overrides = HashMap::from([ - ( - String::from("allowed-uris"), - String::from("https://bordeaux.guix.gnu.org/"), - ), - (String::from("allowed-users"), String::from("jean pierre")), - ]); - let expected = ClientSettings { - keep_failed: false, - keep_going: false, - try_fallback: false, - verbosity: Verbosity::LvlNotice, - max_build_jobs: 16, - max_silent_time: 0, - verbose_build: false, - build_cores: 0, - use_substitutes: true, - overrides, - }; - assert_eq!(settings, expected); - } } |