use std::time::{Duration, SystemTime};
use std::borrow::Cow;
use std::fmt::Debug;
use std::mem;
use crate::value::{AttrDataFormat, AttrDataKind, CommandDataKind, DevEncoded, DevEnum, TangoElement, Value, ValueFormat};
use crate::error::{reason, Error, ErrorKind, ErrorSeverity, Result};
use crate::value::{Scalar, Spectrum};
use crate::buf::Buf;
use crate::dev::{
AttrArchiveEventConfig, AttrChangeEventConfig, AttrConfig, AttrDeltaConfig, AttrInfo,
AttrMemorized, AttrMode, AttrNumber, AttrPeriodicEventConfig, AttrQuality,
AttrRangeConfig, AttrRead, CommandInfo, DevInfo, DevSource, DevState, DisplayLevel, Named, NamedResult};
use super::orb::cdr::{CdrReader, CdrWriter, CdrRead, CdrWrite};
use super::DevVersion;
macro_rules! tango_repo_id {
($ty:literal) => { concat!("IDL:Tango/", $ty, ":1.0") };
}
pub(crate) use tango_repo_id as tango_repo_id;
use uuid::Uuid;
const TK_NULL: u32 = 0;
const TK_VOID: u32 = 1;
const TK_SHORT: u32 = 2;
const TK_LONG: u32 = 3;
const TK_USHORT: u32 = 4;
const TK_ULONG: u32 = 5;
const TK_FLOAT: u32 = 6;
const TK_DOUBLE: u32 = 7;
const TK_BOOL: u32 = 8;
const TK_OCTET: u32 = 10;
const TK_STRUCT: u32 = 15;
const TK_ENUM: u32 = 17;
const TK_STRING: u32 = 18;
const TK_SEQUENCE: u32 = 19;
const TK_ALIAS: u32 = 21;
const TK_LONGLONG: u32 = 23;
const TK_ULONGLONG: u32 = 24;
const ATT_BOOL: u32 = 0;
const ATT_SHORT: u32 = 1;
const ATT_LONG: u32 = 2;
const ATT_LONG64: u32 = 3;
const ATT_FLOAT: u32 = 4;
const ATT_DOUBLE: u32 = 5;
const ATT_UCHAR: u32 = 6;
const ATT_USHORT: u32 = 7;
const ATT_ULONG: u32 = 8;
const ATT_ULONG64: u32 = 9;
const ATT_STRING: u32 = 10;
const ATT_STATE: u32 = 11;
const ATT_DEVICE_STATE: u32 = 12;
const ATT_ENCODED: u32 = 13;
const ATT_NO_DATA: u32 = 14;
const FMT_SCALAR: u32 = 0;
const FMT_SPECTRUM: u32 = 1;
const FMT_IMAGE: u32 = 2;
const FMT_UNKNOWN: u32 = 3;
const DEV_VOID: i32 = 0;
const DEV_BOOLEAN: i32 = 1;
const DEV_SHORT: i32 = 2;
const DEV_LONG: i32 = 3;
const DEV_FLOAT: i32 = 4;
const DEV_DOUBLE: i32 = 5;
const DEV_USHORT: i32 = 6;
const DEV_ULONG: i32 = 7;
const DEV_STRING: i32 = 8;
const DEVVAR_CHARARRAY: i32 = 9;
const DEVVAR_SHORTARRAY: i32 = 10;
const DEVVAR_LONGARRAY: i32 = 11;
const DEVVAR_FLOATARRAY: i32 = 12;
const DEVVAR_DOUBLEARRAY: i32 = 13;
const DEVVAR_USHORTARRAY: i32 = 14;
const DEVVAR_ULONGARRAY: i32 = 15;
const DEVVAR_STRINGARRAY: i32 = 16;
const DEVVAR_LONGSTRINGARRAY: i32 = 17;
const DEVVAR_DOUBLESTRINGARRAY: i32 = 18;
const DEV_STATE: i32 = 19;
const CONST_DEV_STRING: i32 = 20;
const DEVVAR_BOOLEANARRAY: i32 = 21;
const DEV_UCHAR: i32 = 22;
const DEV_LONG64: i32 = 23;
const DEV_ULONG64: i32 = 24;
const DEVVAR_LONG64ARRAY: i32 = 25;
const DEVVAR_ULONG64ARRAY: i32 = 26;
const DEV_ENCODED: i32 = 28;
const DEV_ENUM: i32 = 29;
const DEV_PIPE_BLOB: i32 = 30;
const DEVVAR_STATEARRAY: i32 = 31;
const DEVVAR_ENCODEDARRAY: i32 = 32;
const DATA_TYPE_UNKNOWN: i32 = 100;
const DL_OPERATOR: u32 = 0;
const DL_EXPERT: u32 = 1;
const DL_UNKNOWN: u32 = 2;
const WT_READ: u32 = 0;
const WT_READ_WITH_WRITE: u32 = 1;
const WT_WRITE: u32 = 2;
const WT_READ_WRITE: u32 = 3;
const WT_UNKNOWN: u32 = 4;
const NOT_SPECIFIED: &str = "Not specified";
const NOT_A_NUMBER: &str = "NaN";
const NO_LABEL: &str = "No label";
const NO_DESCRIPTION: &str = "No description";
const NO_STANDARD_UNIT: &str = "No standard unit";
const NO_DISPLAY_UNIT: &str = "No display unit";
const UNINITIALIZED: &str = "Uninitialised";
macro_rules! def_cdr_enum_adapter {
( $enum:ty { $( $variant:ident = $value:literal, )* } ) => {
impl CdrRead<'_> for $enum {
fn read(buf: &mut CdrReader<'_>) -> Result<Self> {
Ok(match buf.read_ulong()? {
$( $value => Self::$variant,)*
id => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!(concat!("Invalid ", stringify!($enum), " numeric value: {}"), id))),
})
}
}
impl CdrWrite for $enum {
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
buf.write_ulong(match self {
$( Self::$variant => $value,)*
})
}
}
};
}
def_cdr_enum_adapter!(ErrorSeverity {
Warning = 0,
Error = 1,
Panic = 2,
});
def_cdr_enum_adapter!(DevState {
On = 0,
Off = 1,
Close = 2,
Open = 3,
Insert = 4,
Extract = 5,
Moving = 6,
Standby = 7,
Fault = 8,
Init = 9,
Running = 10,
Alarm = 11,
Disable = 12,
Unknown = 13,
});
def_cdr_enum_adapter!(DevSource {
Dev = 0,
Cache = 1,
CacheDev = 2,
});
def_cdr_enum_adapter!(AttrQuality {
Valid = 0,
Invalid = 1,
Alarm = 2,
Changing = 3,
Warning = 4,
});
impl CdrRead<'_> for Option<Error> {
fn read(buf: &mut CdrReader<'_>) -> Result<Self> {
let mut top_err = None;
let len = buf.read_sequence_len()?;
for _ in 0..len {
let reason = buf.read_string()?;
let mut err = Error::new(ErrorKind::Reason(&reason));
err.set_severity(buf.read()?);
err.set_description(buf.read_string()?.into_owned());
err.set_origin(buf.read_string()?.into_owned());
if let Some(parent_err) = top_err.take() {
err.set_parent(parent_err);
}
top_err = Some(err);
}
Ok(top_err)
}
}
impl CdrWrite for Option<Error> {
#[inline]
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
buf.write(&self.as_ref())
}
}
impl CdrWrite for Option<&Error> {
#[inline]
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
if let Some(error) = self {
buf.write(error)
} else {
buf.write_sequence_len(0)
}
}
}
impl CdrWrite for Error {
#[inline]
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
let mut chain = Vec::new();
let mut next = Some(self);
while let Some(err) = next {
chain.push(err);
next = err.parent();
}
buf.write_sequence_len(chain.len())?;
for err in chain.iter().rev() {
buf.write_string(err.reason())?;
buf.write(&err.severity())?;
buf.write_string(err.description())?;
buf.write_string(err.origin())?;
}
Ok(())
}
}
impl CdrRead<'_> for Error {
fn read(buf: &mut CdrReader<'_>) -> Result<Self> {
let err = buf.read::<Option<Error>>()?;
err.ok_or_else(|| Error::new(ErrorKind::InvalidData)
.with_description("The given DevFailed should have at least one error"))
}
}
impl CdrRead<'_> for SystemTime {
fn read(buf: &mut CdrReader<'_>) -> Result<Self> {
let tv_sec = buf.read_long()?;
let tv_usec = buf.read_long()?;
let tv_nsec = buf.read_long()?;
let (Ok(secs), Ok(usecs), Ok(nsecs)) = (
u64::try_from(tv_sec),
u64::try_from(tv_usec),
u64::try_from(tv_nsec),
) else {
return Ok(SystemTime::UNIX_EPOCH);
};
let since_epoch = Duration::from_secs(secs)
.checked_add(Duration::from_micros(usecs))
.and_then(|elapsed| elapsed.checked_add(Duration::from_nanos(nsecs)));
Ok(since_epoch
.and_then(|since_epoch| SystemTime::UNIX_EPOCH.checked_add(since_epoch))
.unwrap_or(SystemTime::UNIX_EPOCH))
}
}
impl CdrWrite for SystemTime {
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
let since_epoch = self.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| Error::new(ErrorKind::InvalidData)
.with_description("Cannot write a system time that is before the UNIX epoch"))?;
let tv_sec = i32::try_from(since_epoch.as_secs())
.map_err(|_| Error::new(ErrorKind::InvalidData)
.with_description("Cannot write a system time whose seconds do not fit into a long"))?;
let subsec_nanos = since_epoch.subsec_nanos();
buf.write_long(tv_sec)?;
buf.write_long((subsec_nanos / 1_000) as i32)?;
buf.write_long((subsec_nanos % 1_000) as i32)
}
}
#[derive(Debug,)]
pub enum ClientIdent<'a> {
Cpp {
id: u32,
},
Java {
main_class: Cow<'a, str>,
uuid: Uuid,
},
}
impl CdrWrite for ClientIdent<'_> {
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
match self {
ClientIdent::Cpp { id } => {
buf.write_ulong(0)?;
buf.write_ulong(*id)?;
}
ClientIdent::Java { main_class, uuid } => {
buf.write_ulong(1)?;
buf.write_string(main_class)?;
let (msb, lsb) = uuid.as_u64_pair();
buf.write_ulonglong_slice(&[msb, lsb])?;
}
}
Ok(())
}
}
trait CdrTangoElement: TangoElement {
const TK: u32;
const ARRAY_REPO_ID: &'static str;
const ATT: u32;
const DEV_TYPE: i32;
fn read_values(buf: &mut CdrReader<'_>, len: usize) -> Result<Spectrum<Self>>;
fn write_values<const N: usize>(buf: &mut CdrWriter, data: &Buf<Self, N>) -> Result<()>;
}
macro_rules! impl_cdr_tango_element {
( $( $ty:ty => $tk:ident, $array:literal, $att:ident, $dev:ident; )* ) => {
$(
impl CdrTangoElement for $ty {
const TK: u32 = $tk;
const ARRAY_REPO_ID: &'static str = tango_repo_id!($array);
const ATT: u32 = $att;
const DEV_TYPE: i32 = $dev;
fn read_values(buf: &mut CdrReader<'_>, len: usize) -> Result<Spectrum<Self>> {
buf.read_buf(len)
}
fn write_values<const N: usize>(buf: &mut CdrWriter, data: &Buf<Self, N>) -> Result<()> {
buf.write_data(data)
}
}
)*
};
}
impl_cdr_tango_element! {
bool => TK_BOOL, "DevVarBooleanArray", ATT_BOOL, DEV_BOOLEAN;
u8 => TK_OCTET, "DevVarCharArray", ATT_UCHAR, DEV_UCHAR;
u16 => TK_USHORT, "DevVarUShortArray", ATT_USHORT, DEV_USHORT;
i16 => TK_SHORT, "DevVarShortArray", ATT_SHORT, DEV_SHORT;
u32 => TK_ULONG, "DevVarULongArray", ATT_ULONG, DEV_ULONG;
i32 => TK_LONG, "DevVarLongArray", ATT_LONG, DEV_LONG;
u64 => TK_ULONGLONG, "DevVarULong64Array", ATT_ULONG64, DEV_ULONG64;
i64 => TK_LONGLONG, "DevVarLong64Array", ATT_LONG64, DEV_LONG64;
f32 => TK_FLOAT, "DevVarFloatArray", ATT_FLOAT, DEV_FLOAT;
f64 => TK_DOUBLE, "DevVarDoubleArray", ATT_DOUBLE, DEV_DOUBLE;
DevEnum => TK_SHORT, "DevVarShortArray", ATT_SHORT, DEV_ENUM;
}
impl CdrTangoElement for DevState {
const TK: u32 = TK_ENUM;
const ARRAY_REPO_ID: &'static str = tango_repo_id!("DevVarStateArray");
const ATT: u32 = ATT_STATE;
const DEV_TYPE: i32 = DEV_STATE;
fn read_values(buf: &mut CdrReader<'_>, len: usize) -> Result<Spectrum<Self>> {
buf.read_vec(len).map(Buf::from)
}
fn write_values<const N: usize>(buf: &mut CdrWriter, data: &Buf<Self, N>) -> Result<()> {
data.iter().try_for_each(|state| buf.write(&state))
}
}
impl CdrTangoElement for str {
const TK: u32 = TK_STRING;
const ARRAY_REPO_ID: &'static str = tango_repo_id!("DevVarStringArray");
const ATT: u32 = ATT_STRING;
const DEV_TYPE: i32 = DEV_STRING;
fn read_values(buf: &mut CdrReader<'_>, len: usize) -> Result<Spectrum<Self>> {
buf.read_string_buf(len)
}
fn write_values<const N: usize>(buf: &mut CdrWriter, data: &Buf<Self, N>) -> Result<()> {
buf.write_string_data(data)
}
}
fn read_seq<T: CdrTangoElement + ?Sized>(buf: &mut CdrReader<'_>) -> Result<Spectrum<T>> {
let len = buf.read_sequence_len()?;
T::read_values(buf, len)
}
fn write_seq<T: CdrTangoElement + ?Sized, const N: usize>(buf: &mut CdrWriter, data: &Buf<T, N>) -> Result<()> {
buf.write_sequence_len(data.len())?;
T::write_values(buf, data)
}
fn read_scalar<T: CdrTangoElement + ?Sized>(buf: &mut CdrReader<'_>) -> Result<Value> {
let value = T::read_values(buf, 1)?.into_scalar().expect("a single value was read");
Ok(T::wrap(ValueFormat::Scalar(value)))
}
fn read_spectrum<T: CdrTangoElement + ?Sized>(buf: &mut CdrReader<'_>) -> Result<Value> {
Ok(T::wrap(ValueFormat::Spectrum(read_seq(buf)?)))
}
fn write_scalar_tc(buf: &mut CdrWriter, tk: u32) -> Result<()> {
buf.write_ulong(tk)?;
if tk == TK_STRING {
buf.write_ulong(0)?; } else if tk == TK_ENUM {
buf.write_encapsulation(|param| {
param.write_string(tango_repo_id!("DevState"))?;
param.write_string("DevState")?;
param.write(&["ON", "OFF", "CLOSE", "OPEN", "INSERT", "EXTRACT", "MOVING", "STANDBY", "FAULT", "INIT", "RUNNING", "ALARM", "DISABLE", "UNKNOWN"])
})?;
}
Ok(())
}
fn write_array_tc(buf: &mut CdrWriter, tk: u32, repo_id: &str) -> Result<()> {
let name = &repo_id[10..repo_id.len() - 4];
buf.write_ulong(TK_ALIAS)?;
buf.write_encapsulation(|alias| {
alias.write_string(repo_id)?;
alias.write_string(name)?;
alias.write_ulong(TK_SEQUENCE)?;
alias.write_encapsulation(|sequence| {
write_scalar_tc(sequence, tk)?;
sequence.write_ulong(0) })
})
}
fn write_any_string_array_tc(buf: &mut CdrWriter, repo_id: &str, first_name: &str, first_tk: u32, first_repo_id: &str) -> Result<()> {
let name = &repo_id[10..repo_id.len() - 4];
buf.write_ulong(TK_STRUCT)?;
buf.write_encapsulation(|param| {
param.write_string(repo_id)?;
param.write_string(name)?;
param.write_ulong(2)?;
param.write_string(first_name)?;
write_array_tc(param, first_tk, first_repo_id)?;
param.write_string("svalue")?;
write_array_tc(param, TK_STRING, tango_repo_id!("DevVarStringArray"))
})
}
fn write_encoded_tc(buf: &mut CdrWriter) -> Result<()> {
buf.write_ulong(TK_STRUCT)?;
buf.write_encapsulation(|param| {
param.write_string(tango_repo_id!("DevEncoded"))?;
param.write_string("DevEncoded")?;
param.write_ulong(2)?;
param.write_string("encoded_format")?;
param.write_ulong(TK_ALIAS)?;
param.write_encapsulation(|alias| {
alias.write_string(tango_repo_id!("DevString"))?;
alias.write_string("DevString")?;
write_scalar_tc(alias, TK_STRING)
})?;
param.write_string("encoded_data")?;
write_array_tc(param, u8::TK, u8::ARRAY_REPO_ID)
})
}
fn read_encoded(buf: &mut CdrReader<'_>) -> Result<DevEncoded> {
Ok(DevEncoded { format: buf.read::<Scalar<str>>()?, data: read_seq(buf)? })
}
fn write_encoded(buf: &mut CdrWriter, encoded: &DevEncoded) -> Result<()> {
buf.write_string(encoded.format.value())?;
write_seq(buf, &encoded.data)
}
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)
}
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)
}
}
}
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 => {
let repo_id = buf.read_encapsulation()?.read_string()?;
if repo_id != tango_repo_id!("DevState") {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid enum repository ID for making Tango command data: {repo_id}
Only accepting DevState for TK_ENUM")))
}
read_scalar::<DevState>(buf)?
}
TK_STRUCT => {
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!("Invalid structure repository ID for making a Tango command data: {repo_id}
Only accepting DevVarLongStringArray, DevVarDoubleStringArray or DevEncoded"))),
}
}
TK_ALIAS => {
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!("Invalid alias repository ID for making Tango command data: {repo_id}
Only accepting DevVar*Array for TK_ALIAS"))),
}
}
tk => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid typecode kind for making Tango command data: {tk}"))),
})
}
pub fn read_attr(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<NamedResult<AttrRead<Value>>> {
let mut values = if version <= DevVersion::V3 {
read_attr_values_any(buf)?.map(AttrValues::Flat)
} else {
read_attr_values_union(buf)?
};
let quality = buf.read::<AttrQuality>()?;
if quality == AttrQuality::Invalid {
values = None;
}
let mut format = None;
if version >= DevVersion::V4 {
format = read_attr_data_format(buf)?;
}
if version >= DevVersion::V5 {
let data_type = buf.read_long()?;
if data_type == DEV_ENUM
&& let Some(AttrValues::Flat(Value::Short(ValueFormat::Spectrum(shorts)))) = &values {
let enums = shorts.clone().cast::<DevEnum, 1>(shorts.dim());
values = Some(AttrValues::Flat(Value::from(enums.expect("a short is a valid enumeration"))));
}
}
let time = buf.read::<SystemTime>()?;
let name = buf.read::<Scalar<str>>()?;
let r_x = buf.read_sequence_len()?;
let r_y = buf.read_sequence_len()?;
let mut w_x = 0;
let mut w_y = 0;
let mut err = None;
if version >= DevVersion::V3 {
w_x = buf.read_sequence_len()?;
w_y = buf.read_sequence_len()?;
err = buf.read::<Option<Error>>()?;
}
Ok(Named::new(
name,
if let Some(err) = err {
Err(err)
} else {
let (value, w_value) = match values {
Some(AttrValues::Flat(values)) => split_attr_values(Some(values), format, (r_x, r_y), (w_x, w_y)),
Some(AttrValues::Encoded(parts)) => split_attr_encoded(parts, format, (r_x, r_y), (w_x, w_y)),
None => (None, None),
};
Ok(AttrRead {
value,
w_value,
quality,
time,
})
}
))
}
pub fn write_attr(buf: &mut CdrWriter, version: DevVersion, name: &str, read: &Result<AttrRead<Value>>) -> Result<()> {
let (value, w_value, quality, time) = match read {
Ok(read) => (read.value.as_ref(), read.w_value.as_ref(), read.quality, read.time),
Err(_) => (None, None, AttrQuality::Invalid, SystemTime::UNIX_EPOCH),
};
let (
format,
data_type,
r_dim,
w_dim,
) = write_attr_data(buf, version <= DevVersion::V3, value, w_value)?;
buf.write(&quality)?;
if version >= DevVersion::V4 {
write_attr_data_format(buf, format)?;
}
if version >= DevVersion::V5 {
buf.write_long(data_type)?;
}
buf.write(&time)?;
buf.write_string(name)?;
if version >= DevVersion::V3 {
let r_dim = if value.is_none() && w_value.is_some() { w_dim } else { r_dim };
write_attr_dim(buf, r_dim.0, r_dim.1)?;
write_attr_dim(buf, w_dim.0, w_dim.1)?;
buf.write(&read.as_ref().err())?;
} else {
let (dim_x, dim_y) = if value.is_some() { r_dim } else { w_dim };
write_attr_dim(buf, dim_x, dim_y)?;
}
Ok(())
}
fn read_attr_values_any(buf: &mut CdrReader<'_>) -> Result<Option<Value>> {
let tk = buf.read_ulong()?;
if tk == TK_NULL || tk == TK_VOID {
return Ok(None);
} else if tk == TK_ENUM {
let repo_id = buf.read_encapsulation()?.read_string()?;
if repo_id != tango_repo_id!("DevState") {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid enum repository ID for making Tango attribute data: {repo_id}
Only accepting DevState for TK_ENUM")))
}
return DevState::read_values(buf, 1).map(|state| Some(Value::from(state)));
} else if tk != TK_ALIAS {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid typecode kind for making Tango attribute data: {tk}")));
}
let repo_id = buf.read_encapsulation()?.read_string()?;
let kind = match &*repo_id {
tango_repo_id!("DevVarBooleanArray") => AttrDataKind::Bool,
tango_repo_id!("DevVarCharArray") => AttrDataKind::Octet,
tango_repo_id!("DevVarUShortArray") => AttrDataKind::UShort,
tango_repo_id!("DevVarShortArray") => AttrDataKind::Short,
tango_repo_id!("DevVarULongArray") => AttrDataKind::ULong,
tango_repo_id!("DevVarLongArray") => AttrDataKind::Long,
tango_repo_id!("DevVarULong64Array") => AttrDataKind::ULong64,
tango_repo_id!("DevVarLong64Array") => AttrDataKind::Long64,
tango_repo_id!("DevVarFloatArray") => AttrDataKind::Float,
tango_repo_id!("DevVarDoubleArray") => AttrDataKind::Double,
tango_repo_id!("DevVarStateArray") => AttrDataKind::State,
tango_repo_id!("DevVarStringArray") => AttrDataKind::String,
repo_id => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid alias repository ID for making Tango data: {repo_id}
Only accepting DevVar*Array for TK_ALIAS"))),
};
read_attr_values(buf, kind).map(Some)
}
enum AttrValues {
Flat(Value),
Encoded(Vec<DevEncoded>),
}
fn read_attr_values_union(buf: &mut CdrReader<'_>) -> Result<Option<AttrValues>> {
let discriminant = buf.read_ulong()?;
let kind = match discriminant {
ATT_BOOL => AttrDataKind::Bool,
ATT_SHORT => AttrDataKind::Short,
ATT_LONG => AttrDataKind::Long,
ATT_LONG64 => AttrDataKind::Long64,
ATT_FLOAT => AttrDataKind::Float,
ATT_DOUBLE => AttrDataKind::Double,
ATT_UCHAR => AttrDataKind::Octet,
ATT_USHORT => AttrDataKind::UShort,
ATT_ULONG => AttrDataKind::ULong,
ATT_ULONG64 => AttrDataKind::ULong64,
ATT_STRING => AttrDataKind::String,
ATT_STATE => AttrDataKind::State,
ATT_DEVICE_STATE => return DevState::read_values(buf, 1).map(|state| Some(AttrValues::Flat(Value::from(state)))),
ATT_ENCODED => {
let len = buf.read_sequence_len()?;
let mut parts = Vec::new();
for _ in 0..len {
parts.push(read_encoded(buf)?);
}
return Ok(Some(AttrValues::Encoded(parts)));
}
ATT_NO_DATA => {
let _no_data = buf.read_bool()?;
return Ok(None);
}
_ => return Err(Error::not_implemented()
.with_append_description(format_args!("Unknown attribute value union discriminant: {discriminant}"))),
};
read_attr_values(buf, kind).map(|values| Some(AttrValues::Flat(values)))
}
fn read_attr_values(buf: &mut CdrReader<'_>, kind: AttrDataKind) -> Result<Value> {
match kind {
AttrDataKind::Bool => read_spectrum::<bool>(buf),
AttrDataKind::Octet => read_spectrum::<u8>(buf),
AttrDataKind::UShort => read_spectrum::<u16>(buf),
AttrDataKind::Short => read_spectrum::<i16>(buf),
AttrDataKind::ULong => read_spectrum::<u32>(buf),
AttrDataKind::Long => read_spectrum::<i32>(buf),
AttrDataKind::ULong64 => read_spectrum::<u64>(buf),
AttrDataKind::Long64 => read_spectrum::<i64>(buf),
AttrDataKind::Float => read_spectrum::<f32>(buf),
AttrDataKind::Double => read_spectrum::<f64>(buf),
AttrDataKind::Enum => read_spectrum::<DevEnum>(buf),
AttrDataKind::State => read_spectrum::<DevState>(buf),
AttrDataKind::String => read_spectrum::<str>(buf),
AttrDataKind::Encoded => Err(Error::new(ErrorKind::InvalidData)
.with_description("Encoded attribute values are not a sequence of elements")),
}
}
fn read_attr_data_format<'a>(buf: &mut CdrReader<'a>) -> Result<Option<AttrDataFormat>> {
Ok(Some(match buf.read_ulong()? {
FMT_SCALAR => AttrDataFormat::Scalar,
FMT_SPECTRUM => AttrDataFormat::Spectrum(0),
FMT_IMAGE => AttrDataFormat::Image(0, 0),
FMT_UNKNOWN => return Ok(None),
value => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid attribute data format value: {value}")))
}))
}
fn write_attr_data_format(buf: &mut CdrWriter, format: Option<AttrDataFormat>) -> Result<()> {
buf.write_ulong(match format {
Some(AttrDataFormat::Scalar) => FMT_SCALAR,
Some(AttrDataFormat::Spectrum(_)) => FMT_SPECTRUM,
Some(AttrDataFormat::Image(_, _)) => FMT_IMAGE,
None => FMT_UNKNOWN,
})
}
fn write_attr_values(buf: &mut CdrWriter, value: Option<&Value>) -> Result<(Option<AttrDataFormat>, (usize, usize), usize)> {
fn part<T: CdrTangoElement + ?Sized>(buf: &mut CdrWriter, format: &ValueFormat<T>) -> Result<(Option<AttrDataFormat>, (usize, usize), usize)> {
let (format, dim, len) = match format {
ValueFormat::Scalar(value) => {
T::write_values(buf, value)?;
(AttrDataFormat::Scalar, (1, 0), 1)
}
ValueFormat::Spectrum(values) => {
T::write_values(buf, values)?;
(AttrDataFormat::Spectrum(0), (values.len(), 0), values.len())
}
ValueFormat::Image(values) => {
T::write_values(buf, values)?;
let [x, y] = values.dim();
(AttrDataFormat::Image(0, 0), (x, y), values.len())
}
};
Ok((Some(format), dim, len))
}
let Some(value) = value else {
return Ok((None, (0, 0), 0));
};
match value {
Value::Bool(format) => part(buf, format),
Value::Octet(format) => part(buf, format),
Value::UShort(format) => part(buf, format),
Value::Short(format) => part(buf, format),
Value::ULong(format) => part(buf, format),
Value::Long(format) => part(buf, format),
Value::ULong64(format) => part(buf, format),
Value::Long64(format) => part(buf, format),
Value::Float(format) => part(buf, format),
Value::Double(format) => part(buf, format),
Value::Enum(format) => part(buf, format),
Value::State(format) => part(buf, format),
Value::String(format) => part(buf, format),
value => Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("The given data could not be encoded as a Tango attribute value: {value:?}"))),
}
}
fn write_attr_data(
buf: &mut CdrWriter,
any: bool,
value: Option<&Value>,
w_value: Option<&Value>,
) -> Result<(Option<AttrDataFormat>, i32, (usize, usize), (usize, usize))> {
fn codes<T: CdrTangoElement + ?Sized>(_: &ValueFormat<T>) -> (u32, &'static str, u32, i32) {
(T::TK, T::ARRAY_REPO_ID, T::ATT, T::DEV_TYPE)
}
let Some(first) = value.or(w_value) else {
if any {
buf.write_ulong(TK_NULL)?;
} else {
buf.write_ulong(ATT_NO_DATA)?;
buf.write_bool(false)?;
}
return Ok((None, DATA_TYPE_UNKNOWN, (0, 0), (0, 0)));
};
if let (Some(value), Some(w_value)) = (value, w_value)
&& mem::discriminant(value) != mem::discriminant(w_value) {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("The two directions of an attribute must share their type: {value:?} and {w_value:?}")));
}
if let Value::Encoded(_) = first {
return write_attr_encoded(buf, any, value, w_value);
}
let (tk, array_repo_id, discriminant, data_type) = match first {
Value::Bool(format) => codes(format),
Value::Octet(format) => codes(format),
Value::UShort(format) => codes(format),
Value::Short(format) => codes(format),
Value::ULong(format) => codes(format),
Value::Long(format) => codes(format),
Value::ULong64(format) => codes(format),
Value::Long64(format) => codes(format),
Value::Float(format) => codes(format),
Value::Double(format) => codes(format),
Value::Enum(format) => codes(format),
Value::State(format) => codes(format),
Value::String(format) => codes(format),
value => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("The given data could not be encoded as a Tango attribute value: {value:?}"))),
};
if any {
write_array_tc(buf, tk, array_repo_id)?;
} else {
buf.write_ulong(discriminant)?;
}
let len_pos = buf.position();
buf.write_sequence_len(0)?;
let (r_format, r_dim, r_len) = write_attr_values(buf, value)?;
let (w_format, w_dim, w_len) = write_attr_values(buf, w_value)?;
let end = buf.position();
buf.set_position(len_pos);
buf.write_sequence_len(r_len + w_len)?;
buf.set_position(end);
Ok((r_format.or(w_format), data_type, r_dim, w_dim))
}
fn write_attr_encoded(
buf: &mut CdrWriter,
any: bool,
value: Option<&Value>,
w_value: Option<&Value>,
) -> Result<(Option<AttrDataFormat>, i32, (usize, usize), (usize, usize))> {
if any {
return Err(Error::new(ErrorKind::Reason(reason::API_NotSupportedFeature))
.with_description("The DevEncoded data type is only available to devices of IDL 4 and above"));
}
fn encoded(value: Option<&Value>) -> Option<&DevEncoded> {
match value {
Some(Value::Encoded(encoded)) => Some(encoded),
_ => None,
}
}
let (value, w_value) = (encoded(value), encoded(w_value));
buf.write_ulong(ATT_ENCODED)?;
buf.write_sequence_len(value.is_some() as usize + w_value.is_some() as usize)?;
let mut dim = |part: Option<&DevEncoded>| -> Result<(usize, usize)> {
match part {
Some(part) => write_encoded(buf, part).map(|()| (1, 0)),
None => Ok((0, 0)),
}
};
let r_dim = dim(value)?;
let w_dim = dim(w_value)?;
Ok((Some(AttrDataFormat::Scalar), DEV_ENCODED, r_dim, w_dim))
}
fn write_attr_dim(buf: &mut CdrWriter, dim_x: usize, dim_y: usize) -> Result<()> {
let (Ok(dim_x), Ok(dim_y)) = (i32::try_from(dim_x), i32::try_from(dim_y)) else {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Attribute dimensions do not fit into a long: {dim_x}x{dim_y}")));
};
buf.write_long(dim_x)?;
buf.write_long(dim_y)
}
fn split_attr_values(
values: Option<Value>,
format: Option<AttrDataFormat>,
r_dim: (usize, usize),
w_dim: (usize, usize),
) -> (Option<Value>, Option<Value>) {
fn split<T: TangoElement + ?Sized>(
values: ValueFormat<T>,
format: Option<AttrDataFormat>,
r_dim: (usize, usize),
w_dim: (usize, usize),
) -> (Option<Value>, Option<Value>) {
let Ok(values) = values.into_spectrum() else {
return (None, None);
};
let (r_format, w_format) = attr_formats(format, r_dim, w_dim, values.len());
let mut at = 0;
let mut take = |format: AttrDataFormat| {
let len = match format {
AttrDataFormat::Scalar => 1,
AttrDataFormat::Spectrum(x) => x,
AttrDataFormat::Image(x, y) => x * y,
};
let part = values.slice(at..at + len);
at += len;
T::wrap(match format {
AttrDataFormat::Scalar => ValueFormat::Scalar(part.into_scalar().unwrap()),
AttrDataFormat::Spectrum(_) => ValueFormat::Spectrum(part),
AttrDataFormat::Image(x, y) => ValueFormat::Image(part.reshape([x, y]).unwrap()),
})
};
(r_format.map(&mut take), w_format.map(take))
}
match values {
Some(Value::Bool(values)) => split(values, format, r_dim, w_dim),
Some(Value::Octet(values)) => split(values, format, r_dim, w_dim),
Some(Value::UShort(values)) => split(values, format, r_dim, w_dim),
Some(Value::Short(values)) => split(values, format, r_dim, w_dim),
Some(Value::ULong(values)) => split(values, format, r_dim, w_dim),
Some(Value::Long(values)) => split(values, format, r_dim, w_dim),
Some(Value::ULong64(values)) => split(values, format, r_dim, w_dim),
Some(Value::Long64(values)) => split(values, format, r_dim, w_dim),
Some(Value::Float(values)) => split(values, format, r_dim, w_dim),
Some(Value::Double(values)) => split(values, format, r_dim, w_dim),
Some(Value::Enum(values)) => split(values, format, r_dim, w_dim),
Some(Value::State(values)) => split(values, format, r_dim, w_dim),
Some(Value::String(values)) => split(values, format, r_dim, w_dim),
Some(_) | None => (None, None),
}
}
fn split_attr_encoded(
parts: Vec<DevEncoded>,
format: Option<AttrDataFormat>,
r_dim: (usize, usize),
w_dim: (usize, usize),
) -> (Option<Value>, Option<Value>) {
let (r_format, w_format) = attr_formats(format, r_dim, w_dim, parts.len());
let mut parts = parts.into_iter();
let mut take = |format: AttrDataFormat| match format {
AttrDataFormat::Scalar => parts.next().map(Value::Encoded),
_ => None,
};
(r_format.and_then(&mut take), w_format.and_then(take))
}
fn attr_formats(
format: Option<AttrDataFormat>,
r_dim: (usize, usize),
w_dim: (usize, usize),
len: usize,
) -> (Option<AttrDataFormat>, Option<AttrDataFormat>) {
use AttrDataFormat::{Scalar, Spectrum, Image};
match format {
Some(AttrDataFormat::Scalar) => {
match (r_dim, w_dim, len) {
((1, 0), (0, 0), 1) => ( Some(Scalar),
None
),
((_, _), (1, 0), 1) => ( None,
Some(Scalar),
),
((1, 0), (1, 0), 2) => ( Some(Scalar),
Some(Scalar),
),
_ => (None, None)
}
}
Some(AttrDataFormat::Spectrum(_)) => {
match (r_dim, w_dim) {
((r_x, 0), (0, 0)) if r_x == len => ( Some(Spectrum(r_x)),
None,
),
((_, 0), (w_x, 0)) if w_x == len => ( None,
Some(Spectrum(w_x)),
),
((r_x, 0), (w_x, 0)) if r_x + w_x == len => ( Some(Spectrum(r_x)),
Some(Spectrum(w_x)),
),
_ => (None, None)
}
}
Some(AttrDataFormat::Image(_, _)) => {
match (r_dim, w_dim) {
((r_x, r_y), (0, 0)) if r_x * r_y == len => ( Some(Image(r_x, r_y)),
None,
),
((_, _), (w_x, w_y)) if w_x * w_y == len => ( None,
Some(Image(w_x, w_y)),
),
((r_x, r_y), (w_x, w_y)) if r_x * r_y + w_x * w_y == len => ( Some(Image(r_x, r_y)),
Some(Image(w_x, w_y)),
),
_ => (None, None)
}
}
None => {
match (r_dim, w_dim) {
((_, _), (1, 0)) if len == 1 => ( None,
Some(Scalar),
),
((_, _), (w_x, 0)) if w_x == len => ( None,
Some(Spectrum(w_x)),
),
((_, _), (w_x, w_y)) if w_x * w_y == len => ( None,
Some(Image(w_x, w_y)),
),
((1, 0), (0, 0)) if len == 1 => ( Some(Scalar),
None,
),
((1, 0), (0, 0) | (1, 0)) if len == 2 => ( Some(Scalar),
Some(Scalar),
),
((r_x, 0), (0, 0)) if r_x == len => ( Some(Spectrum(r_x)),
None,
),
((r_x, 0), (w_x, 0)) if r_x + w_x == len => ( Some(Spectrum(r_x)),
Some(Spectrum(w_x)),
),
((r_x, r_y), (0, 0)) if r_x * r_y == len => ( Some(Image(r_x, r_y)),
None,
),
((r_x, r_y), (w_x, w_y)) if r_x * r_y + w_x * w_y == len => ( Some(Image(r_x, r_y)),
Some(Image(w_x, w_y)),
),
_ => (None, None)
}
}
}
}
fn unsupported_data_type_err(data_type: i32) -> Error {
match data_type {
DEVVAR_ENCODEDARRAY => Error::not_implemented()
.with_append_description("DevVarEncodedArray data"),
DEV_PIPE_BLOB => Error::not_implemented()
.with_append_description("Pipe blob data"),
data_type => Error::new(ErrorKind::InvalidData)
.with_description(format!("Unknown Tango data type: {data_type}")),
}
}
fn read_display_level(buf: &mut CdrReader<'_>) -> Result<DisplayLevel> {
Ok(match buf.read_ulong()? {
DL_OPERATOR | DL_UNKNOWN => DisplayLevel::Operator,
DL_EXPERT => DisplayLevel::Expert,
value => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid display level value: {value}"))),
})
}
fn read_prop<'a>(buf: &mut CdrReader<'a>, unset: &str) -> Result<Option<Cow<'static, str>>> {
let value = buf.read_string()?;
Ok(if value.is_empty() || value == unset {
None
} else {
Some(Cow::Owned(value.into_owned()))
})
}
fn read_value_str<'a>(buf: &mut CdrReader<'a>) -> Result<Option<Cow<'a, str>>> {
let value = buf.read_string()?;
Ok(if value.is_empty() || value == NOT_SPECIFIED || value == NOT_A_NUMBER {
None
} else {
Some(value)
})
}
fn read_value_prop<'a>(buf: &mut CdrReader<'a>) -> Result<Option<f64>> {
Ok(read_value_str(buf)?
.and_then(|value| value.trim().parse::<f64>().ok())
.filter(|value| value.is_finite()))
}
fn read_limit_prop<'a>(buf: &mut CdrReader<'a>, kind: LimitKind) -> Result<Option<AttrNumber>> {
let Some(value) = read_value_str(buf)? else {
return Ok(None);
};
let value = value.trim();
let typed = match kind {
LimitKind::Long => value.parse::<i64>().ok().map(AttrNumber::Long),
LimitKind::ULong => value.parse::<u64>().ok().map(AttrNumber::ULong),
LimitKind::Double => None,
};
Ok(typed.or_else(|| value.parse::<f64>().ok()
.filter(|value| value.is_finite())
.map(AttrNumber::Double)))
}
fn limit_kind(data_type: i32) -> LimitKind {
match data_type {
DEV_SHORT | DEV_LONG | DEV_LONG64 => LimitKind::Long,
DEV_UCHAR | DEV_USHORT | DEV_ULONG | DEV_ULONG64 => LimitKind::ULong,
_ => LimitKind::Double,
}
}
#[derive(Debug, Clone, Copy)]
enum LimitKind {
Double,
Long,
ULong,
}
fn read_duration_prop<'a>(buf: &mut CdrReader<'a>, unit: Duration) -> Result<Option<Duration>> {
let Some(value) = read_value_str(buf)? else {
return Ok(None);
};
Ok(value.trim().parse::<u32>().ok().map(|value| unit * value))
}
fn command_data_kind(data_type: i32) -> Result<CommandDataKind> {
Ok(match data_type {
DEV_VOID => CommandDataKind::Void,
DEV_BOOLEAN => CommandDataKind::Bool,
DEV_UCHAR => CommandDataKind::Octet,
DEV_USHORT => CommandDataKind::UShort,
DEV_SHORT => CommandDataKind::Short,
DEV_ULONG => CommandDataKind::ULong,
DEV_LONG => CommandDataKind::Long,
DEV_ULONG64 => CommandDataKind::ULong64,
DEV_LONG64 => CommandDataKind::Long64,
DEV_FLOAT => CommandDataKind::Float,
DEV_DOUBLE => CommandDataKind::Double,
DEV_STATE => CommandDataKind::State,
DEV_STRING | CONST_DEV_STRING => CommandDataKind::String,
DEV_ENUM => CommandDataKind::Short,
DEVVAR_BOOLEANARRAY => CommandDataKind::BoolArray,
DEVVAR_CHARARRAY => CommandDataKind::OctetArray,
DEVVAR_USHORTARRAY => CommandDataKind::UShortArray,
DEVVAR_SHORTARRAY => CommandDataKind::ShortArray,
DEVVAR_ULONGARRAY => CommandDataKind::ULongArray,
DEVVAR_LONGARRAY => CommandDataKind::LongArray,
DEVVAR_ULONG64ARRAY => CommandDataKind::ULong64Array,
DEVVAR_LONG64ARRAY => CommandDataKind::Long64Array,
DEVVAR_FLOATARRAY => CommandDataKind::FloatArray,
DEVVAR_DOUBLEARRAY => CommandDataKind::DoubleArray,
DEVVAR_STATEARRAY => CommandDataKind::StateArray,
DEVVAR_STRINGARRAY => CommandDataKind::StringArray,
DEVVAR_LONGSTRINGARRAY => CommandDataKind::LongStringArray,
DEVVAR_DOUBLESTRINGARRAY => CommandDataKind::DoubleStringArray,
DEV_ENCODED => CommandDataKind::Encoded,
data_type => return Err(unsupported_data_type_err(data_type)),
})
}
fn attr_data_kind(data_type: i32) -> Result<AttrDataKind> {
Ok(match data_type {
DEV_BOOLEAN => AttrDataKind::Bool,
DEV_UCHAR => AttrDataKind::Octet,
DEV_USHORT => AttrDataKind::UShort,
DEV_SHORT => AttrDataKind::Short,
DEV_ULONG => AttrDataKind::ULong,
DEV_LONG => AttrDataKind::Long,
DEV_ULONG64 => AttrDataKind::ULong64,
DEV_LONG64 => AttrDataKind::Long64,
DEV_FLOAT => AttrDataKind::Float,
DEV_DOUBLE => AttrDataKind::Double,
DEV_STATE => AttrDataKind::State,
DEV_STRING | CONST_DEV_STRING => AttrDataKind::String,
DEV_ENUM => AttrDataKind::Enum,
DEV_ENCODED => AttrDataKind::Encoded,
data_type => return Err(unsupported_data_type_err(data_type)),
})
}
pub fn read_command_info<'a>(buf: &mut CdrReader<'a>, version: DevVersion) -> Result<(Cow<'a, str>, Result<CommandInfo>)> {
let name = buf.read_string()?;
let display_level = if version >= DevVersion::V2 {
read_display_level(buf)?
} else {
DisplayLevel::Operator
};
let _cmd_tag = buf.read_long()?;
let in_type = buf.read_long()?;
let out_type = buf.read_long()?;
let in_doc = read_prop(buf, UNINITIALIZED)?;
let out_doc = read_prop(buf, UNINITIALIZED)?;
let info = match (command_data_kind(in_type), command_data_kind(out_type)) {
(Ok(in_kind), Ok(out_kind)) => Ok(CommandInfo {
display_level,
in_kind,
out_kind,
in_doc,
out_doc,
}),
(Err(err), _) |
(_, Err(err)) => Err(err),
};
Ok((name, info))
}
pub fn read_attr_info<'a>(buf: &mut CdrReader<'a>, version: DevVersion) -> Result<(Cow<'a, str>, Result<AttrInfo>)> {
let name = buf.read_string()?;
let writable = buf.read_ulong()?;
let format = read_attr_data_format(buf)?;
let data_type = buf.read_long()?;
let limits = limit_kind(data_type);
let mut memorized = None;
if version >= DevVersion::V5 {
memorized = match (buf.read_bool()?, buf.read_bool()?) {
(false, _) => None,
(true, false) => Some(AttrMemorized::RestoreSetPoint),
(true, true) => Some(AttrMemorized::RestoreAndWrite),
};
}
let max_dim_x = buf.read_long()?;
let max_dim_y = buf.read_long()?;
let max_dim = |value: i32| usize::try_from(value).unwrap_or(0);
let data_format = match format {
Some(AttrDataFormat::Scalar) => AttrDataFormat::Scalar,
Some(AttrDataFormat::Spectrum(_)) => AttrDataFormat::Spectrum(max_dim(max_dim_x)),
Some(AttrDataFormat::Image(_, _)) => AttrDataFormat::Image(max_dim(max_dim_x), max_dim(max_dim_y)),
None => return Err(Error::new(ErrorKind::InvalidData)
.with_description("The device sent an attribute of unknown data format")
.with_append_description(format_args!("Attribute {name:?}"))),
};
let doc = read_prop(buf, NO_DESCRIPTION)?;
let label = read_prop(buf, NO_LABEL)?;
let unit = read_prop(buf, "")?;
let standard_unit = read_prop(buf, NO_STANDARD_UNIT)?;
let display_unit = read_prop(buf, NO_DISPLAY_UNIT)?;
let display_format = read_prop(buf, NOT_SPECIFIED)?;
let range = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
let mut alarm = AttrRangeConfig::default();
if version <= DevVersion::V2 {
alarm = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
}
let write_attr_name = buf.read_string()?;
let mut display_level = DisplayLevel::Operator;
if version >= DevVersion::V2 {
display_level = read_display_level(buf)?;
}
let mut enum_labels = Vec::new();
if version >= DevVersion::V5 {
let _root_attr_name = buf.read_string()?;
enum_labels = buf.read::<Vec<String>>()?;
}
let mut warning = AttrRangeConfig::default();
let mut delta = None;
let mut change_event = AttrChangeEventConfig::default();
let mut periodic_event = AttrPeriodicEventConfig::default();
let mut archive_event = AttrArchiveEventConfig::default();
if version >= DevVersion::V3 {
alarm = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
warning = AttrRangeConfig {
min: read_limit_prop(buf, limits)?,
max: read_limit_prop(buf, limits)?,
};
let delta_time = read_duration_prop(buf, Duration::from_millis(1))?;
let delta_value = read_limit_prop(buf, limits)?;
let _alarm_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
delta = delta_value.map(|value| AttrDeltaConfig {
value,
time: delta_time,
});
change_event = AttrChangeEventConfig {
rel_change: read_value_prop(buf)?,
abs_change: read_value_prop(buf)?,
};
let _change_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
periodic_event = AttrPeriodicEventConfig {
period: read_duration_prop(buf, Duration::from_millis(1))?,
};
let _periodic_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
archive_event = AttrArchiveEventConfig {
rel_change: read_value_prop(buf)?,
abs_change: read_value_prop(buf)?,
period: read_duration_prop(buf, Duration::from_millis(1))?,
};
let _archive_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
}
let extensions = buf.read::<Vec<String>>()?.into_iter()
.map(Cow::Owned)
.collect();
if version >= DevVersion::V3 {
let _sys_extensions = buf.read::<Vec<Cow<'_, str>>>()?;
}
let mode = match writable {
WT_READ => AttrMode::Read,
WT_READ_WITH_WRITE => AttrMode::ReadWithWrite {
write_attr_name: Cow::Owned(write_attr_name.into_owned()),
},
WT_WRITE => AttrMode::Write,
WT_READ_WRITE => AttrMode::ReadWrite,
WT_UNKNOWN => return Err(Error::new(ErrorKind::InvalidData)
.with_description("The device sent an attribute of unknown writability")
.with_append_description(format_args!("Attribute {name:?}"))),
value => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid attribute writability value: {value}"))),
};
let info = attr_data_kind(data_type).map(|data_kind| AttrInfo {
config: AttrConfig {
doc,
label,
unit,
standard_unit: standard_unit.and_then(|value| value.trim().parse::<f64>().ok()),
display_unit,
display_format,
range,
alarm,
warning,
delta,
change_event,
periodic_event,
archive_event,
enum_labels: enum_labels.into_iter().map(Cow::Owned).collect(),
extensions,
},
mode,
data_kind,
data_format,
display_level,
memorized,
});
Ok((name, info))
}
pub fn read_dev_info(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<DevInfo> {
let class = buf.read_string()?.into_owned();
let server_id = buf.read_string()?.into_owned();
let server_host = buf.read_string()?.into_owned();
let server_version = buf.read_long()?;
let doc_url = read_prop(buf, NOT_SPECIFIED)?;
let mut dev_type = None;
if version >= DevVersion::V3 {
dev_type = read_prop(buf, NOT_SPECIFIED)?;
}
let mut version_info = Vec::new();
if version >= DevVersion::V6 {
let len = buf.read_sequence_len()?;
version_info.try_reserve(len)
.map_err(|_| Error::new(ErrorKind::Internal)
.with_description(format!("Could not read the {len} versions of a device info")))?;
for _ in 0..len {
let key = buf.read_string()?.into_owned();
let value = buf.read_string()?.into_owned();
version_info.push((Cow::Owned(key), Cow::Owned(value)));
}
}
Ok(DevInfo {
class,
server_id,
server_host,
server_version,
doc_url,
dev_type,
version_info,
})
}
pub fn write_dev_info(buf: &mut CdrWriter, version: DevVersion, info: &DevInfo) -> Result<()> {
buf.write_string(&info.class)?;
buf.write_string(&info.server_id)?;
buf.write_string(&info.server_host)?;
buf.write_long(info.server_version)?;
write_prop(buf, info.doc_url.as_deref(), NOT_SPECIFIED)?;
if version >= DevVersion::V3 {
write_prop(buf, info.dev_type.as_deref(), NOT_SPECIFIED)?;
}
if version >= DevVersion::V6 {
buf.write_sequence_len(info.version_info.len())?;
for (key, value) in &info.version_info {
buf.write_string(key)?;
buf.write_string(value)?;
}
}
Ok(())
}
pub fn write_command_info(buf: &mut CdrWriter, version: DevVersion, name: &str, info: &CommandInfo) -> Result<()> {
buf.write_string(name)?;
if version >= DevVersion::V2 {
write_display_level(buf, info.display_level)?;
}
buf.write_long(0)?;
buf.write_long(command_data_type(&info.in_kind))?;
buf.write_long(command_data_type(&info.out_kind))?;
write_prop(buf, info.in_doc.as_deref(), UNINITIALIZED)?;
write_prop(buf, info.out_doc.as_deref(), UNINITIALIZED)?;
Ok(())
}
pub fn write_attr_info(buf: &mut CdrWriter, version: DevVersion, name: &str, info: &AttrInfo) -> Result<()> {
let AttrInfo {
config,
mode,
data_kind,
data_format,
display_level,
memorized,
} = info;
buf.write_string(name)?;
buf.write_ulong(match mode {
AttrMode::Read => WT_READ,
AttrMode::ReadWithWrite { .. } => WT_READ_WITH_WRITE,
AttrMode::Write => WT_WRITE,
AttrMode::ReadWrite => WT_READ_WRITE,
})?;
write_attr_data_format(buf, Some(*data_format))?;
let data_type = attr_data_type(data_kind);
buf.write_long(data_type)?;
let limits = limit_kind(data_type);
if version >= DevVersion::V5 {
buf.write_bool(memorized.is_some())?;
buf.write_bool(*memorized == Some(AttrMemorized::RestoreAndWrite))?;
}
let (max_dim_x, max_dim_y) = match data_format {
AttrDataFormat::Scalar => (1, 0),
AttrDataFormat::Spectrum(max_x) => (*max_x, 0),
AttrDataFormat::Image(max_x, max_y) => (*max_x, *max_y),
};
let (Ok(max_dim_x), Ok(max_dim_y)) = (i32::try_from(max_dim_x), i32::try_from(max_dim_y)) else {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Maximum attribute dimensions do not fit into a long: {max_dim_x}x{max_dim_y}"))
.with_append_description(format_args!("Attribute {name:?}")));
};
buf.write_long(max_dim_x)?;
buf.write_long(max_dim_y)?;
write_prop(buf, config.doc.as_deref(), NO_DESCRIPTION)?;
write_prop(buf, config.label.as_deref(), NO_LABEL)?;
write_prop(buf, config.unit.as_deref(), "")?;
match config.standard_unit {
Some(factor) => buf.write_string(&factor.to_string())?,
None => buf.write_string(NO_STANDARD_UNIT)?,
}
write_prop(buf, config.display_unit.as_deref(), NO_DISPLAY_UNIT)?;
write_prop(buf, config.display_format.as_deref(), NOT_SPECIFIED)?;
write_limit_prop(buf, config.range.min, limits)?;
write_limit_prop(buf, config.range.max, limits)?;
if version <= DevVersion::V2 {
write_limit_prop(buf, config.alarm.min, limits)?;
write_limit_prop(buf, config.alarm.max, limits)?;
}
buf.write_string(match mode {
AttrMode::ReadWithWrite { write_attr_name } => write_attr_name,
_ => "",
})?;
if version >= DevVersion::V2 {
write_display_level(buf, *display_level)?;
}
if version >= DevVersion::V5 {
buf.write_string("")?;
buf.write(&config.enum_labels)?;
}
if version >= DevVersion::V3 {
write_limit_prop(buf, config.alarm.min, limits)?;
write_limit_prop(buf, config.alarm.max, limits)?;
write_limit_prop(buf, config.warning.min, limits)?;
write_limit_prop(buf, config.warning.max, limits)?;
write_duration_prop(buf, config.delta.and_then(|delta| delta.time), Duration::from_millis(1))?;
write_limit_prop(buf, config.delta.map(|delta| delta.value), limits)?;
buf.write_sequence_len(0)?;
write_value_prop(buf, config.change_event.rel_change)?;
write_value_prop(buf, config.change_event.abs_change)?;
buf.write_sequence_len(0)?;
write_duration_prop(buf, config.periodic_event.period, Duration::from_millis(1))?;
buf.write_sequence_len(0)?;
write_value_prop(buf, config.archive_event.rel_change)?;
write_value_prop(buf, config.archive_event.abs_change)?;
write_duration_prop(buf, config.archive_event.period, Duration::from_millis(1))?;
buf.write_sequence_len(0)?;
}
buf.write(&config.extensions)?;
if version >= DevVersion::V3 {
buf.write_sequence_len(0)?;
}
Ok(())
}
fn write_display_level(buf: &mut CdrWriter, level: DisplayLevel) -> Result<()> {
buf.write_ulong(match level {
DisplayLevel::Operator => DL_OPERATOR,
DisplayLevel::Expert => DL_EXPERT,
})
}
fn write_prop(buf: &mut CdrWriter, value: Option<&str>, unset: &str) -> Result<()> {
buf.write_string(value.unwrap_or(unset))
}
fn write_value_prop(buf: &mut CdrWriter, value: Option<f64>) -> Result<()> {
match value {
Some(value) => buf.write_string(&value.to_string()),
None => buf.write_string(NOT_SPECIFIED),
}
}
fn write_limit_prop(buf: &mut CdrWriter, value: Option<AttrNumber>, _kind: LimitKind) -> Result<()> {
match value {
Some(value) => buf.write_string(&value.to_string()),
None => buf.write_string(NOT_SPECIFIED),
}
}
fn write_duration_prop(buf: &mut CdrWriter, value: Option<Duration>, unit: Duration) -> Result<()> {
let Some(value) = value else {
return buf.write_string(NOT_SPECIFIED);
};
let unit = unit.as_nanos().max(1);
let count = (value.as_nanos() + unit / 2) / unit;
buf.write_string(&count.max(1).to_string())
}
fn command_data_type(kind: &CommandDataKind) -> i32 {
match kind {
CommandDataKind::Void => DEV_VOID,
CommandDataKind::Bool => DEV_BOOLEAN,
CommandDataKind::Octet => DEV_UCHAR,
CommandDataKind::UShort => DEV_USHORT,
CommandDataKind::Short => DEV_SHORT,
CommandDataKind::ULong => DEV_ULONG,
CommandDataKind::Long => DEV_LONG,
CommandDataKind::ULong64 => DEV_ULONG64,
CommandDataKind::Long64 => DEV_LONG64,
CommandDataKind::Float => DEV_FLOAT,
CommandDataKind::Double => DEV_DOUBLE,
CommandDataKind::State => DEV_STATE,
CommandDataKind::String => DEV_STRING,
CommandDataKind::BoolArray => DEVVAR_BOOLEANARRAY,
CommandDataKind::OctetArray => DEVVAR_CHARARRAY,
CommandDataKind::UShortArray => DEVVAR_USHORTARRAY,
CommandDataKind::ShortArray => DEVVAR_SHORTARRAY,
CommandDataKind::ULongArray => DEVVAR_ULONGARRAY,
CommandDataKind::LongArray => DEVVAR_LONGARRAY,
CommandDataKind::ULong64Array => DEVVAR_ULONG64ARRAY,
CommandDataKind::Long64Array => DEVVAR_LONG64ARRAY,
CommandDataKind::FloatArray => DEVVAR_FLOATARRAY,
CommandDataKind::DoubleArray => DEVVAR_DOUBLEARRAY,
CommandDataKind::StateArray => DEVVAR_STATEARRAY,
CommandDataKind::StringArray => DEVVAR_STRINGARRAY,
CommandDataKind::LongStringArray => DEVVAR_LONGSTRINGARRAY,
CommandDataKind::DoubleStringArray => DEVVAR_DOUBLESTRINGARRAY,
CommandDataKind::Encoded => DEV_ENCODED,
}
}
fn attr_data_type(kind: &AttrDataKind) -> i32 {
match kind {
AttrDataKind::Bool => DEV_BOOLEAN,
AttrDataKind::Octet => DEV_UCHAR,
AttrDataKind::UShort => DEV_USHORT,
AttrDataKind::Short => DEV_SHORT,
AttrDataKind::ULong => DEV_ULONG,
AttrDataKind::Long => DEV_LONG,
AttrDataKind::ULong64 => DEV_ULONG64,
AttrDataKind::Long64 => DEV_LONG64,
AttrDataKind::Float => DEV_FLOAT,
AttrDataKind::Double => DEV_DOUBLE,
AttrDataKind::State => DEV_STATE,
AttrDataKind::String => DEV_STRING,
AttrDataKind::Enum => DEV_ENUM,
AttrDataKind::Encoded => DEV_ENCODED,
}
}
#[derive(Debug)]
pub struct NamedError<'a> {
pub name: Cow<'a, str>,
pub index: usize,
pub err: Option<Error>,
}
impl<'a> CdrRead<'a> for NamedError<'a> {
fn read(buf: &mut CdrReader<'a>) -> Result<Self> {
let name = buf.read_string()?;
let index = buf.read_long()?;
let index = usize::try_from(index)
.map_err(|_| Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid attribute index in a MultiDevFailed: {index}")))?;
Ok(Self {
name,
index,
err: buf.read()?,
})
}
}
impl CdrWrite for NamedError<'_> {
fn write(&self, buf: &mut CdrWriter) -> Result<()> {
buf.write_string(&self.name)?;
buf.write_long(i32::try_from(self.index).unwrap_or(-1))?;
buf.write(&self.err.as_ref())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Image;
use super::super::orb::cdr::CdrWriter;
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));
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");
}
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();
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");
}
fn type_name(repo_id: &str) -> &str {
&repo_id[10..repo_id.len() - 4]
}
#[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);
}
#[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);
}
#[test]
fn encoded_attr() {
let read = Value::from(DevEncoded::new("JPEG_RGB", vec![1u8, 2, 3]));
let written = Value::from(DevEncoded::new("RAW", vec![4u8]));
for version in [DevVersion::V4, DevVersion::V5] {
for (value, w_value) in [
(Some(read.clone()), Some(written.clone())),
(Some(read.clone()), None),
(None, Some(written.clone())),
] {
let read = AttrRead { value, w_value, quality: AttrQuality::Valid, time: SystemTime::UNIX_EPOCH };
let mut writer = CdrWriter::new();
write_attr(&mut writer, version, "attr", &Ok(read.clone())).unwrap();
let buf = writer.into_buf().freeze();
let back = read_attr(&mut CdrReader::new(&buf), version).unwrap().value;
assert!(!buf.is_unique(), "the values were copied out of the reply");
assert_eq!(back.unwrap(), read, "{version:?}");
}
}
}
#[test]
fn encoded_attr_any() {
let read = AttrRead::new(Value::from(DevEncoded::new("RAW", vec![1u8])));
let mut writer = CdrWriter::new();
assert!(write_attr(&mut writer, DevVersion::V3, "attr", &Ok(read)).is_err());
}
#[test]
fn written_scalar() {
for r_dim in [(671181954, 32662), (3758189698, 0), (0, 0), (1, 0)] {
let (value, w_value) = split_attr_values(Some(Value::from(vec![1e300])), None, r_dim, (1, 0));
assert!(value.is_none(), "a write request carries no read value, {r_dim:?}");
assert_eq!(w_value, Some(Value::from(1e300)), "with a read dim of {r_dim:?}");
}
}
#[test]
fn written_spectrum() {
for r_dim in [(671181954, 32662), (3758189698, 0), (0, 0), (3, 0)] {
let written = Value::from(vec![1.0, -2.0, 3.5]);
let (value, w_value) = split_attr_values(Some(written.clone()), None, r_dim, (3, 0));
assert!(value.is_none(), "a write request carries no read value, {r_dim:?}");
assert_eq!(w_value, Some(written), "with a read dim of {r_dim:?}");
}
}
#[test]
fn read_values() {
let (value, w_value) = split_attr_values(Some(Value::from(vec![1.5, 2.5])), None, (1, 0), (1, 0));
assert_eq!(value, Some(Value::from(1.5)));
assert_eq!(w_value, Some(Value::from(2.5)));
let (value, w_value) = split_attr_values(Some(Value::from(vec![1.5])), None, (1, 0), (0, 0));
assert_eq!(value, Some(Value::from(1.5)));
assert!(w_value.is_none(), "a read-only attribute has no written value");
let read = Value::from(vec![1.0, 2.0, 3.0]);
let (value, w_value) = split_attr_values(Some(read.clone()), None, (3, 0), (0, 0));
assert_eq!(value, Some(read));
assert!(w_value.is_none(), "a read-only attribute has no written value");
}
#[test]
fn attr_round_trip() {
let image = Value::from(Image::<f64>::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [3, 2]).unwrap());
let enums = Value::from(Spectrum::<DevEnum>::from(vec![DevEnum(1), DevEnum(2)]));
for (version, value) in [(DevVersion::V4, image.clone()), (DevVersion::V5, image), (DevVersion::V5, enums)] {
let read = AttrRead {
value: Some(value.clone()),
w_value: Some(value),
quality: AttrQuality::Valid,
time: SystemTime::UNIX_EPOCH,
};
let mut writer = CdrWriter::new();
write_attr(&mut writer, version, "attr", &Ok(read.clone())).unwrap();
let buf = writer.into_buf().freeze();
let Named { name, value: back } = read_attr(&mut CdrReader::new(&buf), version).unwrap();
let back = back.unwrap();
assert_eq!(name.value(), "attr");
assert!(!buf.is_unique(), "the values were copied out of the reply");
assert_eq!(back, read, "{version:?}");
}
}
#[test]
fn attr_info_round_trip() {
let config = AttrConfig {
unit: Some("mm".into()),
range: AttrRangeConfig { min: Some(AttrNumber::Long(-5)), max: None },
alarm: AttrRangeConfig { min: None, max: Some(AttrNumber::Long(10)) },
delta: Some(AttrDeltaConfig { value: AttrNumber::Long(1), time: Some(Duration::from_millis(250)) }),
change_event: AttrChangeEventConfig { rel_change: Some(0.5), abs_change: None },
periodic_event: AttrPeriodicEventConfig { period: Some(Duration::from_millis(1000)) },
..AttrConfig::default()
};
let info = AttrInfo {
config,
mode: AttrMode::ReadWrite,
data_kind: AttrDataKind::Long,
data_format: AttrDataFormat::Scalar,
display_level: DisplayLevel::Operator,
memorized: None,
};
let mut writer = CdrWriter::new();
write_attr_info(&mut writer, DevVersion::V5, "Position", &info).unwrap();
let buf = writer.into_buf().freeze();
let (name, back) = read_attr_info(&mut CdrReader::new(&buf), DevVersion::V5).unwrap();
assert_eq!(name, "Position");
assert_eq!(back.unwrap(), info);
}
#[test]
fn unset_time() {
for (tv_sec, tv_usec, tv_nsec) in [
(-116981683, 32749, -116981655),
(32716, -1409265532, 32716),
(i32::MIN, i32::MIN, i32::MIN),
(-1, 0, 0),
] {
let mut writer = CdrWriter::new();
writer.write_long(tv_sec).unwrap();
writer.write_long(tv_usec).unwrap();
writer.write_long(tv_nsec).unwrap();
let buf = writer.into_buf().freeze();
let read = CdrReader::new(&buf).read::<SystemTime>()
.unwrap_or_else(|err| panic!("({tv_sec}, {tv_usec}, {tv_nsec}) was refused: {err}"));
assert_eq!(read, SystemTime::UNIX_EPOCH, "({tv_sec}, {tv_usec}, {tv_nsec})");
}
}
#[test]
fn unreachable_time() {
let mut writer = CdrWriter::new();
writer.write_long(i32::MAX).unwrap();
writer.write_long(i32::MAX).unwrap();
writer.write_long(i32::MAX).unwrap();
let buf = writer.into_buf().freeze();
assert!(CdrReader::new(&buf).read::<SystemTime>().is_ok(), "a time too far out was refused");
}
#[test]
fn time_roundtrip() {
let sent = SystemTime::UNIX_EPOCH + Duration::from_micros(1_788_714_320_192_300);
let mut writer = CdrWriter::new();
writer.write(&sent).unwrap();
let buf = writer.into_buf().freeze();
assert_eq!(CdrReader::new(&buf).read::<SystemTime>().unwrap(), sent);
}
#[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);
}
}