use std::time::SystemTime;
use std::mem;
use crate::value::{AttrDataFormat, AttrDataKind, DevEncoded, DevEnum, Scalar, TangoElement, Value, ValueFormat};
use crate::error::{reason, Error, ErrorKind, Result};
use crate::dev::{AttrQuality, AttrRead, AttrReady, DevState, Named, NamedResult};
use super::super::orb::cdr::{CdrReader, CdrWriter};
use super::super::DevVersion;
use super::{
attr_data_kind, attr_data_type, read_attr_data_format, read_encoded, read_spectrum, write_array_tc, write_attr_data_format,
write_attr_dim, write_encoded, CdrTangoElement,
ATT_BOOL, ATT_DEVICE_STATE, ATT_DOUBLE, ATT_ENCODED, ATT_FLOAT, ATT_LONG, ATT_LONG64,
ATT_NO_DATA, ATT_SHORT, ATT_STATE, ATT_STRING, ATT_UCHAR, ATT_ULONG, ATT_ULONG64, ATT_USHORT,
DATA_TYPE_UNKNOWN, DEV_ENCODED, DEV_ENUM, TK_ALIAS, TK_ENUM, TK_NULL, TK_VOID,
};
pub fn read_attr(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<NamedResult<AttrRead<Value>>> {
let (name, _, read) = read_attr_timed(buf, version)?;
Ok(Named::new(name, read))
}
pub(super) fn read_attr_timed(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<(Scalar<str>, SystemTime, Result<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>>()?;
}
let read = 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,
})
};
Ok((name, time, read))
}
pub fn read_attr_ready(buf: &mut CdrReader<'_>) -> Result<Named<AttrReady>> {
let name = buf.read::<Scalar<str>>()?;
let data_kind = attr_data_kind(buf.read_long()?)?;
let counter = buf.read_long()?;
Ok(Named::new(name, AttrReady { data_kind, counter }))
}
pub fn write_attr_ready(buf: &mut CdrWriter, name: &str, ready: &AttrReady) -> Result<()> {
buf.write_string(name)?;
buf.write_long(attr_data_type(&ready.data_kind))?;
buf.write_long(ready.counter)
}
pub fn write_attr(buf: &mut CdrWriter, version: DevVersion, name: &str, read: &Result<AttrRead<Value>>) -> Result<()> {
let time = read.as_ref().map_or(SystemTime::UNIX_EPOCH, |read| read.time);
write_attr_at(buf, version, name, read.as_ref(), time)
}
pub(super) fn write_attr_at(buf: &mut CdrWriter, version: DevVersion, name: &str, read: std::result::Result<&AttrRead<Value>, &Error>, time: SystemTime) -> Result<()> {
let (value, w_value, quality) = match read {
Ok(read) => (read.value.as_ref(), read.w_value.as_ref(), read.quality),
Err(_) => (None, None, AttrQuality::Invalid),
};
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.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(())
}
pub(super) 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!("Enum repository ID {repo_id:?} is not accepted in Tango attribute data"))
.with_append_description("Expected: DevState"))
}
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!("Typecode kind {tk} is not accepted in Tango attribute data")));
}
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!("Alias repository ID {repo_id:?} is not accepted in Tango attribute data"))
.with_append_description("Expected: DevVar*Array")),
};
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_description(format!("Attribute value union discriminant {discriminant} is not implemented"))),
};
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 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("Failed to encode data as a Tango attribute value")
.with_append_description(format_args!("Got: {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("The two directions of an attribute must share their type")
.with_append_description(format_args!("Got: {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("Failed to encode data as a Tango attribute value")
.with_append_description(format_args!("Got: {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))
}
pub(super) 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,
),
((r_x, 0), (w_x, 0)) if r_x + w_x == len => ( Some(Spectrum(r_x)),
Some(Spectrum(w_x)),
),
((_, 0), (w_x, 0)) if w_x == len => ( None,
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,
),
((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)),
),
((_, _), (w_x, w_y)) if w_x * w_y == len => ( None,
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)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::{Image, Spectrum};
#[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 empty_read_values() {
let written = Value::from(vec![-1.5, 0.0, 2.25, 1e-300]);
let (value, w_value) = split_attr_values(Some(written.clone()), Some(AttrDataFormat::Spectrum(0)), (0, 0), (4, 0));
assert_eq!(value, Some(Value::from(Vec::<f64>::new())));
assert_eq!(w_value, Some(written));
let (value, w_value) = split_attr_values(Some(Value::from(vec![1.0, 2.0])), Some(AttrDataFormat::Image(0, 0)), (0, 0), (2, 1));
assert!(value.is_some(), "an empty read image is still a read value");
assert!(w_value.is_some(), "the written image is kept");
}
#[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:?}");
}
}
}