pub mod metadata;
pub mod signal;
pub mod time;
pub mod values;
pub(crate) mod vlsd;
pub use metadata::*;
pub use signal::*;
pub use time::*;
pub use values::*;
pub(crate) use vlsd::*;
use crate::blocks::source::SourceInfo;
use crate::blocks::{
AxisRef, CaStorage, ChannelType, Conversion, ConversionInput, ConversionOutput, DataType,
SyncType,
};
use crate::blocks::{ChElement, ChType, EvCause, EvRangeType, EvSyncType, EventType, SrSyncType};
use crate::data_index::{DataBlockIndex, RecordIndex};
#[derive(Debug, Clone)]
pub struct DataGroup {
pub id: usize,
pub index: usize,
pub channel_groups: Vec<ChannelGroup>,
pub comment: String,
pub(crate) dg_offset: u64,
pub(crate) data_offset: u64,
pub(crate) rec_id_size: u8,
pub(crate) data_block_index: DataBlockIndex,
pub(crate) record_index: Option<RecordIndex>,
}
impl DataGroup {
pub fn channel_group_count(&self) -> usize {
self.channel_groups.len()
}
pub fn channels(&self) -> impl Iterator<Item = &Channel> {
self.channel_groups.iter().flat_map(|cg| cg.channels.iter())
}
pub fn find_channel(&self, name: &str) -> Option<&Channel> {
self.channels().find(|ch| ch.name == name)
}
pub fn block_index(&self) -> &DataBlockIndex {
&self.data_block_index
}
pub fn is_unsorted(&self) -> bool {
self.record_index.is_some()
}
pub fn rec_id_size(&self) -> u8 {
self.rec_id_size
}
pub fn block_offset(&self) -> u64 {
self.dg_offset
}
pub fn data_block_offset(&self) -> u64 {
self.data_offset
}
}
#[derive(Debug, Clone)]
pub struct ChannelGroup {
pub id: usize,
pub index: usize,
pub data_group_index: usize,
pub acquisition_name: String,
pub sample_count: u64,
pub channels: Vec<Channel>,
pub source: Option<SourceInfo>,
pub comment: String,
pub(crate) record_id: u64,
pub(crate) data_bytes: u32,
pub(crate) inval_bytes: u32,
pub(crate) cg_offset: u64,
pub(crate) is_vlsd: bool,
pub(crate) bus_event: bool,
pub(crate) plain_bus_event: bool,
pub(crate) sample_reductions: Vec<SampleReduction>,
}
impl ChannelGroup {
pub fn channel_count(&self) -> usize {
self.channels.len()
}
pub fn master_channel(&self) -> Option<&Channel> {
self.channels.iter().find(|ch| ch.is_master())
}
pub fn find_channel(&self, name: &str) -> Option<&Channel> {
self.channels.iter().find(|ch| ch.name == name)
}
pub fn record_size(&self, rec_id_size: u8) -> usize {
rec_id_size as usize + self.payload_size()
}
pub fn data_bytes_len(&self) -> usize {
self.data_bytes as usize
}
pub fn inval_bytes_len(&self) -> usize {
self.inval_bytes as usize
}
pub fn sample_reductions(&self) -> &[SampleReduction] {
&self.sample_reductions
}
pub fn record_id(&self) -> u64 {
self.record_id
}
pub fn is_vlsd(&self) -> bool {
self.is_vlsd
}
pub fn is_bus_event(&self) -> bool {
self.bus_event
}
pub fn is_plain_bus_event(&self) -> bool {
self.plain_bus_event
}
pub fn matches_offset(&self, offset: u64) -> bool {
offset != 0 && self.cg_offset == offset
}
pub fn block_offset(&self) -> u64 {
self.cg_offset
}
pub fn payload_size(&self) -> usize {
self.data_bytes as usize + self.inval_bytes as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnreadableReason {
ArrayGroupTemplate,
ArrayDynamicSize,
ArrayComposition,
SyncChannel,
}
impl UnreadableReason {
pub fn detail(&self) -> &'static str {
match self {
UnreadableReason::ArrayGroupTemplate => {
"the channel holds an array whose elements are stored one per channel \
group or one per data group rather than adjacently in this record"
}
UnreadableReason::ArrayDynamicSize => {
"the channel holds a dynamic-size array whose real per-sample size cannot \
be resolved from this record alone"
}
UnreadableReason::ArrayComposition => {
"the channel holds an array composed with something this build cannot \
resolve into elements"
}
UnreadableReason::SyncChannel => {
"the channel indexes a media stream rather than carrying samples"
}
}
}
pub fn feature(&self) -> &'static str {
match self {
UnreadableReason::SyncChannel => "synchronisation channel",
UnreadableReason::ArrayGroupTemplate
| UnreadableReason::ArrayDynamicSize
| UnreadableReason::ArrayComposition => "channel array (CA)",
}
}
}
impl std::fmt::Display for UnreadableReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.detail())
}
}
#[derive(Debug, Clone)]
pub struct Channel {
pub id: usize,
pub index: usize,
pub channel_group_index: usize,
pub data_group_index: usize,
pub name: String,
pub unit: String,
pub channel_type: ChannelType,
pub sync_type: SyncType,
pub data_type: DataType,
pub conversion: Conversion,
pub bit_count: u32,
pub byte_offset: u32,
pub bit_offset: u8,
pub all_invalid: bool,
pub invalidation_bit: bool,
pub inval_bit_pos: u32,
pub comment: String,
pub source: Option<SourceInfo>,
pub min_value: Option<f64>,
pub max_value: Option<f64>,
pub sample_count: u64,
pub(crate) cn_offset: u64,
pub(crate) data_link: u64,
pub(crate) unreadable: Option<UnreadableReason>,
pub array_shape: Option<Vec<u64>>,
pub(crate) array_element: Option<ArrayElement>,
pub(crate) array_dynamic_size: Option<AxisRef>,
}
impl Channel {
pub fn synthetic(name: impl Into<String>, unit: impl Into<String>) -> Self {
Self {
id: 0,
index: 0,
channel_group_index: 0,
data_group_index: 0,
name: name.into(),
unit: unit.into(),
channel_type: ChannelType::FixedLength,
sync_type: SyncType::None,
data_type: DataType::FloatLe,
conversion: Conversion::None,
bit_count: 64,
byte_offset: 0,
bit_offset: 0,
all_invalid: false,
invalidation_bit: false,
inval_bit_pos: 0,
comment: String::new(),
source: None,
min_value: None,
max_value: None,
sample_count: 0,
cn_offset: 0,
data_link: 0,
unreadable: None,
array_shape: None,
array_element: None,
array_dynamic_size: None,
}
}
pub fn is_master(&self) -> bool {
self.channel_type.is_master()
}
pub fn is_time_channel(&self) -> bool {
self.is_master() && self.sync_type == SyncType::Time
}
pub fn block_offset(&self) -> u64 {
self.cn_offset
}
pub fn byte_size(&self) -> usize {
self.bit_count.div_ceil(8) as usize
}
pub fn is_numeric(&self) -> bool {
self.data_type.is_numeric()
}
pub fn is_little_endian(&self) -> bool {
self.data_type.is_little_endian()
}
pub fn is_signed(&self) -> bool {
matches!(self.data_type, DataType::IntLe | DataType::IntBe)
}
pub fn is_float(&self) -> bool {
matches!(self.data_type, DataType::FloatLe | DataType::FloatBe)
}
pub fn data_link(&self) -> u64 {
self.data_link
}
pub fn convert(&self, raw: f64) -> f64 {
self.conversion.convert(raw, self.is_float())
}
pub fn unreadable(&self) -> Option<UnreadableReason> {
self.unreadable
}
pub fn is_array(&self) -> bool {
self.array_shape.is_some()
}
pub fn array_shape(&self) -> Option<&[u64]> {
self.array_shape.as_deref()
}
pub fn value_kind(&self) -> ValueKind {
if self.array_shape.is_some() {
return ValueKind::F64;
}
if self.data_type.is_string() && self.conversion.input() != ConversionInput::Text {
return ValueKind::Str;
}
match self.conversion.output() {
ConversionOutput::Text => return ValueKind::Str,
ConversionOutput::Unsupported => return ValueKind::F64,
ConversionOutput::Numeric => {}
}
if !self.conversion.is_identity() {
return ValueKind::F64;
}
self.raw_value_kind()
}
pub fn raw_value_kind(&self) -> ValueKind {
if self.channel_type.is_virtual() {
return ValueKind::U64;
}
let bits = self.bit_count;
match self.data_type {
DataType::UIntLe | DataType::UIntBe => {
if bits <= 8 {
ValueKind::U8
} else if bits <= 16 {
ValueKind::U16
} else if bits <= 32 {
ValueKind::U32
} else {
ValueKind::U64
}
}
DataType::IntLe | DataType::IntBe => {
if bits <= 8 {
ValueKind::I8
} else if bits <= 16 {
ValueKind::I16
} else if bits <= 32 {
ValueKind::I32
} else {
ValueKind::I64
}
}
DataType::FloatLe | DataType::FloatBe => {
if bits <= 32 {
ValueKind::F32
} else {
ValueKind::F64
}
}
DataType::StringSbc
| DataType::StringUtf8
| DataType::StringUtf16Le
| DataType::StringUtf16Be => ValueKind::Str,
DataType::ComplexLe | DataType::ComplexBe => ValueKind::Complex,
DataType::CaNopenDate => ValueKind::CanopenDate,
DataType::CaNopenTime => ValueKind::CanopenTime,
_ => ValueKind::Bytes,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FileStatistics {
pub data_group_count: usize,
pub channel_group_count: usize,
pub channel_count: usize,
pub total_sample_count: u64,
pub file_size: u64,
}
impl FileStatistics {
pub fn from_data_groups(data_groups: &[DataGroup], file_size: u64) -> Self {
let mut stats = FileStatistics {
file_size,
..Default::default()
};
stats.data_group_count = data_groups.len();
for dg in data_groups {
stats.channel_group_count += dg.channel_groups.len();
for cg in &dg.channel_groups {
stats.channel_count += cg.channels.len();
stats.total_sample_count += cg.sample_count;
}
}
stats
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct MlsdLength {
pub byte_offset: u32,
pub bit_offset: u8,
pub bit_count: u32,
pub little_endian: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct ArrayElement {
pub data_type: DataType,
pub bit_count: u32,
pub bit_offset: u8,
pub byte_offset: u32,
pub inverse_layout: bool,
pub stride: usize,
pub element_offsets: Option<Vec<usize>>,
pub storage: CaStorage,
pub group_links: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptionInfo {
pub encrypted: bool,
pub algorithm: String,
pub original_md5_sum: String,
pub original_size: usize,
}
#[derive(Debug, Clone)]
pub struct Attachment {
pub file_name: String,
pub file_path: String,
pub comment: String,
pub is_embedded: bool,
pub is_compressed: bool,
pub original_size: u64,
pub md5_checksum: [u8; 16],
pub md5_valid: bool,
pub(crate) embedded_offset: u64,
pub(crate) embedded_size: u64,
}
impl Attachment {
pub fn embedded_size(&self) -> u64 {
self.embedded_size
}
pub fn encryption_info(&self) -> Option<EncryptionInfo> {
extract_encryption_info(&self.comment)
}
pub fn is_encrypted(&self) -> bool {
self.encryption_info().is_some_and(|info| info.encrypted)
}
}
fn extract_encryption_info(comment: &str) -> Option<EncryptionInfo> {
if !comment.contains("<encrypted>") && !comment.contains("<encrypted ") {
return None;
}
let mut reader = quick_xml::Reader::from_str(comment);
reader.config_mut().trim_text(true);
let mut in_extension = false;
let mut current_tag = Vec::new();
let mut encrypted = false;
let mut algorithm = String::new();
let mut original_md5_sum = String::new();
let mut original_size = 0usize;
loop {
match reader.read_event() {
Ok(quick_xml::events::Event::Start(e)) => {
let name = e.name().as_ref().to_vec();
if name == b"extension" {
in_extension = true;
}
current_tag = name;
}
Ok(quick_xml::events::Event::Text(t)) if in_extension => {
if let Ok(text) = t.unescape() {
match current_tag.as_slice() {
b"encrypted" => {
encrypted = text.trim().eq_ignore_ascii_case("true");
}
b"algorithm" => {
algorithm = text.trim().to_string();
}
b"original_md5_sum" => {
original_md5_sum = text.trim().to_string();
}
b"original_size" => {
original_size = text.trim().parse().unwrap_or(0);
}
_ => {}
}
}
}
Ok(quick_xml::events::Event::End(e)) => {
if e.name().as_ref() == b"extension" {
if encrypted {
return Some(EncryptionInfo {
encrypted,
algorithm,
original_md5_sum,
original_size,
});
}
in_extension = false;
}
current_tag.clear();
}
Ok(quick_xml::events::Event::Eof) | Err(_) => break,
_ => {}
}
}
if encrypted {
Some(EncryptionInfo {
encrypted,
algorithm,
original_md5_sum,
original_size,
})
} else {
None
}
}
#[derive(Debug, Clone)]
pub struct Event {
pub event_type: EventType,
pub sync_type: EvSyncType,
pub range_type: EvRangeType,
pub cause: EvCause,
pub sync_base_value: i64,
pub sync_factor: f64,
pub scope_count: u32,
pub attachment_count: u16,
pub comment: String,
pub name: String,
}
impl Event {
pub fn position(&self) -> f64 {
self.sync_base_value as f64 * self.sync_factor
}
}
#[derive(Debug, Clone)]
pub struct SampleReduction {
pub cycle_count: u64,
pub interval: f64,
pub sync_type: SrSyncType,
pub flags: u8,
pub(crate) data_link: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReductionKind {
Mean,
Min,
Max,
}
impl ReductionKind {
pub(crate) fn index(self) -> usize {
match self {
ReductionKind::Mean => 0,
ReductionKind::Min => 1,
ReductionKind::Max => 2,
}
}
}
#[derive(Debug, Clone)]
pub struct FileHistoryEntry {
pub time: RecordingTime,
pub comment: String,
pub metadata: Metadata,
}
impl FileHistoryEntry {
pub fn tool_id(&self) -> Option<&str> {
self.metadata.get("tool_id")
}
pub fn tool_vendor(&self) -> Option<&str> {
self.metadata.get("tool_vendor")
}
pub fn tool_version(&self) -> Option<&str> {
self.metadata.get("tool_version")
}
}
#[derive(Debug, Clone)]
pub struct ChannelHierarchyNode {
pub name: String,
pub comment: String,
pub hierarchy_type: ChType,
pub elements: Vec<ChElement>,
pub has_children: bool,
pub children: Vec<ChannelHierarchyNode>,
}