use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeConsistencyKind {
DisallowTypeCoercion,
AllowTypeCoercion,
ForceTypeValidation,
}
impl TypeConsistencyKind {
#[must_use]
pub const fn to_u32(self) -> u32 {
match self {
Self::DisallowTypeCoercion => 0,
Self::AllowTypeCoercion => 1,
Self::ForceTypeValidation => 2,
}
}
#[must_use]
pub const fn from_u32(v: u32) -> Self {
match v {
1 => Self::AllowTypeCoercion,
2 => Self::ForceTypeValidation,
_ => Self::DisallowTypeCoercion,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypeConsistencyEnforcement {
pub kind: TypeConsistencyKind,
pub ignore_sequence_bounds: bool,
pub ignore_string_bounds: bool,
pub ignore_member_names: bool,
pub prevent_type_widening: bool,
pub force_type_validation: bool,
}
impl Default for TypeConsistencyEnforcement {
fn default() -> Self {
Self {
kind: TypeConsistencyKind::AllowTypeCoercion,
ignore_sequence_bounds: true,
ignore_string_bounds: true,
ignore_member_names: false,
prevent_type_widening: false,
force_type_validation: false,
}
}
}
impl TypeConsistencyEnforcement {
#[must_use]
pub fn to_bytes_le(self) -> Vec<u8> {
let mut out = Vec::with_capacity(12);
out.extend_from_slice(&self.kind.to_u32().to_le_bytes());
out.push(u8::from(self.ignore_sequence_bounds));
out.push(u8::from(self.ignore_string_bounds));
out.push(u8::from(self.ignore_member_names));
out.push(u8::from(self.prevent_type_widening));
out.push(u8::from(self.force_type_validation));
while out.len() % 4 != 0 {
out.push(0); }
out
}
#[must_use]
pub fn from_bytes_le(bytes: &[u8]) -> Self {
if bytes.len() < 4 {
return Self::default();
}
let mut k = [0u8; 4];
k.copy_from_slice(&bytes[..4]);
let kind = TypeConsistencyKind::from_u32(u32::from_le_bytes(k));
Self {
kind,
ignore_sequence_bounds: bytes.get(4).copied().unwrap_or(1) != 0,
ignore_string_bounds: bytes.get(5).copied().unwrap_or(1) != 0,
ignore_member_names: bytes.get(6).copied().unwrap_or(0) != 0,
prevent_type_widening: bytes.get(7).copied().unwrap_or(0) != 0,
force_type_validation: bytes.get(8).copied().unwrap_or(0) != 0,
}
}
}
#[repr(i16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataRepresentationId {
Xcdr1 = 0,
Xml = 1,
Xcdr2 = 2,
}
impl DataRepresentationId {
#[must_use]
pub const fn from_i16(v: i16) -> Option<Self> {
match v {
0 => Some(Self::Xcdr1),
1 => Some(Self::Xml),
2 => Some(Self::Xcdr2),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepresentationNegotiation {
Accepted(DataRepresentationId),
NoOverlap,
}
#[must_use]
pub fn negotiate_representation(
writer_offered: &[i16],
reader_accepted: &[i16],
) -> RepresentationNegotiation {
for w in writer_offered {
if reader_accepted.contains(w) {
if let Some(kind) = DataRepresentationId::from_i16(*w) {
return RepresentationNegotiation::Accepted(kind);
}
}
}
RepresentationNegotiation::NoOverlap
}
pub fn check_data_repr_extensibility(
repr: DataRepresentationId,
ext: ExtensibilityForRepr,
) -> Result<(), &'static str> {
use DataRepresentationId::*;
use ExtensibilityForRepr::*;
match (repr, ext) {
(Xcdr1, Final) | (Xcdr1, Mutable) => Ok(()),
(Xcdr1, Appendable) => Err(
"Tab.59 §7.6.3.1: XCDR1 unterstuetzt APPENDABLE nicht direkt — Encoder muss FINAL waehlen",
),
(Xcdr2, _) => Ok(()),
(Xml, _) => Ok(()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtensibilityForRepr {
Final,
Appendable,
Mutable,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn consistency_roundtrip_default() {
let c = TypeConsistencyEnforcement::default();
let bytes = c.to_bytes_le();
let decoded = TypeConsistencyEnforcement::from_bytes_le(&bytes);
assert_eq!(decoded, c);
}
#[test]
fn consistency_flags_all_set() {
let c = TypeConsistencyEnforcement {
kind: TypeConsistencyKind::ForceTypeValidation,
ignore_sequence_bounds: false,
ignore_string_bounds: false,
ignore_member_names: true,
prevent_type_widening: true,
force_type_validation: true,
};
let bytes = c.to_bytes_le();
let decoded = TypeConsistencyEnforcement::from_bytes_le(&bytes);
assert_eq!(decoded, c);
}
#[test]
fn negotiate_xcdr2_preferred() {
let result = negotiate_representation(&[2, 0], &[0, 2]);
assert_eq!(
result,
RepresentationNegotiation::Accepted(DataRepresentationId::Xcdr2)
);
}
#[test]
fn negotiate_fallback_to_xcdr1() {
let result = negotiate_representation(&[0], &[0, 2]);
assert_eq!(
result,
RepresentationNegotiation::Accepted(DataRepresentationId::Xcdr1)
);
}
#[test]
fn negotiate_no_overlap() {
let result = negotiate_representation(&[2], &[0]);
assert_eq!(result, RepresentationNegotiation::NoOverlap);
}
#[test]
fn xcdr1_final_combination_is_allowed() {
assert!(
check_data_repr_extensibility(DataRepresentationId::Xcdr1, ExtensibilityForRepr::Final)
.is_ok()
);
}
#[test]
fn xcdr1_mutable_combination_is_allowed() {
assert!(
check_data_repr_extensibility(
DataRepresentationId::Xcdr1,
ExtensibilityForRepr::Mutable
)
.is_ok()
);
}
#[test]
fn xcdr1_appendable_is_disallowed() {
let res = check_data_repr_extensibility(
DataRepresentationId::Xcdr1,
ExtensibilityForRepr::Appendable,
);
assert!(res.is_err());
}
#[test]
fn xcdr2_supports_all_three_extensibilities() {
for ext in [
ExtensibilityForRepr::Final,
ExtensibilityForRepr::Appendable,
ExtensibilityForRepr::Mutable,
] {
assert!(
check_data_repr_extensibility(DataRepresentationId::Xcdr2, ext).is_ok(),
"XCDR2 + {ext:?} muss zulaessig sein"
);
}
}
}