1 ///! Rule group for tracking command collision issues.
2 use chrono::NaiveDateTime;
3 use std::convert::Into;
4 use std::io::Write;
5
6 use crate::engine::{Rule, RuleGroup, Signal};
7 use crate::parser::{Packet, PacketChild};
8 use hcidoc_packets::hci::{ErrorCode, EventChild, OpCode};
9
10 enum CollisionSignal {
11 RnrAndInquiry,
12 RnrAndConnection,
13 InquiryAndConnection,
14 InquirySelf,
15 }
16
17 impl Into<&'static str> for CollisionSignal {
into(self) -> &'static str18 fn into(self) -> &'static str {
19 match self {
20 CollisionSignal::RnrAndInquiry => "RnR-Inquiry-Collision",
21 CollisionSignal::RnrAndConnection => "RnR-Connection-Collision",
22 CollisionSignal::InquiryAndConnection => "Inquiry-Connection-Collision",
23 CollisionSignal::InquirySelf => "Inquiry-Self-Collision",
24 }
25 }
26 }
27
28 // What state are we in for the serializable state?
29 #[derive(Debug, PartialEq)]
30 enum CollisionState {
31 Nothing,
32 ConnectionActive,
33 RemoteNameReqActive,
34 InquiryActive,
35 }
36
37 /// This rule keeps track of collisions that occur due to serializable commands
38 /// not being serialized correctly: Create Connection, Remote Name Request and Inquiry.
39 struct ConnectionSerializationRule {
40 /// What's the current state of the serializable commands?
41 state: CollisionState,
42
43 /// When was the last state set? (Except Nothing)
44 state_set_at: Option<(usize, NaiveDateTime)>,
45
46 /// Pre-defined signals discovered in the logs.
47 signals: Vec<Signal>,
48
49 /// Interesting occurrences surfaced by this rule.
50 reportable: Vec<(NaiveDateTime, String)>,
51 }
52
53 impl ConnectionSerializationRule {
new() -> Self54 pub fn new() -> Self {
55 ConnectionSerializationRule {
56 state: CollisionState::Nothing,
57 state_set_at: None,
58 signals: vec![],
59 reportable: vec![],
60 }
61 }
62
63 // Determine signal to emit based on the currently active state.
get_signal_type(&self, opcode: &OpCode) -> Option<CollisionSignal>64 pub(crate) fn get_signal_type(&self, opcode: &OpCode) -> Option<CollisionSignal> {
65 match (opcode, &self.state) {
66 (&OpCode::CreateConnection, &CollisionState::InquiryActive) => {
67 Some(CollisionSignal::InquiryAndConnection)
68 }
69
70 (&OpCode::CreateConnection, &CollisionState::RemoteNameReqActive) => {
71 Some(CollisionSignal::RnrAndConnection)
72 }
73
74 (&OpCode::Inquiry, &CollisionState::InquiryActive) => {
75 Some(CollisionSignal::InquirySelf)
76 }
77
78 (&OpCode::Inquiry, &CollisionState::RemoteNameReqActive) => {
79 Some(CollisionSignal::RnrAndInquiry)
80 }
81
82 (&OpCode::Inquiry, &CollisionState::ConnectionActive) => {
83 Some(CollisionSignal::InquiryAndConnection)
84 }
85
86 (&OpCode::RemoteNameRequest, &CollisionState::InquiryActive) => {
87 Some(CollisionSignal::RnrAndInquiry)
88 }
89
90 (&OpCode::RemoteNameRequest, &CollisionState::ConnectionActive) => {
91 Some(CollisionSignal::RnrAndConnection)
92 }
93
94 (_, _) => None,
95 }
96 }
97 }
98
99 impl Rule for ConnectionSerializationRule {
process(&mut self, packet: &Packet)100 fn process(&mut self, packet: &Packet) {
101 match &packet.inner {
102 PacketChild::HciEvent(ev) => match ev.specialize() {
103 // Most of the serializable commands will get "Disallowed" on
104 // command status except `Remote Name Req`.
105 EventChild::CommandStatus(cs) => match cs.get_command_op_code() {
106 OpCode::CreateConnection | OpCode::Inquiry | OpCode::RemoteNameRequest => {
107 // Set the state to the new successful command if it was
108 // previously not set.
109 if cs.get_status() == ErrorCode::Success
110 && self.state == CollisionState::Nothing
111 {
112 let new_state: Option<CollisionState> = match cs.get_command_op_code() {
113 OpCode::CreateConnection => Some(CollisionState::ConnectionActive),
114 OpCode::Inquiry => Some(CollisionState::InquiryActive),
115 OpCode::RemoteNameRequest => {
116 Some(CollisionState::RemoteNameReqActive)
117 }
118 _ => None,
119 };
120
121 if let Some(new_state) = new_state {
122 self.state = new_state;
123 self.state_set_at = Some((packet.index, packet.ts.clone()));
124 }
125 }
126 // We've hit a disallowed status. Check if we're
127 // conflicting with something that should be serializable.
128 else if cs.get_status() == ErrorCode::CommandDisallowed {
129 self.reportable.push((
130 packet.ts,
131 format!("Command Status was 'Disallowed' on {:?}. Potential conflict with: {:?} at {:?}",
132 cs.get_command_op_code(), self.state, self.state_set_at)
133 ));
134
135 let signal: Option<CollisionSignal> =
136 self.get_signal_type(&cs.get_command_op_code());
137
138 if let Some(signal) = signal {
139 self.signals.push(Signal {
140 index: packet.index,
141 ts: packet.ts.clone(),
142 tag: signal.into(),
143 });
144 }
145 }
146 }
147
148 _ => (),
149 },
150
151 // RnR will only tell you "Disallowed" on the event itself.
152 // However, we should handle this in CommandStatus as well in case
153 // this is a controller quirk.
154 EventChild::RemoteNameRequestComplete(rnr_ev) => {
155 // Everything except "Disallowed" resets the state.
156 if rnr_ev.get_status() != ErrorCode::CommandDisallowed
157 && self.state == CollisionState::RemoteNameReqActive
158 {
159 self.state = CollisionState::Nothing;
160 self.state_set_at = None;
161 } else if rnr_ev.get_status() == ErrorCode::CommandDisallowed {
162 self.reportable.push((
163 packet.ts,
164 format!("Remote name req complete with disallowed. Potential conflict with: {:?} at {:?}",
165 self.state, self.state_set_at)
166
167 ));
168
169 // Insert signals based on current serializable state.
170 let signal = self.get_signal_type(&OpCode::RemoteNameRequest);
171
172 if let Some(signal) = signal {
173 self.signals.push(Signal {
174 index: packet.index,
175 ts: packet.ts.clone(),
176 tag: signal.into(),
177 });
178 }
179 }
180 }
181
182 EventChild::InquiryComplete(_) => {
183 // Inquiry was observed to be disallowed only on |CommandStatus|
184 // so always clear state here.
185 if self.state == CollisionState::InquiryActive {
186 self.state = CollisionState::Nothing;
187 self.state_set_at = None;
188 }
189 }
190
191 EventChild::ConnectionComplete(_) => {
192 // Connection was observed to be disallowed only on |CommandStatus|
193 // so always clear state here.
194 if self.state == CollisionState::ConnectionActive {
195 self.state = CollisionState::Nothing;
196 self.state_set_at = None;
197 }
198 }
199
200 _ => {}
201 },
202
203 _ => {}
204 }
205 }
206
report(&self, writer: &mut dyn Write)207 fn report(&self, writer: &mut dyn Write) {
208 if self.reportable.len() > 0 {
209 let _ = writeln!(writer, "ConnectionSerializationRule report:");
210 for (ts, message) in self.reportable.iter() {
211 let _ = writeln!(writer, "[{:?}] {}", ts, message);
212 }
213 }
214 }
215
report_signals(&self) -> &[Signal]216 fn report_signals(&self) -> &[Signal] {
217 self.signals.as_slice()
218 }
219 }
220
221 /// Get a rule group with collision rules.
get_collisions_group() -> RuleGroup222 pub fn get_collisions_group() -> RuleGroup {
223 let mut group = RuleGroup::new();
224 group.add_rule(Box::new(ConnectionSerializationRule::new()));
225
226 group
227 }
228