about summary refs log tree commit diff
path: root/tvix
diff options
context:
space:
mode:
authorPadraic-O-Mhuiris <patrick.morris.310@gmail.com>2024-02-21T16·49+0000
committerPádraic Ó Mhuiris <patrick.morris.310@gmail.com>2024-02-23T16·04+0000
commit5c3065b43a61a5fa019cbbb157984fc5eb81d439 (patch)
tree897a44fdb7da446413276861c13d2a2365ea5f4b /tvix
parentffb134398dedcae6cd13cdf49b2cd57d43793bda (diff)
feat(tvix/eval): implement `builtins.hashString` r/7597
Implements md5, sha1, sha256 and sha512 using the related crates from
the RustCrypto hashes project (https://github.com/RustCrypto/hashes)

Change-Id: I00730dea44ec9ef85309edc27addab0ae88814b8
Reviewed-on: https://cl.tvl.fyi/c/depot/+/11005
Tested-by: BuildkiteCI
Reviewed-by: aspen <root@gws.fyi>
Diffstat (limited to 'tvix')
-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
13 files changed, 167 insertions, 11 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
index 585199440b1e..7995402ead4a 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 b0c36524e472..9ee11cbd94f7 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 4f4551a349d2..e2bd07f8279f 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 39b59437e26b..eff761c7057d 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 131f2b7bb201..119c0bda2dc3 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 db02093b8d65..652252dadfa0 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 bfca5652a59b..bfca5652a59b 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 7e0eab28b036..862d89dbd670 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 d720a082ddb3..d720a082ddb3 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 b0f62b245ca8..b0f62b245ca8 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 000000000000..e00b80e561fb
--- /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 000000000000..aed723d3670a
--- /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 2f1592546b85..6ce0d190c0a0 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()