feat(tvix/eval): implement builtins.hashString

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>
This commit is contained in:
Padraic-O-Mhuiris 2024-02-21 16:49:07 +00:00 committed by Pádraic Ó Mhuiris
parent ffb134398d
commit 5c3065b43a
14 changed files with 263 additions and 11 deletions

View file

@ -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")]

View file

@ -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

View file

@ -1,4 +1,4 @@
with import ./../lib.nix;
with import ./lib.nix;
builtins.groupBy (n:
builtins.substring 0 1 (builtins.hashString "sha256" (toString n))

View file

@ -0,0 +1 @@
[ "8a0614b4eaa4cffb7515ec101847e198" "8bd218cf61321d8aa05b3602b99f90d2d8cef3d6" "80ac06d74cb6c5d14af718ce8c3c1255969a1a595b76a3cf92354a95331a879a" "0edac513b6b0454705b553deda4c9b055da0939d26d2f73548862817ebeac5378cf64ff7a752ce1a0590a736735d3bbd9e8a7f04d93617cdf514313f5ab5baa4" ]

View file

@ -0,0 +1,6 @@
[
(builtins.hashString "md5" "tvix")
(builtins.hashString "sha1" "tvix")
(builtins.hashString "sha256" "tvix")
(builtins.hashString "sha512" "tvix")
]

View file

@ -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()