rstango 0.4.0

Implementation of the Tango distributed control system
Documentation
//! The data a command takes and gives, which travels in an `any`.

use crate::value::{Spectrum, Value, ValueFormat};
use crate::error::{Error, ErrorKind, Result};
use crate::dev::DevState;

use super::super::orb::cdr::{CdrReader, CdrWriter};
use super::{
    read_encoded, read_scalar, read_seq, read_spectrum, write_any_string_array_tc, write_array_tc,
    write_encoded, write_encoded_tc, write_scalar_tc, write_seq, CdrTangoElement,
    TK_ALIAS, TK_BOOL, TK_DOUBLE, TK_ENUM, TK_FLOAT, TK_LONG, TK_LONGLONG, TK_NULL, TK_OCTET,
    TK_SHORT, TK_STRING, TK_STRUCT, TK_ULONG, TK_ULONGLONG, TK_USHORT,
};


/// Write the `any` a command travels in.
pub fn write_command_data(buf: &mut CdrWriter, value: &Value) -> Result<()> {

    fn format<T: CdrTangoElement + ?Sized>(buf: &mut CdrWriter, format: &ValueFormat<T>) -> Result<()> {
        match format {
            ValueFormat::Scalar(value) => {
                write_scalar_tc(buf, T::TK)?;
                T::write_values(buf, value)
            }
            ValueFormat::Spectrum(values) => {
                write_array_tc(buf, T::TK, T::ARRAY_REPO_ID)?;
                write_seq(buf, values)
            }
            ValueFormat::Image(_) => Err(Error::new(ErrorKind::InvalidData)
                .with_description("Image data cannot be used as command parameter")),
        }
    }

    fn long_double_string<T: CdrTangoElement>(buf: &mut CdrWriter, repo_id: &str, name: &str, values: &Spectrum<T>, strings: &Spectrum<str>) -> Result<()> {
        write_any_string_array_tc(buf, repo_id, name, T::TK, T::ARRAY_REPO_ID)?;
        write_seq(buf, values)?;
        write_seq(buf, strings)
    }

    // Note that enumerations are actually not supported for commands, but we just send the
    // underlying representation anyway.
    match value {
        Value::Bool(values) => format(buf, values),
        Value::Octet(values) => format(buf, values),
        Value::UShort(values) => format(buf, values),
        Value::Short(values) => format(buf, values),
        Value::ULong(values) => format(buf, values),
        Value::Long(values) => format(buf, values),
        Value::ULong64(values) => format(buf, values),
        Value::Long64(values) => format(buf, values),
        Value::Float(values) => format(buf, values),
        Value::Double(values) => format(buf, values),
        Value::Enum(values) => format(buf, values),
        Value::State(values) => format(buf, values),
        Value::String(values) => format(buf, values),
        Value::Void => write_scalar_tc(buf, TK_NULL),
        Value::LongString(longs, strings) => 
            long_double_string(buf, tango_repo_id!("DevVarLongStringArray"), "lvalue", longs, strings),
        Value::DoubleString(doubles, strings) => 
            long_double_string(buf, tango_repo_id!("DevVarDoubleStringArray"), "dvalue", doubles, strings),
        Value::Encoded(encoded) => {
            write_encoded_tc(buf)?;
            write_encoded(buf, encoded)
        }
    }

}

/// Read the `any` a command travels in, out of the octets it was received in: its arrays
/// and strings share them, see [`CdrReader::read_buf`].
///
/// The reader is left right past the value, so that the arguments a later version of the
/// operation carries after it can be read out of it.
pub fn read_command_data(buf: &mut CdrReader<'_>) -> Result<Value> {

    Ok(match buf.read_ulong()? {
        TK_NULL => Value::Void,
        TK_BOOL => read_scalar::<bool>(buf)?,
        TK_OCTET => read_scalar::<u8>(buf)?,
        TK_SHORT => read_scalar::<i16>(buf)?,
        TK_USHORT => read_scalar::<u16>(buf)?,
        TK_LONG => read_scalar::<i32>(buf)?,
        TK_ULONG => read_scalar::<u32>(buf)?,
        TK_LONGLONG => read_scalar::<i64>(buf)?,
        TK_ULONGLONG => read_scalar::<u64>(buf)?,
        TK_FLOAT => read_scalar::<f32>(buf)?,
        TK_DOUBLE => read_scalar::<f64>(buf)?,
        TK_STRING => {
            let _max_len = buf.read_ulong()?;
            read_scalar::<str>(buf)?
        }
        TK_ENUM => {
            // tk_enum, whose parameters are an encapsulation read in place.
            let repo_id = buf.read_encapsulation()?.read_string()?;
            if repo_id != tango_repo_id!("DevState") {
                return Err(Error::new(ErrorKind::InvalidData)
                    .with_description(format!("Enum repository ID {repo_id:?} is not accepted in Tango command data"))
                    .with_append_description("Expected: DevState"))
            }
            read_scalar::<DevState>(buf)?
        }
        TK_STRUCT => {
            // tk_struct: only the Tango composite types, the two numeric and string arrays
            // and the encoded value.
            let repo_id = buf.read_encapsulation()?.read_string()?;
            match &*repo_id {
                tango_repo_id!("DevVarLongStringArray") => Value::LongString(read_seq(buf)?, read_seq(buf)?),
                tango_repo_id!("DevVarDoubleStringArray") => Value::DoubleString(read_seq(buf)?, read_seq(buf)?),
                tango_repo_id!("DevEncoded") => Value::Encoded(read_encoded(buf)?),
                repo_id => return Err(Error::new(ErrorKind::InvalidData)
                    .with_description(format!("Structure repository ID {repo_id:?} is not accepted in Tango command data"))
                    .with_append_description("Expected: DevVarLongStringArray, DevVarDoubleStringArray or DevEncoded")),
            }
        }
        TK_ALIAS => {
            // tk_alias, whose parameters are an encapsulation read in place.
            let repo_id = buf.read_encapsulation()?.read_string()?;
            match &*repo_id {
                tango_repo_id!("DevVarBooleanArray") => read_spectrum::<bool>(buf)?,
                tango_repo_id!("DevVarCharArray") => read_spectrum::<u8>(buf)?,
                tango_repo_id!("DevVarUShortArray") => read_spectrum::<u16>(buf)?,
                tango_repo_id!("DevVarShortArray") => read_spectrum::<i16>(buf)?,
                tango_repo_id!("DevVarULongArray") => read_spectrum::<u32>(buf)?,
                tango_repo_id!("DevVarLongArray") => read_spectrum::<i32>(buf)?,
                tango_repo_id!("DevVarULong64Array") => read_spectrum::<u64>(buf)?,
                tango_repo_id!("DevVarLong64Array") => read_spectrum::<i64>(buf)?,
                tango_repo_id!("DevVarFloatArray") => read_spectrum::<f32>(buf)?,
                tango_repo_id!("DevVarDoubleArray") => read_spectrum::<f64>(buf)?,
                tango_repo_id!("DevVarStateArray") => read_spectrum::<DevState>(buf)?,
                tango_repo_id!("DevVarStringArray") => read_spectrum::<str>(buf)?,
                repo_id => return Err(Error::new(ErrorKind::InvalidData)
                    .with_description(format!("Alias repository ID {repo_id:?} is not accepted in Tango command data"))
                    .with_append_description("Expected: DevVar*Array")),
            }
        }
        tk => return Err(Error::new(ErrorKind::InvalidData)
            .with_description(format!("Typecode kind {tk} is not accepted in Tango command data"))),
    })

}


#[cfg(test)]
mod tests {

    use super::*;

    use crate::buf::Buf;
    use crate::value::DevEncoded;
    use super::super::TK_SEQUENCE;

    /// Check the typecode of one of the two composite Tango types, the way the CORBA
    /// specification lays a `tk_struct` out: a repository ID, a name, the number of members,
    /// and then that many pairs of a member name and a member typecode.
    ///
    /// The parameters are checked to hold exactly that and nothing more, because a parameter
    /// that is missing does not shorten the stream, it makes every following one be read out
    /// of the wrong octets, which is what a server rejects the whole request for.
    fn check_composite_typecode(reader: &mut CdrReader<'_>, repo_id: &str, first_name: &str, first_repo_id: &str) {

        assert_eq!(reader.read_ulong().unwrap(), TK_STRUCT);

        let mut param = reader.read_encapsulation().unwrap();
        assert_eq!(param.read_string().unwrap(), repo_id);
        assert_eq!(param.read_string().unwrap(), type_name(repo_id));

        // The number of members, without which a server answers the requests carrying one
        // of these types with a marshalling error.
        assert_eq!(param.read_sequence_len().unwrap(), 2);

        assert_eq!(param.read_string().unwrap(), first_name);
        check_array_typecode(&mut param, first_repo_id);

        assert_eq!(param.read_string().unwrap(), "svalue");
        check_array_typecode(&mut param, tango_repo_id!("DevVarStringArray"));

        assert!(param.is_empty(), "the parameters of the structure hold more than the two members they declare");

    }

    /// Check the typecode of one of the Tango array types, which is a `tk_alias` naming a
    /// `tk_sequence` of the element type.
    fn check_array_typecode(reader: &mut CdrReader<'_>, repo_id: &str) {

        assert_eq!(reader.read_ulong().unwrap(), TK_ALIAS);

        let mut param = reader.read_encapsulation().unwrap();
        assert_eq!(param.read_string().unwrap(), repo_id);
        assert_eq!(param.read_string().unwrap(), type_name(repo_id));

        assert_eq!(param.read_ulong().unwrap(), TK_SEQUENCE);
        let mut sequence = param.read_encapsulation().unwrap();

        // The element type, of which only a string has a parameter of its own, the maximum
        // length that makes it a bounded one.
        if sequence.read_ulong().unwrap() == TK_STRING {
            assert_eq!(sequence.read_ulong().unwrap(), 0, "the strings should be unbounded");
        }

        assert_eq!(sequence.read_ulong().unwrap(), 0, "the sequence should be unbounded");
        assert!(sequence.is_empty(), "the parameters of the sequence hold more than an element type and a bound");
        assert!(param.is_empty(), "the parameters of the alias hold more than a name and a type");

    }

    /// The name a Tango repository ID gives the type, which is what it holds between the
    /// `IDL:Tango/` prefix and the `:1.0` version.
    fn type_name(repo_id: &str) -> &str {
        &repo_id[10..repo_id.len() - 4]
    }

    /// A `DevVarLongStringArray` command argument goes out as a `tk_struct` of two arrays,
    /// and comes back out of the very same octets.
    #[test]
    fn long_string_array() {

        let data = Value::from((Buf::from(&[1i32, 2, 3][..]), Buf::from(&["one", "two"][..])));

        let mut writer = CdrWriter::new();
        write_command_data(&mut writer, &data).unwrap();
        let buf = writer.into_buf().freeze();

        let mut reader = CdrReader::new(&buf);
        check_composite_typecode(
            &mut reader,
            tango_repo_id!("DevVarLongStringArray"),
            "lvalue",
            tango_repo_id!("DevVarLongArray"));

        assert_eq!(read_command_data(&mut CdrReader::new(&buf)).unwrap(), data);

    }

    /// A `DevEncoded` command argument goes out as the `tk_struct` the IDL declares, its
    /// format being a `DevString` alias, and comes back sharing the octets of the message.
    #[test]
    fn encoded_command() {

        let data = Value::from(DevEncoded::new("JPEG_RGB", vec![0xFFu8, 0xD8, 0xFF]));

        let mut writer = CdrWriter::new();
        write_command_data(&mut writer, &data).unwrap();
        let buf = writer.into_buf().freeze();

        let mut reader = CdrReader::new(&buf);
        assert_eq!(reader.read_ulong().unwrap(), TK_STRUCT);
        let mut param = reader.read_encapsulation().unwrap();
        assert_eq!(param.read_string().unwrap(), tango_repo_id!("DevEncoded"));
        assert_eq!(param.read_string().unwrap(), "DevEncoded");
        assert_eq!(param.read_sequence_len().unwrap(), 2);

        assert_eq!(param.read_string().unwrap(), "encoded_format");
        assert_eq!(param.read_ulong().unwrap(), TK_ALIAS);
        let mut alias = param.read_encapsulation().unwrap();
        assert_eq!(alias.read_string().unwrap(), tango_repo_id!("DevString"));
        assert_eq!(alias.read_string().unwrap(), "DevString");
        assert_eq!(alias.read_ulong().unwrap(), TK_STRING);
        assert_eq!(alias.read_ulong().unwrap(), 0, "the string should be unbounded");
        assert!(alias.is_empty(), "the parameters of the alias hold more than a name and a type");

        assert_eq!(param.read_string().unwrap(), "encoded_data");
        check_array_typecode(&mut param, tango_repo_id!("DevVarCharArray"));
        assert!(param.is_empty(), "the parameters of the structure hold more than the two members they declare");

        let back = read_command_data(&mut CdrReader::new(&buf)).unwrap();
        assert!(!buf.is_unique(), "the value was copied out of the message");
        assert_eq!(back, data);

    }

    /// Same as above for the other composite type, which only differs by the type of its
    /// first array and by the name that array goes out under.
    #[test]
    fn double_string_array() {

        let data = Value::from((Buf::from(&[1.5f64, 2.5][..]), Buf::from(&["one"][..])));

        let mut writer = CdrWriter::new();
        write_command_data(&mut writer, &data).unwrap();
        let buf = writer.into_buf().freeze();

        let mut reader = CdrReader::new(&buf);
        check_composite_typecode(
            &mut reader,
            tango_repo_id!("DevVarDoubleStringArray"),
            "dvalue",
            tango_repo_id!("DevVarDoubleArray"));

        assert_eq!(read_command_data(&mut CdrReader::new(&buf)).unwrap(), data);

    }

}