about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/Cargo.lock25
-rw-r--r--tvix/Cargo.nix90
-rw-r--r--tvix/eval/Cargo.toml4
-rw-r--r--tvix/eval/docs/builtins.md2
-rw-r--r--tvix/eval/src/builtins/mod.rs32
-rw-r--r--tvix/eval/src/errors.rs10
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-groupBy.exp)0
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-groupBy.nix)2
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-hashstring.exp)0
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-hashstring.nix)0
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix6
-rw-r--r--tvix/eval/src/value/string.rs6
-rw-r--r--web/tvixbolt/Cargo.lock96
14 files changed, 263 insertions, 11 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
index 585199440b..7995402ead 100644
--- a/tvix/Cargo.lock
+++ b/tvix/Cargo.lock
@@ -1417,6 +1417,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
 
 [[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
+[[package]]
 name = "memchr"
 version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2544,6 +2554,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
 name = "sha2"
 version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3346,12 +3367,14 @@ dependencies = [
  "codemap",
  "codemap-diagnostic",
  "criterion",
+ "data-encoding",
  "dirs",
  "genawaiter",
  "imbl",
  "itertools 0.12.0",
  "lazy_static",
  "lexical-core",
+ "md-5",
  "os_str_bytes",
  "path-clean",
  "pretty_assertions",
@@ -3362,6 +3385,8 @@ dependencies = [
  "rstest",
  "serde",
  "serde_json",
+ "sha1",
+ "sha2",
  "smol_str",
  "tabwriter",
  "tempfile",
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
index b0c36524e4..9ee11cbd94 100644
--- a/tvix/Cargo.nix
+++ b/tvix/Cargo.nix
@@ -4191,6 +4191,41 @@ rec {
         features = { };
         resolvedDefaultFeatures = [ "default" ];
       };
+      "md-5" = rec {
+        crateName = "md-5";
+        version = "0.10.6";
+        edition = "2018";
+        sha256 = "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq";
+        libName = "md5";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "md5-asm" ];
+          "default" = [ "std" ];
+          "md5-asm" = [ "dep:md5-asm" ];
+          "oid" = [ "digest/oid" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
       "memchr" = rec {
         crateName = "memchr";
         version = "2.7.1";
@@ -7763,6 +7798,45 @@ rec {
         ];
 
       };
+      "sha1" = rec {
+        crateName = "sha1";
+        version = "0.10.6";
+        edition = "2018";
+        sha256 = "1fnnxlfg08xhkmwf2ahv634as30l1i3xhlhkvxflmasi5nd85gz3";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86" == target."arch" or null) || ("x86_64" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha1-asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha1-asm" = [ "dep:sha1-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
       "sha2" = rec {
         crateName = "sha2";
         version = "0.10.8";
@@ -10494,6 +10568,10 @@ rec {
             packageId = "codemap-diagnostic";
           }
           {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
             name = "dirs";
             packageId = "dirs";
           }
@@ -10521,6 +10599,10 @@ rec {
             features = [ "format" "parse-floats" ];
           }
           {
+            name = "md-5";
+            packageId = "md-5";
+          }
+          {
             name = "os_str_bytes";
             packageId = "os_str_bytes";
             features = [ "conversions" ];
@@ -10558,6 +10640,14 @@ rec {
             packageId = "serde_json";
           }
           {
+            name = "sha1";
+            packageId = "sha1";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
             name = "smol_str";
             packageId = "smol_str";
           }
diff --git a/tvix/eval/Cargo.toml b/tvix/eval/Cargo.toml
index 4f4551a349..e2bd07f827 100644
--- a/tvix/eval/Cargo.toml
+++ b/tvix/eval/Cargo.toml
@@ -33,6 +33,10 @@ tabwriter = "1.2"
 test-strategy = { version = "0.2.1", optional = true }
 toml = "0.6.0"
 xml-rs = "0.8.4"
+sha2 = "0.10.8"
+sha1 = "0.10.6"
+md-5 = "0.10.6"
+data-encoding = "2.5.0"
 
 [dev-dependencies]
 criterion = "0.5"
diff --git a/tvix/eval/docs/builtins.md b/tvix/eval/docs/builtins.md
index 39b59437e2..eff761c705 100644
--- a/tvix/eval/docs/builtins.md
+++ b/tvix/eval/docs/builtins.md
@@ -66,7 +66,7 @@ The `impl` column indicates implementation status in tvix:
 | hasAttr                       | false  |       |       |         |
 | hasContext                    | false  |       |       |         |
 | hashFile                      | false  |       | false | todo    |
-| hashString                    | false  |       |       | todo    |
+| hashString                    | false  |       |       |         |
 | head                          | false  |       |       |         |
 | import                        | true   |       |       |         |
 | intersectAttrs                | false  |       |       |         |
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
index 131f2b7bb2..119c0bda2d 100644
--- a/tvix/eval/src/builtins/mod.rs
+++ b/tvix/eval/src/builtins/mod.rs
@@ -5,9 +5,14 @@
 
 use bstr::{ByteSlice, ByteVec};
 use builtin_macros::builtins;
+use data_encoding::HEXLOWER;
 use genawaiter::rc::Gen;
 use imbl::OrdMap;
+use md5::Md5;
 use regex::Regex;
+use sha1::Sha1;
+use sha2::digest::Output;
+use sha2::{Digest, Sha256, Sha512};
 use std::cmp::{self, Ordering};
 use std::collections::VecDeque;
 use std::collections::{BTreeMap, HashSet};
@@ -686,15 +691,24 @@ mod pure_builtins {
 
     #[builtin("hashString")]
     #[allow(non_snake_case)]
-    async fn builtin_hashString(
-        co: GenCo,
-        _algo: Value,
-        _string: Value,
-    ) -> Result<Value, ErrorKind> {
-        // FIXME: propagate contexts here.
-        Ok(Value::from(CatchableErrorKind::UnimplementedFeature(
-            "hashString".into(),
-        )))
+    async fn builtin_hashString(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
+        fn hash<D: Digest>(b: &[u8]) -> Output<D> {
+            let mut hasher = D::new();
+            hasher.update(b);
+            hasher.finalize()
+        }
+
+        let s = s.to_str()?;
+
+        let encoded_hash = match algo.to_str()?.as_bytes() {
+            b"md5" => HEXLOWER.encode(hash::<Md5>(&s).as_bstr()),
+            b"sha1" => HEXLOWER.encode(hash::<Sha1>(&s).as_bstr()),
+            b"sha256" => HEXLOWER.encode(hash::<Sha256>(&s).as_bstr()),
+            b"sha512" => HEXLOWER.encode(hash::<Sha512>(&s).as_bstr()),
+            _ => return Err(ErrorKind::UnknownHashType(s.into())),
+        };
+
+        Ok(Value::from(encoded_hash))
     }
 
     #[builtin("head")]
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index db02093b8d..652252dadf 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -229,6 +229,10 @@ pub enum ErrorKind {
     /// tvix-eval when returning a result to the user, never inside of
     /// eval code.
     CatchableError(CatchableErrorKind),
+
+    /// Invalid hash type specified, must be one of "md5", "sha1", "sha256"
+    /// or "sha512"
+    UnknownHashType(String),
 }
 
 impl error::Error for Error {
@@ -533,6 +537,10 @@ to a missing value in the attribute set(s) included via `with`."#,
             ErrorKind::CatchableError(inner) => {
                 write!(f, "{}", inner)
             }
+
+            ErrorKind::UnknownHashType(hash_type) => {
+                write!(f, "unknown hash type '{}'", hash_type)
+            }
         }
     }
 }
@@ -821,6 +829,7 @@ impl Error {
             | ErrorKind::TvixBug { .. }
             | ErrorKind::NotImplemented(_)
             | ErrorKind::WithContext { .. }
+            | ErrorKind::UnknownHashType(_)
             | ErrorKind::CatchableError(_) => return None,
         };
 
@@ -866,6 +875,7 @@ impl Error {
             ErrorKind::NotSerialisableToJson(_) => "E036",
             ErrorKind::UnexpectedContext => "E037",
             ErrorKind::Utf8 => "E038",
+            ErrorKind::UnknownHashType(_) => "E039",
 
             // Special error code for errors from other Tvix
             // components. We may want to introduce a code namespacing
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-groupBy.exp b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp
index bfca5652a5..bfca5652a5 100644
--- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-groupBy.exp
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-groupBy.nix b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix
index 7e0eab28b0..862d89dbd6 100644
--- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-groupBy.nix
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix
@@ -1,4 +1,4 @@
-with import ./../lib.nix;
+with import ./lib.nix;
 
 builtins.groupBy (n:
   builtins.substring 0 1 (builtins.hashString "sha256" (toString n))
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-hashstring.exp b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp
index d720a082dd..d720a082dd 100644
--- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-hashstring.exp
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-hashstring.nix b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix
index b0f62b245c..b0f62b245c 100644
--- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-hashstring.nix
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp
new file mode 100644
index 0000000000..e00b80e561
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp
@@ -0,0 +1 @@
+[ "8a0614b4eaa4cffb7515ec101847e198" "8bd218cf61321d8aa05b3602b99f90d2d8cef3d6" "80ac06d74cb6c5d14af718ce8c3c1255969a1a595b76a3cf92354a95331a879a" "0edac513b6b0454705b553deda4c9b055da0939d26d2f73548862817ebeac5378cf64ff7a752ce1a0590a736735d3bbd9e8a7f04d93617cdf514313f5ab5baa4" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix
new file mode 100644
index 0000000000..aed723d367
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix
@@ -0,0 +1,6 @@
+[
+  (builtins.hashString "md5" "tvix")
+  (builtins.hashString "sha1" "tvix")
+  (builtins.hashString "sha256" "tvix")
+  (builtins.hashString "sha512" "tvix")
+]
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs
index 2f1592546b..6ce0d190c0 100644
--- a/tvix/eval/src/value/string.rs
+++ b/tvix/eval/src/value/string.rs
@@ -524,6 +524,12 @@ impl<'a> From<&'a NixString> for &'a BStr {
     }
 }
 
+impl From<NixString> for String {
+    fn from(s: NixString) -> Self {
+        s.to_string()
+    }
+}
+
 impl From<NixString> for BString {
     fn from(s: NixString) -> Self {
         s.as_bstr().to_owned()
diff --git a/web/tvixbolt/Cargo.lock b/web/tvixbolt/Cargo.lock
index 7c7afb63f9..d3c5faf10c 100644
--- a/web/tvixbolt/Cargo.lock
+++ b/web/tvixbolt/Cargo.lock
@@ -36,6 +36,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403"
 
 [[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
 name = "boolinator"
 version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -103,6 +112,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
 
 [[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
 name = "dirs"
 version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -174,6 +218,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc"
 
 [[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
 name = "getrandom"
 version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -459,6 +513,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
+[[package]]
 name = "memchr"
 version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -726,6 +790,28 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
 name = "slab"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -857,12 +943,14 @@ dependencies = [
  "bytes",
  "codemap",
  "codemap-diagnostic",
+ "data-encoding",
  "dirs",
  "genawaiter",
  "imbl",
  "itertools",
  "lazy_static",
  "lexical-core",
+ "md-5",
  "os_str_bytes",
  "path-clean",
  "regex",
@@ -870,6 +958,8 @@ dependencies = [
  "rowan",
  "serde",
  "serde_json",
+ "sha1",
+ "sha2",
  "smol_str",
  "tabwriter",
  "toml",
@@ -902,6 +992,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
 name = "unicode-ident"
 version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"