|
22 | 22 | use std::path::Path; |
23 | 23 |
|
24 | 24 | use crate::error::{Error, Result}; |
25 | | -use crate::model::{Beat, LoadedLog, LogKind, Payload}; |
| 25 | +use crate::model::{Beat, Iface, LoadedLog, LogKind, Payload}; |
26 | 26 | use crate::value::Value; |
27 | 27 |
|
28 | 28 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
@@ -564,6 +564,45 @@ pub fn describe(beat: &Beat) -> String { |
564 | 564 | } |
565 | 565 | } |
566 | 566 |
|
| 567 | +fn side_channel_width(iface: &Iface, name: &str) -> u32 { |
| 568 | + match iface { |
| 569 | + Iface::Hci { uw, iw, ew, .. } => match name { |
| 570 | + "user" => *uw, |
| 571 | + "id" => *iw, |
| 572 | + _ => *ew, |
| 573 | + }, |
| 574 | + Iface::Stream { .. } => 0, |
| 575 | + } |
| 576 | +} |
| 577 | + |
| 578 | +/// Refuse to compare a side channel that one of the logs does not carry. |
| 579 | +/// |
| 580 | +/// Older HCI-Core interfaces have no `id` or `ecc` signals at all, so their logs |
| 581 | +/// declare `IW = 0` / `EW = 0` and leave those fields out. Comparing them anyway |
| 582 | +/// would silently treat the absent side as zero and report a difference for |
| 583 | +/// every transaction -- a misalignment that says nothing about the design. |
| 584 | +pub fn check_side_channels(a: &LoadedLog, b: &LoadedLog, ctx: &CmpCtx) -> Result<()> { |
| 585 | + for (on, name, flag) in [ |
| 586 | + (ctx.spec.user, "user", "--check-user"), |
| 587 | + (ctx.spec.id, "id", "--check-id"), |
| 588 | + (ctx.spec.ecc, "ecc", "--check-ecc"), |
| 589 | + ] { |
| 590 | + if !on { |
| 591 | + continue; |
| 592 | + } |
| 593 | + for (tag, log) in [("A", a), ("B", b)] { |
| 594 | + if side_channel_width(&log.iface, name) == 0 { |
| 595 | + return Err(Error::Usage(format!( |
| 596 | + "{flag} was given, but {tag} ({}) declares no `{name}` side channel: \ |
| 597 | + comparing it would report a difference for every transaction", |
| 598 | + log.file.display() |
| 599 | + ))); |
| 600 | + } |
| 601 | + } |
| 602 | + } |
| 603 | + Ok(()) |
| 604 | +} |
| 605 | + |
567 | 606 | /// `--x-policy=error`: refuse to go on if any *enabled* byte carries x/z. |
568 | 607 | pub fn check_unknowns(log: &LoadedLog, side: Side, ctx: &CmpCtx) -> Result<()> { |
569 | 608 | if ctx.opts.x != XPolicy::Error { |
@@ -702,6 +741,27 @@ mod tests { |
702 | 741 | assert!(beats_equal(&a, &b, &lenient)); |
703 | 742 | } |
704 | 743 |
|
| 744 | + #[test] |
| 745 | + fn refuses_to_compare_a_side_channel_that_is_not_there() { |
| 746 | + // An old HCI-Core interface has no `id` signal at all, so its log |
| 747 | + // declares IW = 0; comparing `id` anyway would flag every transaction. |
| 748 | + let no_id = req_log(vec![req_beat(0, 1, "0x0", "0x1", "0xf")]); |
| 749 | + let c = CmpCtx::new( |
| 750 | + Mode::HciReq, |
| 751 | + &no_id, |
| 752 | + &no_id, |
| 753 | + 32, |
| 754 | + CompareOptions { check_id: true, ..Default::default() }, |
| 755 | + ); |
| 756 | + let err = check_side_channels(&no_id, &no_id, &c).unwrap_err(); |
| 757 | + assert!(err.to_string().contains("--check-id"), "{err}"); |
| 758 | + |
| 759 | + // With the side channel present, it is compared as asked. |
| 760 | + let mut with_id = req_log(vec![req_beat(0, 1, "0x0", "0x1", "0xf")]); |
| 761 | + with_id.iface = Iface::Hci { dw: 32, aw: 32, bw: 8, uw: 0, iw: 8, ew: 0, ehw: 0 }; |
| 762 | + assert!(check_side_channels(&with_id, &with_id, &c).is_ok()); |
| 763 | + } |
| 764 | + |
705 | 765 | #[test] |
706 | 766 | fn keep_writes_drops_loads() { |
707 | 767 | let mut log = req_log(vec![ |
|
0 commit comments