use std::time::{Duration, SystemTime};
use std::borrow::Cow;
use std::fmt::Debug;
use crate::value::{AttrDataFormat, AttrDataKind, CommandDataKind, DevEncoded, DevEnum, TangoElement, Value, ValueFormat};
use crate::error::{Error, ErrorKind, ErrorSeverity, Result};
use crate::value::{Scalar, Spectrum};
use crate::buf::Buf;
use crate::dev::{AttrQuality, DevSource, DevState};
use super::orb::cdr::{CdrReader, CdrWriter, CdrRead, CdrWrite};
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;
mod command;
mod attr;
mod info;
mod history;
pub use command::{read_command_data, write_command_data};
pub use attr::{read_attr, read_attr_ready, write_attr, write_attr_ready};
pub use info::{read_attr_info, read_command_info, read_dev_info, write_attr_info, write_command_info, write_dev_info};
pub use history::{read_attr_history, read_command_history, write_attr_history, write_command_history};
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!(stringify!($enum), " numeric value {} is invalid"), 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("Failed to 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("Failed to 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)
}
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)),
})
}
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,
}
}
fn unsupported_data_type_err(data_type: i32) -> Error {
match data_type {
DEVVAR_ENCODEDARRAY => Error::not_implemented()
.with_description("DevVarEncodedArray data is not implemented"),
DEV_PIPE_BLOB => Error::not_implemented()
.with_description("Pipe blob data is not implemented"),
data_type => Error::new(ErrorKind::InvalidData)
.with_description(format!("Tango data type {data_type} is unknown")),
}
}
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!("Attribute data format value {value} is invalid")))
}))
}
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_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("Attribute dimensions do not fit into a long")
.with_append_description(format_args!("Got: {dim_x}x{dim_y}")));
};
buf.write_long(dim_x)?;
buf.write_long(dim_y)
}
#[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!("Attribute index {index} in a MultiDevFailed is invalid")))?;
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::*;
#[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);
}
}