use std::iter;
use std::time::SystemTime;
use crate::dev::{AttrQuality, AttrRead, DevState, Recorded, RecordedResult};
use crate::error::{reason, Error, ErrorKind, Result};
use crate::value::{AttrDataFormat, AttrDataKind, CommandDataKind, DevEnum, Spectrum, TangoElement, Value, ValueFormat};
use super::super::orb::cdr::{CdrReader, CdrWriter};
use super::super::{DevVersion, SequenceLen};
use super::attr::{read_attr_timed, read_attr_values_any, split_attr_values, write_attr_at};
use super::command::{read_command_data, write_command_data};
use super::{
attr_data_type, command_data_kind, command_data_type, read_attr_data_format, write_any_string_array_tc,
write_array_tc, write_attr_data_format, write_attr_dim, CdrTangoElement, DEV_ENUM, TK_NULL,
};
macro_rules! with_element {
($kind:expr, $func:ident ( $($arg:expr),* $(,)? )) => {
match $kind {
AttrDataKind::Bool => $func::<bool>($($arg),*),
AttrDataKind::Octet => $func::<u8>($($arg),*),
AttrDataKind::UShort => $func::<u16>($($arg),*),
AttrDataKind::Short => $func::<i16>($($arg),*),
AttrDataKind::ULong => $func::<u32>($($arg),*),
AttrDataKind::Long => $func::<i32>($($arg),*),
AttrDataKind::ULong64 => $func::<u64>($($arg),*),
AttrDataKind::Long64 => $func::<i64>($($arg),*),
AttrDataKind::Float => $func::<f32>($($arg),*),
AttrDataKind::Double => $func::<f64>($($arg),*),
AttrDataKind::Enum => $func::<DevEnum>($($arg),*),
AttrDataKind::State => $func::<DevState>($($arg),*),
AttrDataKind::String => $func::<str>($($arg),*),
AttrDataKind::Encoded => Err(encoded_err()),
}
};
}
pub fn write_command_history(buf: &mut CdrWriter, version: DevVersion, kind: CommandDataKind, entries: &[RecordedResult<Value>]) -> Result<()> {
if version < DevVersion::V4 {
write_command_list(buf, entries)
} else {
write_command_columns(buf, kind, entries)
}
}
pub fn read_command_history(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<Vec<RecordedResult<Value>>> {
if version < DevVersion::V4 {
read_command_list(buf)
} else {
read_command_columns(buf)
}
}
fn write_command_list(buf: &mut CdrWriter, entries: &[RecordedResult<Value>]) -> Result<()> {
buf.write_sequence_len(entries.len())?;
for entry in entries {
buf.write(&entry.time)?;
match &entry.value {
Ok(value) => {
buf.write_bool(false)?;
write_command_data(buf, value)?;
buf.write_sequence_len(0)?;
}
Err(err) => {
buf.write_bool(true)?;
buf.write_ulong(TK_NULL)?;
buf.write(err)?;
}
}
}
Ok(())
}
fn read_command_list(buf: &mut CdrReader<'_>) -> Result<Vec<RecordedResult<Value>>> {
let len = buf.read_sequence_len()?;
let mut entries = Vec::new();
for _ in 0..len {
let time = buf.read::<SystemTime>()?;
let failed = buf.read_bool()?;
let value = read_command_data(buf)?;
let err = buf.read::<Option<Error>>()?;
let value = if failed { Err(failed_err(err)) } else { Ok(value) };
entries.push(Recorded::new(time, value));
}
Ok(entries)
}
fn write_command_columns(buf: &mut CdrWriter, kind: CommandDataKind, entries: &[RecordedResult<Value>]) -> Result<()> {
write_dates(buf, entries)?;
let values = entries.iter().rev().filter_map(|entry| entry.value.as_ref().ok());
match kind {
CommandDataKind::Void => buf.write_ulong(TK_NULL)?,
CommandDataKind::LongStringArray => {
write_string_pairs(buf, tango_repo_id!("DevVarLongStringArray"), "lvalue", values, long_string)?;
}
CommandDataKind::DoubleStringArray => {
write_string_pairs(buf, tango_repo_id!("DevVarDoubleStringArray"), "dvalue", values, double_string)?;
}
kind => {
let (element, array) = command_element(kind).ok_or_else(encoded_err)?;
let format = if array { Format::Spectrum } else { Format::Scalar };
with_element!(element, write_any_elements(buf, values, format))?;
}
}
let dims = entries.iter().map(|entry| match &entry.value {
Err(_) => Some((0, 0)),
Ok(Value::Void) => None,
Ok(value) => Some(command_dim(value)),
});
write_runs(buf, dims, |buf, &(x, y)| write_attr_dim(buf, x, y))?;
write_errors(buf, entries)?;
buf.write_long(command_data_type(&kind))
}
fn read_command_columns(buf: &mut CdrReader<'_>) -> Result<Vec<RecordedResult<Value>>> {
let dates = read_dates(buf)?;
let len = dates.len();
let values = read_command_data(buf)?;
let dims = read_runs(buf, len, read_dim)?;
let errors = read_errors(buf, len)?;
let kind = command_data_kind(buf.read_long()?)?;
let mut ends = Ends::of(&values);
let mut entries = Vec::with_capacity(len);
for ((time, dim), err) in dates.into_iter().zip(dims).zip(errors) {
if let Some(err) = err {
entries.push(Recorded::new(time, Err(err)));
continue;
}
let value = match kind {
CommandDataKind::Void => Value::Void,
CommandDataKind::LongStringArray | CommandDataKind::DoubleStringArray => {
let (strings, numbers) = dim.ok_or_else(|| wrong_buffer_err("a record has no dimensions"))?;
ends.take_pair(&values, strings, numbers)?
}
kind => {
let (_, array) = command_element(kind).ok_or_else(encoded_err)?;
let (x, _) = dim.ok_or_else(|| wrong_buffer_err("a record has no dimensions"))?;
let format = if array { AttrDataFormat::Spectrum(0) } else { AttrDataFormat::Scalar };
let part = take_any_back(&values, &mut ends.values, x)?;
split_attr_values(Some(part), Some(format), (x, 0), (0, 0)).0
.ok_or_else(|| wrong_buffer_err("a result has not as many values as its dimensions"))?
}
};
entries.push(Recorded::new(time, Ok(value)));
}
Ok(entries)
}
fn command_element(kind: CommandDataKind) -> Option<(AttrDataKind, bool)> {
Some(match kind {
CommandDataKind::Bool => (AttrDataKind::Bool, false),
CommandDataKind::Octet => (AttrDataKind::Octet, false),
CommandDataKind::UShort => (AttrDataKind::UShort, false),
CommandDataKind::Short => (AttrDataKind::Short, false),
CommandDataKind::ULong => (AttrDataKind::ULong, false),
CommandDataKind::Long => (AttrDataKind::Long, false),
CommandDataKind::ULong64 => (AttrDataKind::ULong64, false),
CommandDataKind::Long64 => (AttrDataKind::Long64, false),
CommandDataKind::Float => (AttrDataKind::Float, false),
CommandDataKind::Double => (AttrDataKind::Double, false),
CommandDataKind::State => (AttrDataKind::State, false),
CommandDataKind::String => (AttrDataKind::String, false),
CommandDataKind::BoolArray => (AttrDataKind::Bool, true),
CommandDataKind::OctetArray => (AttrDataKind::Octet, true),
CommandDataKind::UShortArray => (AttrDataKind::UShort, true),
CommandDataKind::ShortArray => (AttrDataKind::Short, true),
CommandDataKind::ULongArray => (AttrDataKind::ULong, true),
CommandDataKind::LongArray => (AttrDataKind::Long, true),
CommandDataKind::ULong64Array => (AttrDataKind::ULong64, true),
CommandDataKind::Long64Array => (AttrDataKind::Long64, true),
CommandDataKind::FloatArray => (AttrDataKind::Float, true),
CommandDataKind::DoubleArray => (AttrDataKind::Double, true),
CommandDataKind::StateArray => (AttrDataKind::State, true),
CommandDataKind::StringArray => (AttrDataKind::String, true),
CommandDataKind::Void |
CommandDataKind::LongStringArray |
CommandDataKind::DoubleStringArray |
CommandDataKind::Encoded => return None,
})
}
fn command_dim(value: &Value) -> (usize, usize) {
match value {
Value::LongString(numbers, strings) => (strings.len(), numbers.len()),
Value::DoubleString(numbers, strings) => (strings.len(), numbers.len()),
value => attr_dim(Some(value)),
}
}
fn long_string(value: &Value) -> Option<(&Spectrum<i32>, &Spectrum<str>)> {
match value {
Value::LongString(numbers, strings) => Some((numbers, strings)),
_ => None,
}
}
fn double_string(value: &Value) -> Option<(&Spectrum<f64>, &Spectrum<str>)> {
match value {
Value::DoubleString(numbers, strings) => Some((numbers, strings)),
_ => None,
}
}
fn write_string_pairs<'a, N: CdrTangoElement>(
buf: &mut CdrWriter,
repo_id: &str,
numbers_name: &str,
values: impl Iterator<Item = &'a Value> + Clone,
pair: impl Fn(&Value) -> Option<(&Spectrum<N>, &Spectrum<str>)>,
) -> Result<()> {
if values.clone().next().is_none() {
return buf.write_ulong(TK_NULL);
}
write_any_string_array_tc(buf, repo_id, numbers_name, N::TK, N::ARRAY_REPO_ID)?;
let len = SequenceLen::start(buf)?;
let mut count = 0;
for value in values.clone() {
let (numbers, _) = pair(value).ok_or_else(|| mismatched_err(value))?;
N::write_values(buf, numbers)?;
count += numbers.len();
}
len.finish(buf, count)?;
let len = SequenceLen::start(buf)?;
let mut count = 0;
for value in values {
let (_, strings) = pair(value).ok_or_else(|| mismatched_err(value))?;
<str as CdrTangoElement>::write_values(buf, strings)?;
count += strings.len();
}
len.finish(buf, count)
}
pub fn write_attr_history(
buf: &mut CdrWriter,
version: DevVersion,
name: &str,
kind: AttrDataKind,
format: AttrDataFormat,
entries: &[RecordedResult<AttrRead<Value>>],
) -> Result<()> {
if version < DevVersion::V4 {
write_attr_list(buf, version, name, entries)
} else {
write_attr_columns(buf, version, name, kind, format, entries)
}
}
pub fn read_attr_history(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<Vec<RecordedResult<AttrRead<Value>>>> {
if version < DevVersion::V4 {
read_attr_list(buf, version)
} else {
read_attr_columns(buf, version)
}
}
fn write_attr_list(buf: &mut CdrWriter, version: DevVersion, name: &str, entries: &[RecordedResult<AttrRead<Value>>]) -> Result<()> {
let value_version = if version < DevVersion::V3 { DevVersion::V1 } else { DevVersion::V3 };
buf.write_sequence_len(entries.len())?;
for entry in entries {
buf.write_bool(entry.value.is_err())?;
write_attr_at(buf, value_version, name, entry.value.as_ref(), entry.time)?;
if version < DevVersion::V3 {
buf.write(&entry.value.as_ref().err())?;
}
}
Ok(())
}
fn read_attr_list(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<Vec<RecordedResult<AttrRead<Value>>>> {
let value_version = if version < DevVersion::V3 { DevVersion::V1 } else { DevVersion::V3 };
let len = buf.read_sequence_len()?;
let mut entries = Vec::new();
for _ in 0..len {
let failed = buf.read_bool()?;
let (_, time, read) = read_attr_timed(buf, value_version)?;
let err = if version < DevVersion::V3 {
buf.read::<Option<Error>>()?
} else {
None
};
let read = match read {
Ok(_) if failed => Err(failed_err(err)),
read => read,
};
entries.push(Recorded::new(time, read));
}
Ok(entries)
}
fn write_attr_columns(
buf: &mut CdrWriter,
version: DevVersion,
name: &str,
kind: AttrDataKind,
format: AttrDataFormat,
entries: &[RecordedResult<AttrRead<Value>>],
) -> Result<()> {
buf.write_string(name)?;
if version >= DevVersion::V5 {
write_attr_data_format(buf, Some(format))?;
buf.write_long(attr_data_type(&kind))?;
}
write_dates(buf, entries)?;
let readings = entries.iter()
.map(|entry| entry.value.as_ref().ok().filter(|read| read.quality != AttrQuality::Invalid));
let values = readings.clone()
.rev()
.flatten()
.flat_map(|read| [read.value.as_ref(), read.w_value.as_ref()])
.flatten();
with_element!(kind, write_any_elements(buf, values, Format::Any))?;
let quals = entries.iter().map(|entry| Some(match &entry.value {
Ok(read) => read.quality,
Err(_) => AttrQuality::Invalid,
}));
write_runs(buf, quals, |buf, quality| buf.write(quality))?;
let r_dims = readings.clone().map(|read| Some(attr_dim(read.and_then(|read| read.value.as_ref()))));
write_runs(buf, r_dims, |buf, &(x, y)| write_attr_dim(buf, x, y))?;
let w_dims = readings.map(|read| Some(attr_dim(read.and_then(|read| read.w_value.as_ref()))));
write_runs(buf, w_dims, |buf, &(x, y)| write_attr_dim(buf, x, y))?;
write_errors(buf, entries)
}
fn read_attr_columns(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<Vec<RecordedResult<AttrRead<Value>>>> {
let _name = buf.read_string()?;
let mut format = None;
let mut data_type = None;
if version >= DevVersion::V5 {
format = read_attr_data_format(buf)?;
data_type = Some(buf.read_long()?);
}
let dates = read_dates(buf)?;
let len = dates.len();
let mut values = read_attr_values_any(buf)?;
if data_type == Some(DEV_ENUM)
&& let Some(Value::Short(ValueFormat::Spectrum(shorts))) = &values {
let enums = shorts.clone().cast::<DevEnum, 1>(shorts.dim());
values = Some(Value::from(enums.expect("a short is a valid enumeration")));
}
let quals = read_runs(buf, len, |buf| buf.read::<AttrQuality>())?;
let r_dims = read_runs(buf, len, read_dim)?;
let w_dims = read_runs(buf, len, read_dim)?;
let errors = read_errors(buf, len)?;
let mut end = values.as_ref().map_or(0, value_len);
let mut entries = Vec::with_capacity(len);
for (index, (time, err)) in dates.into_iter().zip(errors).enumerate() {
if let Some(err) = err {
entries.push(Recorded::new(time, Err(err)));
continue;
}
let quality = quals[index].ok_or_else(|| wrong_buffer_err("a record has no quality"))?;
let mut read = AttrRead { value: None, w_value: None, quality, time };
if quality != AttrQuality::Invalid {
let r_dim = r_dims[index].ok_or_else(|| wrong_buffer_err("a record has no read dimensions"))?;
let w_dim = w_dims[index].ok_or_else(|| wrong_buffer_err("a record has no written dimensions"))?;
let values = values.as_ref().ok_or_else(|| wrong_buffer_err("a record has values that were not sent"))?;
let part = take_any_back(values, &mut end, dim_len(r_dim) + dim_len(w_dim))?;
(read.value, read.w_value) = split_attr_values(Some(part), format, r_dim, w_dim);
}
entries.push(Recorded::new(time, Ok(read)));
}
Ok(entries)
}
#[derive(Clone, Copy)]
enum Format {
Scalar,
Spectrum,
Any,
}
fn write_any_elements<'a, T: CdrTangoElement + ?Sized>(
buf: &mut CdrWriter,
values: impl Iterator<Item = &'a Value>,
format: Format,
) -> Result<()> {
let mut values = values.peekable();
if values.peek().is_none() {
return buf.write_ulong(TK_NULL);
}
write_array_tc(buf, T::TK, T::ARRAY_REPO_ID)?;
let seq_len = SequenceLen::start(buf)?;
let mut len = 0;
for value in values {
let data = T::unwrap_ref(value).ok_or_else(|| mismatched_err(value))?;
len += match (data, format) {
(ValueFormat::Scalar(data), Format::Scalar | Format::Any) => T::write_values(buf, data).map(|()| data.len())?,
(ValueFormat::Spectrum(data), Format::Spectrum | Format::Any) => T::write_values(buf, data).map(|()| data.len())?,
(ValueFormat::Image(data), Format::Any) => T::write_values(buf, data).map(|()| data.len())?,
_ => return Err(mismatched_err(value)),
};
}
seq_len.finish(buf, len)
}
fn write_dates<T>(buf: &mut CdrWriter, entries: &[Recorded<T>]) -> Result<()> {
buf.write_sequence_len(entries.len())?;
entries.iter().try_for_each(|entry| buf.write(&entry.time))
}
fn read_dates(buf: &mut CdrReader<'_>) -> Result<Vec<SystemTime>> {
let len = buf.read_sequence_len()?;
(0..len).map(|_| buf.read::<SystemTime>()).collect()
}
fn write_runs<K: PartialEq>(
buf: &mut CdrWriter,
column: impl Iterator<Item = Option<K>> + Clone,
write_key: impl Fn(&mut CdrWriter, &K) -> Result<()>,
) -> Result<()> {
let len = SequenceLen::start(buf)?;
let mut count = 0;
for (key, _, _) in runs(column.clone()) {
write_key(buf, &key)?;
count += 1;
}
len.finish(buf, count)?;
buf.write_sequence_len(count)?;
runs(column).map(|(_, start, count)| write_elt_in_array(buf, start, count)).collect()
}
fn runs<K: PartialEq>(column: impl Iterator<Item = Option<K>>) -> impl Iterator<Item = (K, usize, usize)> {
let mut column = column.enumerate()
.filter_map(|(index, key)| Some((key?, index)))
.peekable();
iter::from_fn(move || {
let (key, mut start) = column.next()?;
let mut count = 1;
while let Some((_, index)) = column.next_if(|(next, index)| *next == key && *index == start + 1) {
start = index;
count += 1;
}
Some((key, start, count))
})
}
fn read_runs<K: Clone>(
buf: &mut CdrReader<'_>,
len: usize,
read_key: impl Fn(&mut CdrReader<'_>) -> Result<K>,
) -> Result<Vec<Option<K>>> {
let keys_len = buf.read_sequence_len()?;
let keys = (0..keys_len).map(|_| read_key(buf)).collect::<Result<Vec<_>>>()?;
let mut column = vec![None; len];
expand_runs(buf, keys, &mut column)?;
Ok(column)
}
fn expand_runs<K: Clone>(buf: &mut CdrReader<'_>, keys: Vec<K>, column: &mut [Option<K>]) -> Result<()> {
let runs_len = buf.read_sequence_len()?;
if runs_len != keys.len() {
return Err(wrong_buffer_err("a column has not as many runs as keys"));
}
for key in keys {
let start = buf.read_long()?;
let count = buf.read_long()?;
let (Ok(start), Ok(count)) = (usize::try_from(start), usize::try_from(count)) else {
return Err(wrong_buffer_err("a run is negative"));
};
if start >= column.len() || count > start + 1 {
return Err(wrong_buffer_err("a run is outside of the records"));
}
for slot in &mut column[start + 1 - count..=start] {
*slot = Some(key.clone());
}
}
Ok(())
}
fn write_errors<T>(buf: &mut CdrWriter, entries: &[RecordedResult<T>]) -> Result<()> {
let failed = entries.iter()
.enumerate()
.filter_map(|(index, entry)| entry.value.as_ref().err().map(|err| (index, err)));
let len = SequenceLen::start(buf)?;
let mut count = 0;
for (_, err) in failed.clone() {
buf.write(err)?;
count += 1;
}
len.finish(buf, count)?;
buf.write_sequence_len(count)?;
failed.map(|(index, _)| write_elt_in_array(buf, index, 1)).collect()
}
fn read_errors(buf: &mut CdrReader<'_>, len: usize) -> Result<Vec<Option<Error>>> {
let errors_len = buf.read_sequence_len()?;
let errors = (0..errors_len)
.map(|_| buf.read::<Option<Error>>().map(failed_err))
.collect::<Result<Vec<_>>>()?;
let mut column = vec![None; len];
expand_runs(buf, errors, &mut column)?;
Ok(column)
}
fn write_elt_in_array(buf: &mut CdrWriter, start: usize, count: usize) -> Result<()> {
let (Ok(start), Ok(count)) = (i32::try_from(start), i32::try_from(count)) else {
return Err(Error::new(ErrorKind::InvalidData)
.with_description("History runs do not fit into a long"));
};
buf.write_long(start)?;
buf.write_long(count)
}
fn read_dim(buf: &mut CdrReader<'_>) -> Result<(usize, usize)> {
let x = buf.read_long()?;
let y = buf.read_long()?;
let (Ok(x), Ok(y)) = (usize::try_from(x), usize::try_from(y)) else {
return Err(wrong_buffer_err("a dimension is negative"));
};
Ok((x, y))
}
struct Ends {
values: usize,
numbers: usize,
}
impl Ends {
fn of(values: &Value) -> Self {
match values {
Value::LongString(numbers, strings) => Self { values: strings.len(), numbers: numbers.len() },
Value::DoubleString(numbers, strings) => Self { values: strings.len(), numbers: numbers.len() },
values => Self { values: value_len(values), numbers: 0 },
}
}
fn take_pair(&mut self, values: &Value, strings: usize, numbers: usize) -> Result<Value> {
Ok(match values {
Value::LongString(all_numbers, all_strings) => Value::LongString(
take_spectrum_back(all_numbers, &mut self.numbers, numbers)?,
take_spectrum_back(all_strings, &mut self.values, strings)?,
),
Value::DoubleString(all_numbers, all_strings) => Value::DoubleString(
take_spectrum_back(all_numbers, &mut self.numbers, numbers)?,
take_spectrum_back(all_strings, &mut self.values, strings)?,
),
_ => return Err(wrong_buffer_err("the values are not pairs of arrays")),
})
}
}
fn value_len(values: &Value) -> usize {
match values {
Value::Void => 0,
values => attr_dim(Some(values)).0,
}
}
fn attr_dim(value: Option<&Value>) -> (usize, usize) {
fn dim<T: TangoElement + ?Sized>(format: &ValueFormat<T>) -> (usize, usize) {
match format {
ValueFormat::Scalar(_) => (1, 0),
ValueFormat::Spectrum(data) => (data.len(), 0),
ValueFormat::Image(data) => {
let [x, y] = data.dim();
(x, y)
}
}
}
match value {
Some(Value::Bool(format)) => dim(format),
Some(Value::Octet(format)) => dim(format),
Some(Value::UShort(format)) => dim(format),
Some(Value::Short(format)) => dim(format),
Some(Value::ULong(format)) => dim(format),
Some(Value::Long(format)) => dim(format),
Some(Value::ULong64(format)) => dim(format),
Some(Value::Long64(format)) => dim(format),
Some(Value::Float(format)) => dim(format),
Some(Value::Double(format)) => dim(format),
Some(Value::Enum(format)) => dim(format),
Some(Value::State(format)) => dim(format),
Some(Value::String(format)) => dim(format),
Some(Value::Encoded(_)) => (1, 0),
Some(Value::Void | Value::LongString(..) | Value::DoubleString(..)) | None => (0, 0),
}
}
fn dim_len((x, y): (usize, usize)) -> usize {
if y == 0 { x } else { x * y }
}
fn take_any_back(values: &Value, end: &mut usize, len: usize) -> Result<Value> {
fn take<T: TangoElement + ?Sized>(data: &ValueFormat<T>, end: &mut usize, len: usize) -> Result<Value> {
match data {
ValueFormat::Spectrum(data) => Ok(T::wrap(ValueFormat::Spectrum(take_spectrum_back(data, end, len)?))),
_ => Err(wrong_buffer_err("the values are not a sequence of elements")),
}
}
match values {
Value::Bool(data) => take(data, end, len),
Value::Octet(data) => take(data, end, len),
Value::UShort(data) => take(data, end, len),
Value::Short(data) => take(data, end, len),
Value::ULong(data) => take(data, end, len),
Value::Long(data) => take(data, end, len),
Value::ULong64(data) => take(data, end, len),
Value::Long64(data) => take(data, end, len),
Value::Float(data) => take(data, end, len),
Value::Double(data) => take(data, end, len),
Value::Enum(data) => take(data, end, len),
Value::State(data) => take(data, end, len),
Value::String(data) => take(data, end, len),
_ => Err(wrong_buffer_err("the values are not a sequence of elements")),
}
}
fn take_spectrum_back<T: TangoElement + ?Sized>(data: &Spectrum<T>, end: &mut usize, len: usize) -> Result<Spectrum<T>> {
let start = end.checked_sub(len)
.ok_or_else(|| wrong_buffer_err("the records have more values than were sent"))?;
let part = data.slice(start..*end);
*end = start;
Ok(part)
}
fn failed_err(err: Option<Error>) -> Error {
err.unwrap_or_else(|| Error::new(ErrorKind::InvalidData)
.with_description("The record failed with no error given"))
}
fn wrong_buffer_err(what: &str) -> Error {
Error::new(ErrorKind::Reason(reason::API_WrongHistoryDataBuffer))
.with_description(format!("The history received is not valid, {what}"))
}
fn mismatched_err(value: &Value) -> Error {
Error::new(ErrorKind::InvalidData)
.with_description("A recorded value is not of the type of its command or attribute")
.with_append_description(format_args!("Got: {value:?}"))
}
fn encoded_err() -> Error {
Error::new(ErrorKind::Reason(reason::API_NotSupportedFeature))
.with_description("The history of a DevEncoded command or attribute is not supported")
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::buf::Buf;
use crate::value::Image;
use super::*;
fn time(index: u64) -> SystemTime {
SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000 + index) + Duration::from_nanos(123_456)
}
fn failed() -> Error {
Error::new(ErrorKind::Reason("Motor_Unplugged")).with_description("No motor")
}
fn entries<T>(values: Vec<Result<T>>) -> Vec<RecordedResult<T>> {
values.into_iter().enumerate().map(|(index, value)| Recorded::new(time(index as u64), value)).collect()
}
fn reading(value: Option<Value>, w_value: Option<Value>, quality: AttrQuality) -> Result<AttrRead<Value>> {
Ok(AttrRead { value, w_value, quality, time: SystemTime::UNIX_EPOCH })
}
fn check<T: PartialEq + std::fmt::Debug>(back: &[RecordedResult<T>], entries: &[RecordedResult<T>], what: &str) {
assert_eq!(back.len(), entries.len(), "{what}");
for (back, entry) in back.iter().zip(entries) {
assert_eq!(back.time, entry.time, "{what}");
match (&back.value, &entry.value) {
(Ok(back), Ok(entry)) => assert_eq!(back, entry, "{what}"),
(Err(back), Err(entry)) => {
assert_eq!(back.reason(), entry.reason(), "{what}");
assert_eq!(back.description(), entry.description(), "{what}");
}
(back, entry) => panic!("{what}: got {back:?}, expected {entry:?}"),
}
}
}
#[test]
fn command_roundtrip() {
let cases = [
(CommandDataKind::Long, vec![Ok(Value::from(1)), Err(failed()), Ok(Value::from(2)), Ok(Value::from(3))]),
(CommandDataKind::String, vec![Ok(Value::from("a")), Ok(Value::from("b")), Err(failed())]),
(CommandDataKind::State, vec![Ok(Value::from(DevState::On)), Ok(Value::from(DevState::Moving))]),
(CommandDataKind::DoubleArray, vec![Ok(Value::from(Buf::from(&[1.0, 2.0][..]))), Ok(Value::from(Buf::from(&[3.0][..]))), Err(failed())]),
(CommandDataKind::StringArray, vec![Ok(Value::from(Buf::from(&["x", "y"][..]))), Ok(Value::from(Buf::from(&[] as &[&str])))]),
(CommandDataKind::LongStringArray, vec![
Ok(Value::from((Buf::from(&[1i32, 2][..]), Buf::from(&["one"][..])))),
Err(failed()),
Ok(Value::from((Buf::from(&[3i32][..]), Buf::from(&["two", "three"][..])))),
]),
(CommandDataKind::Void, vec![Ok(Value::Void), Err(failed()), Ok(Value::Void)]),
(CommandDataKind::Double, vec![Err(failed()), Err(failed())]),
];
for (kind, values) in cases {
let entries = entries(values);
for version in [DevVersion::V2, DevVersion::V4] {
let mut writer = CdrWriter::new();
write_command_history(&mut writer, version, kind, &entries).unwrap();
let buf = writer.into_buf().freeze();
let back = read_command_history(&mut CdrReader::new(&buf), version).unwrap();
check(&back, &entries, &format!("{kind:?} in {version:?}"));
}
}
}
#[test]
fn attr_roundtrip() {
let image = Value::from(Image::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [3, 2]).unwrap());
let cases = [
(AttrDataKind::Double, AttrDataFormat::Scalar, vec![
reading(Some(Value::from(1.5)), None, AttrQuality::Valid),
reading(Some(Value::from(2.5)), None, AttrQuality::Alarm),
Err(failed()),
reading(None, None, AttrQuality::Invalid),
reading(Some(Value::from(3.5)), None, AttrQuality::Valid),
], DevVersion::V2),
(AttrDataKind::Double, AttrDataFormat::Scalar, vec![
reading(Some(Value::from(1.5)), Some(Value::from(2.0)), AttrQuality::Valid),
reading(Some(Value::from(2.5)), Some(Value::from(2.0)), AttrQuality::Valid),
Err(failed()),
], DevVersion::V3),
(AttrDataKind::Long, AttrDataFormat::Spectrum(0), vec![
reading(Some(Value::from(Buf::from(&[1, 2, 3][..]))), None, AttrQuality::Valid),
reading(Some(Value::from(Buf::from(&[4, 5][..]))), None, AttrQuality::Valid),
], DevVersion::V2),
(AttrDataKind::Double, AttrDataFormat::Image(0, 0), vec![
reading(Some(image.clone()), None, AttrQuality::Valid),
Err(failed()),
reading(Some(image), None, AttrQuality::Warning),
], DevVersion::V2),
(AttrDataKind::String, AttrDataFormat::Scalar, vec![
reading(Some(Value::from("a")), None, AttrQuality::Valid),
reading(Some(Value::from("b")), None, AttrQuality::Valid),
], DevVersion::V2),
(AttrDataKind::State, AttrDataFormat::Scalar, vec![
reading(Some(Value::from(DevState::On)), None, AttrQuality::Valid),
Err(failed()),
], DevVersion::V2),
(AttrDataKind::Double, AttrDataFormat::Scalar, vec![Err(failed())], DevVersion::V2),
];
for (kind, format, values, since) in cases {
let entries = entries(values);
for version in [DevVersion::V2, DevVersion::V3, DevVersion::V4, DevVersion::V5] {
if version < since {
continue;
}
let mut writer = CdrWriter::new();
write_attr_history(&mut writer, version, "attr", kind, format, &entries).unwrap();
let buf = writer.into_buf().freeze();
let back = read_attr_history(&mut CdrReader::new(&buf), version).unwrap();
let entries = entries.iter()
.map(|entry| Recorded::new(entry.time, entry.value.clone().map(|read| AttrRead { time: entry.time, ..read })))
.collect::<Vec<_>>();
check(&back, &entries, &format!("{kind:?} {format:?} in {version:?}"));
}
}
}
#[test]
fn enum_attr() {
let entries = entries(vec![reading(Some(Value::from(DevEnum(2))), None, AttrQuality::Valid)]);
let mut writer = CdrWriter::new();
write_attr_history(&mut writer, DevVersion::V5, "attr", AttrDataKind::Enum, AttrDataFormat::Scalar, &entries).unwrap();
let buf = writer.into_buf().freeze();
let back = read_attr_history(&mut CdrReader::new(&buf), DevVersion::V5).unwrap();
assert_eq!(back[0].value.as_ref().unwrap().value, Some(Value::from(DevEnum(2))));
}
#[test]
fn columns() {
let entries = entries(vec![
reading(Some(Value::from(1.0)), None, AttrQuality::Valid),
reading(Some(Value::from(2.0)), None, AttrQuality::Valid),
reading(Some(Value::from(3.0)), None, AttrQuality::Alarm),
Err(failed()),
reading(Some(Value::from(4.0)), None, AttrQuality::Valid),
]);
let mut writer = CdrWriter::new();
write_attr_history(&mut writer, DevVersion::V5, "attr", AttrDataKind::Double, AttrDataFormat::Scalar, &entries).unwrap();
let buf = writer.into_buf().freeze();
let mut reader = CdrReader::new(&buf);
let runs = |reader: &mut CdrReader<'_>| {
let len = reader.read_sequence_len().unwrap();
(0..len).map(|_| (reader.read_long().unwrap(), reader.read_long().unwrap())).collect::<Vec<_>>()
};
assert_eq!(reader.read_string().unwrap(), "attr");
assert_eq!(read_attr_data_format(&mut reader).unwrap(), Some(AttrDataFormat::Scalar));
assert_eq!(reader.read_long().unwrap(), attr_data_type(&AttrDataKind::Double));
assert_eq!(read_dates(&mut reader).unwrap(), (0..5).map(time).collect::<Vec<_>>());
let values = read_attr_values_any(&mut reader).unwrap();
assert_eq!(values, Some(Value::from(Buf::from(&[4.0, 3.0, 2.0, 1.0][..]))));
let quals = (0..reader.read_sequence_len().unwrap()).map(|_| reader.read::<AttrQuality>().unwrap()).collect::<Vec<_>>();
assert_eq!(quals, [AttrQuality::Valid, AttrQuality::Alarm, AttrQuality::Invalid, AttrQuality::Valid]);
assert_eq!(runs(&mut reader), [(1, 2), (2, 1), (3, 1), (4, 1)]);
let r_dims = (0..reader.read_sequence_len().unwrap()).map(|_| read_dim(&mut reader).unwrap()).collect::<Vec<_>>();
assert_eq!(r_dims, [(1, 0), (0, 0), (1, 0)]);
assert_eq!(runs(&mut reader), [(2, 3), (3, 1), (4, 1)]);
let w_dims = (0..reader.read_sequence_len().unwrap()).map(|_| read_dim(&mut reader).unwrap()).collect::<Vec<_>>();
assert_eq!(w_dims, [(0, 0)]);
assert_eq!(runs(&mut reader), [(4, 5)]);
assert_eq!(reader.read_sequence_len().unwrap(), 1);
assert_eq!(reader.read::<Error>().unwrap().reason(), "Motor_Unplugged");
assert_eq!(runs(&mut reader), [(3, 1)]);
}
#[test]
fn mismatched() {
let entries = entries(vec![Ok(Value::from(1.5))]);
let mut writer = CdrWriter::new();
let err = write_command_history(&mut writer, DevVersion::V4, CommandDataKind::Long, &entries).unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidData);
}
}