dolang-ext-digest 0.1.0

Digest and hashing extension for the Do language.
Documentation
use dolang::runtime::{
    Args, Error, Instance, Object, Output, Result, Slot, Strand, Type, Value, object::TypeBuilder,
    unpack, value::TypeObject, vm::Builder,
};

pub(crate) struct DigestState;
pub(crate) struct Blake3State(blake3::Hasher);

trait Algorithm: digest::Digest + Default + Clone + 'static {
    const TYPE_NAME: &'static str;
}

pub(crate) struct Digestible<T>(T);

impl Algorithm for md5::Md5 {
    const TYPE_NAME: &'static str = "Md5";
}

impl Algorithm for sha1::Sha1 {
    const TYPE_NAME: &'static str = "Sha1";
}

impl Algorithm for sha2::Sha256 {
    const TYPE_NAME: &'static str = "Sha256";
}

impl Algorithm for sha2::Sha512 {
    const TYPE_NAME: &'static str = "Sha512";
}

fn bytes_arg<'v, 'a, 's>(
    strand: &mut Strand<'v, 's>,
    value: &'a Value<'v>,
    msg: &'v str,
) -> Result<'v, 's, &'a [u8]> {
    value
        .as_u8_slice(strand)
        .ok_or_else(|| Error::type_error(strand, msg))
}

fn digest_single<'v, 's, T: Algorithm>(
    strand: &mut Strand<'v, 's>,
    value: Slot<'v, '_>,
    out: Slot<'v, '_>,
) -> Result<'v, 's, ()> {
    let bytes = bytes_arg(strand, &value, "expected str or bin")?;
    let digest = T::digest(bytes);
    Output::set(strand, out, digest.as_slice());
    Ok(())
}

fn digest_update<'v, 's, T: Algorithm>(
    strand: &mut Strand<'v, 's>,
    this: Instance<'v, '_, Digestible<T>>,
    value: Slot<'v, '_>,
) -> Result<'v, 's, ()> {
    let bytes = bytes_arg(strand, &value, "update: expected str or bin")?;
    this.borrow_mut(strand)?.0.update(bytes);
    Ok(())
}

fn blake3_single<'v, 's>(
    strand: &mut Strand<'v, 's>,
    value: Slot<'v, '_>,
    out: Slot<'v, '_>,
) -> Result<'v, 's, ()> {
    let bytes = bytes_arg(strand, &value, "expected str or bin")?;
    let digest = blake3::hash(bytes);
    Output::set(strand, out, digest.as_slice());
    Ok(())
}

fn blake3_update<'v, 's>(
    strand: &mut Strand<'v, 's>,
    this: Instance<'v, '_, Blake3State>,
    value: Slot<'v, '_>,
) -> Result<'v, 's, ()> {
    let bytes = bytes_arg(strand, &value, "update: expected str or bin")?;
    this.borrow_mut(strand)?.0.update(bytes);
    Ok(())
}

impl<'v> Object<'v> for DigestState {
    const NAME: &'v str = "State";
    const MODULE: &'v str = "digest";
    type Annex = ();
    type Type = ();
    type TypeAnnex = ();
}

impl<'v> Object<'v> for Blake3State {
    const NAME: &'v str = "Blake3";
    const MODULE: &'v str = "digest";
    type Annex = ();
    type Type = ();
    type TypeAnnex = ();

    async fn new<'a, 's>(
        this: Type<'v, Self>,
        strand: &'a mut Strand<'v, 's>,
        args: Args<'v, 'a>,
        out: Slot<'v, 'a>,
    ) -> Result<'v, 's, ()> {
        let ([], []) = unpack!(strand, args, 0, 0)?;
        this.create(strand, Self(blake3::Hasher::new()), out);
        Ok(())
    }

    fn build<'a>(builder: TypeBuilder<'v, 'a, Self>) -> TypeBuilder<'v, 'a, Self> {
        builder
            .supertype(TypeObject::Sink)
            .method("update", async move |this, strand, args, out| {
                let ([value], []) = unpack!(strand, args, 1, 0)?;
                blake3_update(strand, this, value)?;
                Output::set(strand, out, this);
                Ok(())
            })
            .method("digest", async move |this, strand, args, out| {
                let ([], []) = unpack!(strand, args, 0, 0)?;
                let digest = this.borrow(strand)?.0.clone().finalize();
                Output::set(strand, out, digest.as_slice());
                Ok(())
            })
    }

    async fn output<'a, 's>(
        this: Instance<'v, 'a, Self>,
        strand: &'a mut Strand<'v, 's>,
        out: Slot<'v, 'a>,
    ) -> Result<'v, 's, ()> {
        Output::set(strand, out, this);
        Ok(())
    }

    async fn put<'a, 's>(
        this: Instance<'v, 'a, Self>,
        strand: &'a mut Strand<'v, 's>,
        value: Slot<'v, 'a>,
    ) -> Result<'v, 's, ()> {
        blake3_update(strand, this, value)
    }
}

impl<'v, T: Algorithm> Object<'v> for Digestible<T> {
    const NAME: &'v str = T::TYPE_NAME;
    const MODULE: &'v str = "digest";
    type Annex = ();
    type Type = ();
    type TypeAnnex = ();

    async fn new<'a, 's>(
        this: Type<'v, Self>,
        strand: &'a mut Strand<'v, 's>,
        args: Args<'v, 'a>,
        out: Slot<'v, 'a>,
    ) -> Result<'v, 's, ()> {
        let ([], []) = unpack!(strand, args, 0, 0)?;
        this.create(strand, Self(T::default()), out);
        Ok(())
    }

    fn build<'a>(builder: TypeBuilder<'v, 'a, Self>) -> TypeBuilder<'v, 'a, Self> {
        builder
            .supertype(TypeObject::Sink)
            .method("update", async move |this, strand, args, out| {
                let ([value], []) = unpack!(strand, args, 1, 0)?;
                digest_update(strand, this, value)?;
                Output::set(strand, out, this);
                Ok(())
            })
            .method("digest", async move |this, strand, args, out| {
                let ([], []) = unpack!(strand, args, 0, 0)?;
                let digest = this.borrow(strand)?.0.clone().finalize();
                Output::set(strand, out, digest.as_slice());
                Ok(())
            })
    }

    async fn output<'a, 's>(
        this: Instance<'v, 'a, Self>,
        strand: &'a mut Strand<'v, 's>,
        out: Slot<'v, 'a>,
    ) -> Result<'v, 's, ()> {
        Output::set(strand, out, this);
        Ok(())
    }

    async fn put<'a, 's>(
        this: Instance<'v, 'a, Self>,
        strand: &'a mut Strand<'v, 's>,
        value: Slot<'v, 'a>,
    ) -> Result<'v, 's, ()> {
        digest_update(strand, this, value)
    }
}

pub(crate) fn configure_vm<'v>(builder: &mut Builder<'v>) {
    let state = builder.register_type::<DigestState>();
    let blake3 = builder
        .build_type::<Blake3State>((), ())
        .nominal_supertype(state)
        .build();
    let md5 = builder
        .build_type::<Digestible<md5::Md5>>((), ())
        .nominal_supertype(state)
        .build();
    let sha1 = builder
        .build_type::<Digestible<sha1::Sha1>>((), ())
        .nominal_supertype(state)
        .build();
    let sha256 = builder
        .build_type::<Digestible<sha2::Sha256>>((), ())
        .nominal_supertype(state)
        .build();
    let sha512 = builder
        .build_type::<Digestible<sha2::Sha512>>((), ())
        .nominal_supertype(state)
        .build();

    builder
        .module("digest")
        .value("State", state)
        .value("Blake3", blake3)
        .value("Md5", md5)
        .value("Sha1", sha1)
        .value("Sha256", sha256)
        .value("Sha512", sha512)
        .function("blake3", async move |strand, args, out| {
            let ([value], []) = unpack!(strand, args, 1, 0)?;
            blake3_single(strand, value, out)
        })
        .function("md5", async move |strand, args, out| {
            let ([value], []) = unpack!(strand, args, 1, 0)?;
            digest_single::<md5::Md5>(strand, value, out)
        })
        .function("sha1", async move |strand, args, out| {
            let ([value], []) = unpack!(strand, args, 1, 0)?;
            digest_single::<sha1::Sha1>(strand, value, out)
        })
        .function("sha256", async move |strand, args, out| {
            let ([value], []) = unpack!(strand, args, 1, 0)?;
            digest_single::<sha2::Sha256>(strand, value, out)
        })
        .function("sha512", async move |strand, args, out| {
            let ([value], []) = unpack!(strand, args, 1, 0)?;
            digest_single::<sha2::Sha512>(strand, value, out)
        })
        .commit();
}