prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use core::str;

use rmp::Marker;

use super::core::validate_request_ref;
use super::{RnxCodecError, RnxField};
use crate::message_pack::{MessagePackInteger, MessagePackReader};
use crate::rnx::{
    ExecutionRequestRef, MAX_COMMAND_BYTES, MAX_EXECUTION_REQUEST_BYTES, MAX_STDIN_BYTES,
};

pub fn decode_execution_request_ref(
    input: &[u8],
) -> Result<ExecutionRequestRef<'_>, RnxCodecError> {
    if input.len() > MAX_EXECUTION_REQUEST_BYTES {
        return Err(RnxCodecError::InvalidField(RnxField::Stdin));
    }
    let mut reader = MessagePackReader::new(input);
    let root_marker = marker(&mut reader)?;
    if reader
        .array_length(root_marker)
        .map_err(|_| RnxCodecError::MalformedMessagePack)?
        != Some(5)
    {
        return if matches!(
            root_marker,
            Marker::FixArray(_) | Marker::Array16 | Marker::Array32
        ) {
            Err(RnxCodecError::WrongFieldCount)
        } else {
            Err(RnxCodecError::ExpectedArray)
        };
    }
    let command_marker = marker(&mut reader)?;
    if !MessagePackReader::is_binary(command_marker) {
        return Err(RnxCodecError::InvalidField(RnxField::Command));
    }
    let command = reader
        .binary(command_marker)
        .map_err(|_| RnxCodecError::MalformedMessagePack)?
        .filter(|command| command.len() <= MAX_COMMAND_BYTES)
        .and_then(|command| str::from_utf8(command).ok())
        .ok_or(RnxCodecError::InvalidField(RnxField::Command))?;
    let timeout_seconds = decode_optional_number(&mut reader, RnxField::Timeout)?;
    let stdout_limit = decode_optional_unsigned(&mut reader, RnxField::StdoutLimit)?;
    let stderr_limit = decode_optional_unsigned(&mut reader, RnxField::StderrLimit)?;
    let stdin_marker = marker(&mut reader)?;
    let stdin = if stdin_marker == Marker::Null {
        None
    } else {
        if !MessagePackReader::is_binary(stdin_marker) {
            return Err(RnxCodecError::InvalidField(RnxField::Stdin));
        }
        Some(
            reader
                .binary(stdin_marker)
                .map_err(|_| RnxCodecError::MalformedMessagePack)?
                .filter(|stdin| stdin.len() <= MAX_STDIN_BYTES)
                .ok_or(RnxCodecError::InvalidField(RnxField::Stdin))?,
        )
    };
    if !reader.is_finished() {
        return Err(RnxCodecError::MalformedMessagePack);
    }
    let request = ExecutionRequestRef {
        command,
        timeout_seconds,
        stdout_limit,
        stderr_limit,
        stdin,
    };
    validate_request_ref(request)?;
    Ok(request)
}

fn marker(reader: &mut MessagePackReader<'_>) -> Result<Marker, RnxCodecError> {
    reader
        .marker()
        .map_err(|_| RnxCodecError::MalformedMessagePack)
}

fn decode_optional_number(
    reader: &mut MessagePackReader<'_>,
    field: RnxField,
) -> Result<Option<f64>, RnxCodecError> {
    let marker = marker(reader)?;
    if marker == Marker::Null {
        return Ok(None);
    }
    if !MessagePackReader::is_integer(marker) && !matches!(marker, Marker::F32 | Marker::F64) {
        return Err(RnxCodecError::InvalidField(field));
    }
    let number = match reader
        .integer(marker)
        .map_err(|_| RnxCodecError::MalformedMessagePack)?
    {
        Some(MessagePackInteger::Negative(value)) => value as f64,
        Some(MessagePackInteger::Nonnegative(value)) => value as f64,
        None => reader
            .float(marker)
            .map_err(|_| RnxCodecError::MalformedMessagePack)?
            .ok_or(RnxCodecError::InvalidField(field))?,
    };
    if number.is_finite() {
        Ok(Some(number))
    } else {
        Err(RnxCodecError::InvalidField(field))
    }
}

fn decode_optional_unsigned(
    reader: &mut MessagePackReader<'_>,
    field: RnxField,
) -> Result<Option<u64>, RnxCodecError> {
    let marker = marker(reader)?;
    if marker == Marker::Null {
        return Ok(None);
    }
    if !MessagePackReader::is_integer(marker) {
        return Err(RnxCodecError::InvalidField(field));
    }
    match reader
        .integer(marker)
        .map_err(|_| RnxCodecError::MalformedMessagePack)?
    {
        Some(MessagePackInteger::Nonnegative(value)) => Ok(Some(value)),
        _ => Err(RnxCodecError::InvalidField(field)),
    }
}