haem-frames 0.1.1

Shared framing and embedding messages for isolated workers
Documentation
use super::{Error, FailedCode};

const DIGITS: &[u8; 16] = b"0123456789abcdef";

/// Encode finite float members as lowercase little-endian hexadecimal.
///
/// # Errors
/// Returns `embed_limits` if the text size overflows, or
/// `embed_output_invalid` if a member is not finite.
pub fn encode_vector(vector: &[f32]) -> Result<String, Error> {
    let capacity = vector
        .len()
        .checked_mul(8)
        .ok_or_else(|| Error::new(FailedCode::EmbedLimits, "vector text length overflows"))?;
    let mut text = String::with_capacity(capacity);
    for member in vector {
        if !member.is_finite() {
            return Err(Error::new(
                FailedCode::EmbedOutputInvalid,
                "vector member is not finite",
            ));
        }
        for byte in member.to_le_bytes() {
            text.push(char::from(DIGITS[usize::from(byte >> 4)]));
            text.push(char::from(DIGITS[usize::from(byte & 15)]));
        }
    }
    Ok(text)
}

fn nibble(byte: u8) -> Result<u8, Error> {
    match byte {
        b'0'..=b'9' => Ok(byte - b'0'),
        b'a'..=b'f' => Ok(byte - b'a' + 10),
        _ => Err(Error::new(
            FailedCode::EmbedProtocol,
            "vector text must contain only lowercase hexadecimal",
        )),
    }
}

/// Decode lowercase little-endian hexadecimal into finite float members.
///
/// # Errors
/// Returns `embed_protocol` for malformed text, or `embed_output_invalid`
/// if a decoded member is not finite.
pub fn decode_vector(text: &str) -> Result<Vec<f32>, Error> {
    let (words, rest) = text.as_bytes().as_chunks::<8>();
    if !rest.is_empty() {
        return Err(Error::new(
            FailedCode::EmbedProtocol,
            "vector text length must be a multiple of eight",
        ));
    }
    words
        .iter()
        .map(|word| {
            let mut bytes = [0; 4];
            for (byte, pair) in bytes.iter_mut().zip(word.as_chunks::<2>().0) {
                *byte = (nibble(pair[0])? << 4) | nibble(pair[1])?;
            }
            let member = f32::from_le_bytes(bytes);
            if !member.is_finite() {
                return Err(Error::new(
                    FailedCode::EmbedOutputInvalid,
                    "vector member is not finite",
                ));
            }
            Ok(member)
        })
        .collect()
}