diff options
author | Picnoir <picnoir@alternativebit.fr> | 2024-03-21T15·09+0100 |
---|---|---|
committer | picnoir picnoir <picnoir@alternativebit.fr> | 2024-04-03T11·25+0000 |
commit | 017ff431fe0b3c860e4ddb45e54bbd8b3b2b459d (patch) | |
tree | e40ee35e29e5accd1fa6b3cd7d8e72d0931ca0e5 | |
parent | f7cdbbef452b0e1901be4e66567bfef8e5d5809d (diff) |
feat(tvix/nix-compat): introduce write_bytes r/7843
Write counterpart of read_bytes. Despite its name, we mostly use it to write strings (as in ascii strings) to the wire. We also extract the padding calculation in its own function. Change-Id: I8d936e989961107261b3089e4275acbd2c093a7f Reviewed-on: https://cl.tvl.fyi/c/depot/+/11230 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
-rw-r--r-- | tvix/nix-compat/src/wire/bytes.rs | 71 | ||||
-rw-r--r-- | users/picnoir/tvix-daemon/default.nix | 18 |
2 files changed, 79 insertions, 10 deletions
diff --git a/tvix/nix-compat/src/wire/bytes.rs b/tvix/nix-compat/src/wire/bytes.rs index f2fe30083b1c..a050b161048b 100644 --- a/tvix/nix-compat/src/wire/bytes.rs +++ b/tvix/nix-compat/src/wire/bytes.rs @@ -1,6 +1,6 @@ use std::ops::RangeBounds; -use tokio::io::AsyncReadExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use super::primitive; @@ -28,12 +28,7 @@ where // calculate the total length, including padding. // byte packets are padded to 8 byte blocks each. - let padded_len = if len % 8 == 0 { - len - } else { - len + (8 - len % 8) - }; - + let padded_len = padding_len(len) as u64 + (len as u64); let mut limited_reader = r.take(padded_len); let mut buf = Vec::new(); @@ -63,6 +58,32 @@ where Ok(buf) } +/// Writes a sequence of sized bits to a (hopefully buffered) +/// [AsyncWriteExt] handle. +/// +/// On the wire, it looks as follows: +/// +/// 1. Number of bytes contained in the buffer we're about to write on +/// the wire. (LE-encoded on 64 bits) +/// 2. Raw payload. +/// 3. Null padding up until the next 8 bytes alignment block. +/// +/// Note: if performance matters to you, make sure your +/// [AsyncWriteExt] handle is buffered. This function is quite +/// write-intesive. +pub async fn write_bytes<W: AsyncWriteExt + Unpin>(w: &mut W, b: &[u8]) -> std::io::Result<()> { + // We're assuming the handle is buffered: we can afford not + // writing all the bytes in one go. + let len = b.len(); + primitive::write_u64(w, len as u64).await?; + w.write_all(b).await?; + let padding = padding_len(len as u64); + if padding != 0 { + w.write_all(&vec![0; padding as usize]).await?; + } + Ok(()) +} + #[allow(dead_code)] /// Read an unlimited number of bytes from the AsyncRead. /// Note this can exhaust memory. @@ -72,9 +93,20 @@ pub async fn read_bytes_unchecked<R: AsyncReadExt + Unpin>(r: &mut R) -> std::io read_bytes(r, 0u64..).await } +/// Computes the number of bytes we should add to len (a length in +/// bytes) to be alined on 64 bits (8 bytes). +fn padding_len(len: u64) -> u8 { + let modulo = len % 8; + if modulo == 0 { + 0 + } else { + 8 - modulo as u8 + } +} + #[cfg(test)] mod tests { - use tokio_test::io::Builder; + use tokio_test::{assert_ok, io::Builder}; use super::*; use hex_literal::hex; @@ -120,11 +152,32 @@ mod tests { #[tokio::test] /// Ensure we don't read any further than the size field if the length /// doesn't match the range we want to accept. - async fn test_reject_too_large() { + async fn test_read_reject_too_large() { let mut mock = Builder::new().read(&100u64.to_le_bytes()).build(); read_bytes(&mut mock, 10..10) .await .expect_err("expect this to fail"); } + + #[tokio::test] + async fn test_write_bytes_no_padding() { + let input = hex!("6478696f34657661"); + let len = input.len() as u64; + let mut mock = Builder::new() + .write(&len.to_le_bytes()) + .write(&input) + .build(); + assert_ok!(write_bytes(&mut mock, &input).await) + } + #[tokio::test] + async fn test_write_bytes_with_padding() { + let input = hex!("322e332e3137"); + let len = input.len() as u64; + let mut mock = Builder::new() + .write(&len.to_le_bytes()) + .write(&hex!("322e332e31370000")) + .build(); + assert_ok!(write_bytes(&mut mock, &input).await) + } } diff --git a/users/picnoir/tvix-daemon/default.nix b/users/picnoir/tvix-daemon/default.nix index 68aa12d0e291..e9004e408208 100644 --- a/users/picnoir/tvix-daemon/default.nix +++ b/users/picnoir/tvix-daemon/default.nix @@ -1,4 +1,4 @@ -{ depot, lib, pkgs, ... }: +{ depot, pkgs, ... }: let crate2nix = pkgs.callPackage ./Cargo.nix { @@ -19,6 +19,22 @@ in { shell = (import ./shell.nix { inherit pkgs; }); tvix-daemon = crate2nix.rootCrate.build; + clippy = pkgs.stdenv.mkDerivation { + src = ./.; + cargoDeps = crate2nix.allWorkspaceMembers; + name = "tvix-daemon-clippy"; + + nativeBuildInputs = with pkgs; [ + cargo + clippy + pkg-config + protobuf + rustc + rustPlatform.cargoSetupHook + ]; + + buildPhase = "cargo clippy --tests --all-features --benches --examples | tee $out"; + }; meta.ci.targets = [ "tvix-daemon" "shell" |