#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use serde_json::json;
use zerodds_ast_layout::{Encoding, Endianness};
use zerodds_wire_probe::encode;
use zerodds_wire_probe::encode::{ProbeError, hex, model_from_idl, probe};
use zerodds_wire_probe::value::{Emit, MapLengthPolicy, ProbeRequest};
fn bytes(idl: &str, type_name: &str, values: &serde_json::Value, encoding: Encoding) -> String {
hex(&probe(idl, type_name, values, encoding, Endianness::Little).expect("the probe"))
}
fn key_hash(idl: &str, type_name: &str, values: &serde_json::Value) -> String {
let ast = model_from_idl(idl, "probe.idl").expect("the model");
let request = ProbeRequest::new(type_name, Encoding::PlainCdr2, Endianness::Little)
.emitting(Emit::KeyHash);
hex(&encode(&ast, &request, values).expect("the probe"))
}
fn refusal(idl: &str, type_name: &str, values: &serde_json::Value) -> ProbeError {
probe(
idl,
type_name,
values,
Encoding::PlainCdr2,
Endianness::Little,
)
.expect_err("a refusal")
}
#[test]
fn a_wstring_counts_bytes_and_writes_no_terminator() {
let idl = "@final struct WStr { wstring w; long n; };";
assert_eq!(
bytes(
idl,
"WStr",
&json!({ "w": "ab", "n": 7 }),
Encoding::PlainCdr2
),
"040000006100620007000000"
);
assert_eq!(
bytes(
idl,
"WStr",
&json!({ "w": "", "n": 7 }),
Encoding::PlainCdr2
),
"0000000007000000"
);
}
#[test]
fn a_wstring_outside_the_bmp_is_four_bytes_for_one_character() {
let idl = "@final struct WStr { wstring w; long n; };";
assert_eq!(
bytes(
idl,
"WStr",
&json!({ "w": "\u{1d11e}", "n": 7 }),
Encoding::PlainCdr2
),
"0400000034d81edd07000000"
);
}
#[test]
fn a_wstring_is_the_same_under_both_encoding_versions() {
let idl = "@final struct WStr { wstring w; long n; };";
let values = json!({ "w": "ab", "n": 7 });
assert_eq!(
bytes(idl, "WStr", &values, Encoding::PlainCdr1),
bytes(idl, "WStr", &values, Encoding::PlainCdr2)
);
}
#[test]
fn a_bitmask_is_the_integer_its_bit_bound_calls_for() {
let narrow = "\
@bit_bound(8) bitmask Perm { READ, WRITE, EXEC };
@final struct B8 { Perm p; };";
assert_eq!(
bytes(
narrow,
"B8",
&json!({ "p": ["READ", "WRITE"] }),
Encoding::PlainCdr2
),
"03"
);
let wide = "\
bitmask Flags { A, B, C };
@final struct B32 { Flags f; };";
assert_eq!(
bytes(
wide,
"B32",
&json!({ "f": ["A", "C"] }),
Encoding::PlainCdr2
),
"05000000"
);
assert_eq!(
bytes(wide, "B32", &json!({ "f": [] }), Encoding::PlainCdr2),
"00000000"
);
}
#[test]
fn an_explicit_position_moves_the_bits_the_specification_example_moves() {
let idl = "\
@bit_bound(19) bitmask MyFlags {
FIRST_FLAG,
@position(14) SECOND_FLAG,
THIRD_FLAG
};
@final struct Holder { MyFlags f; };";
assert_eq!(
bytes(
idl,
"Holder",
&json!({ "f": ["FIRST_FLAG", "THIRD_FLAG"] }),
Encoding::PlainCdr2
),
"01800000"
);
}
#[test]
fn a_bit_above_the_bound_is_refused() {
let idl = "\
@bit_bound(2) bitmask Narrow { A, B, C };
@final struct Holder { Narrow f; };";
let error = refusal(idl, "Holder", &json!({ "f": ["C"] }));
let said = format!("{error}");
assert!(
said.contains("§7.4.13.4.3.3") && said.contains("bit mask holds no more values"),
"{said}"
);
}
#[test]
fn a_misspelled_bit_name_is_an_error_and_not_a_cleared_bit() {
let idl = "\
bitmask Flags { A, B };
@final struct Holder { Flags f; };";
let error = refusal(idl, "Holder", &json!({ "f": ["a"] }));
assert!(
matches!(&error, ProbeError::Unknown { path, declared }
if path == "Holder.f[0]" && declared == &["A".to_string(), "B".to_string()]),
"{error}"
);
}
#[test]
fn a_bitset_packs_its_fields_from_the_least_significant_bit_up() {
let idl = "\
bitset B { bitfield<3> low; bitfield<12> high; };
@final struct Holder { B packed; };";
assert_eq!(
bytes(
idl,
"Holder",
&json!({ "packed": { "low": 5, "high": 4000 } }),
Encoding::PlainCdr2
),
"057d"
);
}
#[test]
fn an_anonymous_bitfield_shifts_the_field_above_it() {
let idl = "\
bitset P { bitfield<3> a; bitfield<5>; bitfield<8> b; };
@final struct Holder { P padded; };";
assert_eq!(
bytes(
idl,
"Holder",
&json!({ "padded": { "a": 7, "b": 171 } }),
Encoding::PlainCdr2
),
"07ab"
);
}
#[test]
fn a_bitfield_value_that_does_not_fit_is_refused_and_not_masked() {
let idl = "\
bitset B { bitfield<3> low; bitfield<12> high; };
@final struct Holder { B packed; };";
let error = refusal(idl, "Holder", &json!({ "packed": { "low": 8, "high": 0 } }));
assert!(
matches!(&error, ProbeError::Mismatch { path, .. } if path == "Holder.packed.low"),
"{error}"
);
}
#[test]
fn an_inheriting_bitset_packs_the_base_at_the_low_positions() {
let idl = "\
bitset Base { bitfield<3> low; };
bitset Derived : Base { bitfield<5> high; };
@final struct Holder { Derived d; octet tail; };";
let values = json!({ "d": { "low": 5, "high": 17 }, "tail": 171 });
assert_eq!(bytes(idl, "Holder", &values, Encoding::PlainCdr2), "8dab");
assert_eq!(bytes(idl, "Holder", &values, Encoding::PlainCdr1), "8dab");
let deep = "\
bitset B1 { bitfield<8> a; };
bitset B2 : B1 { bitfield<8> b; };
bitset B3 : B2 { bitfield<4> c; };
@final struct Wide { B3 value; octet tail; };";
let deep_values = json!({ "value": { "a": 17, "b": 34, "c": 3 }, "tail": 171 });
assert_eq!(
bytes(deep, "Wide", &deep_values, Encoding::PlainCdr2),
"11220300ab"
);
}
#[test]
fn a_map_of_primitives_is_a_length_and_the_pairs() {
let idl = "@final struct MapLL { map<long, long> m; };";
let values = json!({ "m": [[1, 2], [3, 4]] });
assert_eq!(
bytes(idl, "MapLL", &values, Encoding::PlainCdr2),
"0200000001000000020000000300000004000000"
);
assert_eq!(
bytes(idl, "MapLL", &values, Encoding::PlainCdr1),
"0200000001000000020000000300000004000000"
);
}
#[test]
fn a_map_with_a_structured_value_is_delimited_under_xcdr2() {
let idl = "\
@final struct Cell { long a; };
@final struct MapLS { map<long, Cell> m; };";
let values = json!({ "m": [[1, { "a": 2 }]] });
assert_eq!(
bytes(idl, "MapLS", &values, Encoding::PlainCdr2),
"0c000000010000000100000002000000"
);
assert_eq!(
bytes(idl, "MapLS", &values, Encoding::PlainCdr1),
"010000000100000002000000"
);
}
#[test]
fn the_two_readings_of_a_delimited_maps_length_differ_by_four_bytes() {
let idl = "\
@final struct Cell { long a; };
@final struct MapLS { map<long, Cell> m; };";
let values = json!({ "m": [[1, { "a": 2 }]] });
let ast = model_from_idl(idl, "probe.idl").expect("the model");
let equivalence = ProbeRequest::new("MapLS", Encoding::PlainCdr2, Endianness::Little);
assert_eq!(
hex(&encode(&ast, &equivalence, &values).expect("bytes")),
"0c000000010000000100000002000000",
"§7.4.1.1.4 read as an equivalence to a sequence"
);
let rule_15 = ProbeRequest {
map_length: MapLengthPolicy::Rule15Omits,
..equivalence
};
assert_eq!(
hex(&encode(&ast, &rule_15, &values).expect("bytes")),
"080000000100000002000000",
"rule (15) read literally"
);
}
#[test]
fn a_map_entry_that_is_not_a_pair_is_an_error_naming_its_index() {
let idl = "@final struct MapLL { map<long, long> m; };";
let error = refusal(idl, "MapLL", &json!({ "m": [[1, 2], [3]] }));
assert!(
matches!(&error, ProbeError::Mismatch { path, .. } if path == "MapLL.m[1]"),
"{error}"
);
}
#[test]
fn a_bounded_map_refuses_more_entries_than_its_bound() {
let idl = "@final struct MapB { map<long, long, 2> m; };";
let error = refusal(idl, "MapB", &json!({ "m": [[1, 2], [3, 4], [5, 6]] }));
assert!(
matches!(&error, ProbeError::Mismatch { path, .. } if path == "MapB.m"),
"{error}"
);
}
#[test]
fn a_fixed_is_packed_bcd_with_a_sign_nibble() {
let idl = "@final struct Money { fixed<5,2> amount; };";
assert_eq!(
bytes(
idl,
"Money",
&json!({ "amount": "123.45" }),
Encoding::PlainCdr2
),
"12345c"
);
assert_eq!(
bytes(
idl,
"Money",
&json!({ "amount": "-1.5" }),
Encoding::PlainCdr2
),
"00150d"
);
assert_eq!(
bytes(
idl,
"Money",
&json!({ "amount": "-0.00" }),
Encoding::PlainCdr2
),
"00000c"
);
}
#[test]
fn an_odd_digit_count_takes_a_leading_pad_nibble() {
let idl = "@final struct Count { fixed<4,0> n; };";
assert_eq!(
bytes(idl, "Count", &json!({ "n": "1234" }), Encoding::PlainCdr2),
"01234c"
);
}
#[test]
fn a_fixed_takes_no_alignment_of_its_own() {
let idl = "@final struct Row { fixed<5,2> amount; long n; };";
assert_eq!(
bytes(
idl,
"Row",
&json!({ "amount": "123.45", "n": 7 }),
Encoding::PlainCdr2
),
"12345c0007000000"
);
}
#[test]
fn a_fixed_that_overflows_its_digits_is_refused() {
let idl = "@final struct Money { fixed<5,2> amount; };";
for value in ["1234.5", "1.234", "12x.45"] {
let error = refusal(idl, "Money", &json!({ "amount": value }));
assert!(
matches!(&error, ProbeError::Mismatch { path, .. } if path == "Money.amount"),
"{value}: {error}"
);
}
}
#[test]
fn a_fixed_given_a_json_number_is_refused() {
let idl = "@final struct Money { fixed<5,2> amount; };";
let error = refusal(idl, "Money", &json!({ "amount": 123.45 }));
assert!(
matches!(&error, ProbeError::Mismatch { path, .. } if path == "Money.amount"),
"{error}"
);
}
#[test]
fn a_sequence_of_wstrings_is_delimited_and_each_element_counts_bytes() {
let idl = "@final struct WSeq { sequence<wstring> ws; };";
let values = json!({ "ws": ["ab", "c"] });
assert_eq!(
bytes(idl, "WSeq", &values, Encoding::PlainCdr2),
"12000000020000000400000061006200020000006300"
);
}
#[test]
fn a_typedef_of_a_fixed_writes_what_the_target_writes() {
let direct = "@final struct Money { fixed<5,2> sum; };";
let aliased = "\
typedef fixed<5,2> Amount;
@final struct Money { Amount sum; };";
let values = json!({ "sum": "123.45" });
assert_eq!(
bytes(aliased, "Money", &values, Encoding::PlainCdr2),
bytes(direct, "Money", &values, Encoding::PlainCdr2)
);
}
#[test]
fn a_bitset_in_a_collection_is_delimited_the_way_fast_dds_delimits_it() {
let seq = "\
bitset Packed { bitfield<3> low; bitfield<5> high; };
@final struct Sample { sequence<Packed> many; long tail; };";
let seq_values = json!({
"many": [{ "low": 5, "high": 17 }, { "low": 0, "high": 31 }],
"tail": -1
});
assert_eq!(
bytes(seq, "Sample", &seq_values, Encoding::PlainCdr2),
"06000000020000008df80000ffffffff"
);
assert_eq!(
bytes(seq, "Sample", &seq_values, Encoding::PlainCdr1),
"020000008df80000ffffffff"
);
let array = "\
bitset Packed { bitfield<3> low; bitfield<5> high; };
@final struct Sample { Packed items[2]; octet tail; };";
let array_values = json!({
"items": [{ "low": 5, "high": 17 }, { "low": 7, "high": 0 }],
"tail": 171
});
assert_eq!(
bytes(array, "Sample", &array_values, Encoding::PlainCdr2),
"020000008d07ab"
);
assert_eq!(
bytes(array, "Sample", &array_values, Encoding::PlainCdr1),
"8d07ab"
);
}
#[test]
fn a_bitset_key_member_is_its_anchor_and_the_hash_fast_dds_computes() {
let short = "\
bitset Packed { bitfield<3> low; bitfield<5> high; };
@final struct KeyS { @key Packed k; octet tail; };";
let short_values = json!({ "k": { "low": 5, "high": 17 }, "tail": 171 });
assert_eq!(
bytes(short, "KeyS", &short_values, Encoding::PlainCdr2),
"8dab"
);
assert_eq!(
key_hash(short, "KeyS", &short_values),
"8d000000000000000000000000000000"
);
let long = "\
bitset Wide { bitfield<12> a; bitfield<12> b; };
@final struct KeyW { @key Wide k; @key string<40> s; octet tail; };";
let long_values = json!({ "k": { "a": 273, "b": 546 }, "s": "abc", "tail": 171 });
assert_eq!(
bytes(long, "KeyW", &long_values, Encoding::PlainCdr2),
"112122000400000061626300ab"
);
assert_eq!(
key_hash(long, "KeyW", &long_values),
"0168f0070ddb81e794da3da67abebd41"
);
}
#[test]
fn each_reading_of_a_delimited_maps_length_is_the_bytes_of_one_implementation() {
let idl = "\
@final struct Cell { long a; long b; };
@final struct Sample { map<long, Cell> m; octet tail; };";
let values = json!({ "m": [[7, { "a": 1, "b": 2 }]], "tail": 171 });
let ast = model_from_idl(idl, "probe.idl").expect("the model");
let fast_dds = ProbeRequest::new("Sample", Encoding::PlainCdr2, Endianness::Little);
assert_eq!(
hex(&encode(&ast, &fast_dds, &values).expect("bytes")),
"1000000001000000070000000100000002000000ab",
"Fast DDS 3.6.1 writes the DHEADER and the entry count"
);
let open_dds = ProbeRequest {
map_length: MapLengthPolicy::Rule15Omits,
..fast_dds.clone()
};
assert_eq!(
hex(&encode(&ast, &open_dds, &values).expect("bytes")),
"0c000000070000000100000002000000ab",
"OpenDDS 3.34.0 writes the DHEADER and no count"
);
let version_one = ProbeRequest::new("Sample", Encoding::PlainCdr1, Endianness::Little);
assert_eq!(
hex(&encode(&ast, &version_one, &values).expect("bytes")),
"01000000070000000100000002000000ab"
);
}