1pub mod args;
6
7use anyhow::{Context, Result, anyhow};
8use bind::bytecode_constants::{FALSE_VAL, RawOp, RawValueType, TRUE_VAL};
9use bind::compiler::Symbol;
10use bind::compiler::symbol_table::get_deprecated_key_identifier;
11use bind::interpreter::common::{BytecodeIter, next_u8, next_u32};
12use bind::interpreter::decode_bind_rules::{DecodedCompositeBindRules, DecodedRules};
13use bind::interpreter::match_bind::{DeviceProperties, MatchBindData, PropertyKey, match_bind};
14use fidl_fuchsia_driver_development as fdd;
15use fidl_fuchsia_driver_framework as fdf;
16use fuchsia_driver_dev as fdev;
17use itertools::Itertools;
18use std::collections::{HashMap, HashSet};
19use std::io;
20
21trait DiagnosableParent {
22 fn to_properties(&self) -> DeviceProperties;
23 fn evaluate_bind_rules(&self, properties: &DeviceProperties) -> Vec<Diagnostic>;
24 fn is_fuzzy_match(&self, properties: &DeviceProperties) -> bool;
25}
26
27impl DiagnosableParent for fdf::ParentSpec {
28 fn to_properties(&self) -> DeviceProperties {
29 node_to_bind_properties(Some(&self.properties))
30 }
31 fn evaluate_bind_rules(&self, properties: &DeviceProperties) -> Vec<Diagnostic> {
32 evaluate_bind_rules(&self.bind_rules, properties)
33 }
34 fn is_fuzzy_match(&self, properties: &DeviceProperties) -> bool {
35 is_spec_fuzzy_match(&self.bind_rules, properties)
36 }
37}
38
39impl DiagnosableParent for fdf::ParentSpec2 {
40 fn to_properties(&self) -> DeviceProperties {
41 node_to_bind_properties2(Some(&self.properties))
42 }
43 fn evaluate_bind_rules(&self, properties: &DeviceProperties) -> Vec<Diagnostic> {
44 evaluate_bind_rules2(&self.bind_rules, properties)
45 }
46 fn is_fuzzy_match(&self, properties: &DeviceProperties) -> bool {
47 is_spec_fuzzy_match2(&self.bind_rules, properties)
48 }
49}
50
51pub async fn doctor(
52 cmd: args::DoctorCommand,
53 driver_dev_proxy: fdd::ManagerProxy,
54 writer: &mut dyn io::Write,
55) -> Result<()> {
56 match (cmd.driver.as_ref(), cmd.node.as_ref(), cmd.composite_node_spec.as_ref()) {
57 (Some(driver_filter), Some(node_moniker), _) => {
58 let driver = resolve_driver(driver_filter, &driver_dev_proxy, writer).await?;
59 if let Some(driver) = driver {
60 diagnose_driver_and_node(&driver, node_moniker, &driver_dev_proxy, writer).await?;
61 }
62 }
63 (None, Some(node_moniker), Some(spec_name)) => {
64 diagnose_spec_and_node(spec_name, node_moniker, &driver_dev_proxy, writer).await?;
65 }
66 (Some(driver_filter), None, Some(spec_name)) => {
67 let driver = resolve_driver(driver_filter, &driver_dev_proxy, writer).await?;
68 if let Some(driver) = driver {
69 diagnose_driver_and_spec(&driver, spec_name, &driver_dev_proxy, writer).await?;
70 }
71 }
72 (Some(driver_filter), None, None) => {
73 let driver = resolve_driver(driver_filter, &driver_dev_proxy, writer).await?;
74 if let Some(driver) = driver {
75 diagnose_driver(&driver, &driver_dev_proxy, writer).await?;
76 }
77 }
78 (None, Some(node_moniker), None) => {
79 diagnose_node(node_moniker, &driver_dev_proxy, writer).await?;
80 }
81 (None, None, Some(spec_name)) => {
82 diagnose_spec(spec_name, &driver_dev_proxy, writer).await?;
83 }
84 (None, None, None) => {
85 diagnose_all(&driver_dev_proxy, writer).await?;
86 }
87 }
88
89 Ok(())
90}
91
92async fn resolve_driver(
93 filter: &str,
94 driver_dev_proxy: &fdd::ManagerProxy,
95 writer: &mut dyn io::Write,
96) -> Result<Option<fdf::DriverInfo>> {
97 let matched_drivers = fdev::get_drivers_from_query(filter, driver_dev_proxy).await?;
98
99 if matched_drivers.is_empty() {
100 writeln!(writer, "ERROR: No drivers matched the filter '{}'.", filter)?;
101 return Ok(None);
102 }
103
104 if matched_drivers.len() > 1 {
105 if let Some(exact) = matched_drivers.iter().find(|d| d.url.as_deref() == Some(filter)) {
106 return Ok(Some(exact.clone()));
107 }
108
109 writeln!(writer, "ERROR: Multiple drivers matched the filter '{}':", filter)?;
110 for driver in &matched_drivers {
111 writeln!(writer, " {}", driver.url.as_deref().unwrap_or("unknown"))?;
112 }
113 writeln!(writer, "Please provide a more specific filter.")?;
114 return Ok(None);
115 }
116
117 Ok(Some(matched_drivers[0].clone()))
118}
119
120async fn diagnose_driver_and_node(
121 driver: &fdf::DriverInfo,
122 node_moniker: &str,
123 driver_dev_proxy: &fdd::ManagerProxy,
124 writer: &mut dyn io::Write,
125) -> Result<()> {
126 let driver_url = driver.url.as_deref().unwrap_or("unknown");
127 writeln!(writer, "Diagnosing driver {} against node {}", driver_url, node_moniker)?;
128 let nodes = fdev::get_device_info(driver_dev_proxy, &[node_moniker.to_string()], true)
129 .await
130 .context("Failed to find node")?;
131 if nodes.is_empty() {
132 return Err(anyhow!("Node not found: {}", node_moniker));
133 }
134 let node = &nodes[0];
135
136 if let Some(bytecode) = &driver.bind_rules_bytecode {
137 match DecodedRules::new(bytecode.clone())? {
138 DecodedRules::Normal(rules) => {
139 let properties = node_to_bind_properties(node.node_property_list.as_deref());
140 if match_bind(
141 MatchBindData {
142 symbol_table: &rules.symbol_table,
143 instructions: &rules.instructions,
144 },
145 &properties,
146 )
147 .unwrap_or(false)
148 {
149 writeln!(writer, " Matches!")?;
150 } else {
151 let diagnostics = evaluate_rules_bytecode(
152 &rules.symbol_table,
153 &rules.instructions,
154 &properties,
155 );
156 report_diagnostics(&diagnostics, writer, 2)?;
157 }
158 }
159 DecodedRules::Composite(_) => {
160 writeln!(
161 writer,
162 "Driver is a composite driver. Use --composite-node-spec to diagnose if you know which spec it should match."
163 )?;
164 }
165 }
166 } else {
167 writeln!(writer, "Driver has no bind rules bytecode.")?;
168 }
169
170 Ok(())
171}
172
173async fn diagnose_spec_and_node(
174 spec_name: &str,
175 node_moniker: &str,
176 driver_dev_proxy: &fdd::ManagerProxy,
177 writer: &mut dyn io::Write,
178) -> Result<()> {
179 writeln!(writer, "Diagnosing composite node spec {} against node {}", spec_name, node_moniker)?;
180 let all_specs = fdev::get_composite_node_specs(driver_dev_proxy, None)
181 .await
182 .context("Failed to get composite node specs")?;
183 let spec = all_specs
184 .iter()
185 .find(|s| {
186 s.spec
187 .as_ref()
188 .and_then(|spec| spec.name.as_ref())
189 .map(|name| name == spec_name)
190 .unwrap_or(false)
191 })
192 .ok_or_else(|| anyhow!("Spec not found: {}", spec_name))?;
193
194 let nodes = fdev::get_device_info(driver_dev_proxy, &[node_moniker.to_string()], true)
195 .await
196 .context("Failed to find node")?;
197 if nodes.is_empty() {
198 return Err(anyhow!("Node not found: {}", node_moniker));
199 }
200 let node = &nodes[0];
201 let node_properties = node_to_bind_properties(node.node_property_list.as_deref());
202
203 if let Some(spec) = &spec.spec {
204 if let Some(parents) = &spec.parents {
205 for (i, parent) in parents.iter().enumerate() {
206 writeln!(writer, "\nComparing against spec parent {}:", i)?;
207 let diagnostics = parent.evaluate_bind_rules(&node_properties);
208 report_diagnostics(&diagnostics, writer, 2)?;
209 }
210 } else if let Some(parents2) = &spec.parents2 {
211 for (i, parent) in parents2.iter().enumerate() {
212 writeln!(writer, "\nComparing against spec parent {}:", i)?;
213 let diagnostics = parent.evaluate_bind_rules(&node_properties);
214 report_diagnostics(&diagnostics, writer, 2)?;
215 }
216 }
217 }
218
219 Ok(())
220}
221
222async fn diagnose_driver_and_spec(
223 driver: &fdf::DriverInfo,
224 spec_name: &str,
225 driver_dev_proxy: &fdd::ManagerProxy,
226 writer: &mut dyn io::Write,
227) -> Result<()> {
228 let driver_url = driver.url.as_deref().unwrap_or("unknown");
229 writeln!(writer, "Diagnosing driver {} against composite node spec {}", driver_url, spec_name)?;
230
231 let all_specs = fdev::get_composite_node_specs(driver_dev_proxy, None)
232 .await
233 .context("Failed to get composite node specs")?;
234
235 let spec = all_specs
236 .iter()
237 .find(|s| {
238 s.spec
239 .as_ref()
240 .and_then(|spec| spec.name.as_ref())
241 .map(|name| name == spec_name)
242 .unwrap_or(false)
243 })
244 .ok_or_else(|| anyhow!("Spec not found: {}", spec_name))?;
245
246 if let Some(bytecode) = &driver.bind_rules_bytecode {
247 match DecodedRules::new(bytecode.clone())? {
248 DecodedRules::Normal(_) => {
249 writeln!(
250 writer,
251 "Driver is NOT a composite driver, but it is being compared against a composite node spec."
252 )?;
253 }
254 DecodedRules::Composite(rules) => {
255 if let Some(spec) = &spec.spec {
256 if let Some(parents) = &spec.parents {
257 diagnose_composite_match(&rules, parents, writer)?;
258 } else if let Some(parents2) = &spec.parents2 {
259 diagnose_composite_match(&rules, parents2, writer)?;
260 } else {
261 writeln!(writer, " Spec '{}' has no parents.", spec_name)?;
262 }
263 } else {
264 writeln!(writer, " Spec '{}' is missing detailed information.", spec_name)?;
265 }
266 }
267 }
268 } else {
269 writeln!(writer, " Driver '{}' has no bind rules bytecode.", driver_url)?;
270 }
271
272 Ok(())
273}
274
275fn diagnose_composite_match<P: DiagnosableParent>(
276 rules: &DecodedCompositeBindRules,
277 parents: &[P],
278 writer: &mut dyn io::Write,
279) -> Result<()> {
280 if parents.is_empty() {
281 writeln!(writer, " Spec has no parents.")?;
282 return Ok(());
283 }
284
285 let mut matched_parents = HashSet::new();
286
287 writeln!(writer, "\nMatching primary node...")?;
288
289 let mut primary_matched = false;
290 for (p_idx, parent) in parents.iter().enumerate() {
291 let props = parent.to_properties();
292 if match_bind(
293 MatchBindData {
294 symbol_table: &rules.symbol_table,
295 instructions: &rules.primary_node.instructions,
296 },
297 &props,
298 )
299 .unwrap_or(false)
300 {
301 writeln!(writer, " Primary node matches parent {}.", p_idx)?;
302 primary_matched = true;
303 matched_parents.insert(p_idx);
304 break;
305 }
306 }
307
308 if !primary_matched {
309 writeln!(
310 writer,
311 " ERROR: Primary node matches no parent in spec. Mismatches against parent 0:"
312 )?;
313 let primary_props = parents[0].to_properties();
314 let diagnostics = evaluate_rules_bytecode(
315 &rules.symbol_table,
316 &rules.primary_node.instructions,
317 &primary_props,
318 );
319 report_diagnostics(&diagnostics, writer, 4)?;
320 }
321
322 for (i, node) in rules.additional_nodes.iter().enumerate() {
323 let node_name = rules
324 .symbol_table
325 .get(&node.name_id)
326 .cloned()
327 .unwrap_or_else(|| format!("additional_{}", i));
328 writeln!(writer, "\nMatching additional node {} ({})...", i, node_name)?;
329
330 let mut matched = false;
331 for (p_idx, parent) in parents.iter().enumerate() {
332 if matched_parents.contains(&p_idx) {
333 continue;
334 }
335 let props = parent.to_properties();
336 if match_bind(
337 MatchBindData {
338 symbol_table: &rules.symbol_table,
339 instructions: &node.instructions,
340 },
341 &props,
342 )
343 .unwrap_or(false)
344 {
345 writeln!(writer, " Matches parent {}.", p_idx)?;
346 matched = true;
347 matched_parents.insert(p_idx);
348 break;
349 }
350 }
351
352 if !matched {
353 writeln!(writer, " ERROR: No parent in spec matches additional node {}.", node_name)?;
354
355 let mut best_parent = None;
356 let mut best_score = 0;
357 let driver_keys = get_keys_from_instructions(&node.decoded_instructions);
358
359 for (p_idx, parent) in parents.iter().enumerate() {
360 if matched_parents.contains(&p_idx) {
361 continue;
362 }
363 let props = parent.to_properties();
364 let score = driver_keys.iter().filter(|k| props.contains_key(k)).count();
365 if score >= best_score {
366 best_score = score;
367 best_parent = Some(p_idx);
368 }
369 }
370
371 if let Some(p_idx) = best_parent {
372 writeln!(writer, " Mismatches against parent {}:", p_idx)?;
373 let props = parents[p_idx].to_properties();
374 let diags =
375 evaluate_rules_bytecode(&rules.symbol_table, &node.instructions, &props);
376 report_diagnostics(&diags, writer, 4)?;
377 } else {
378 writeln!(
379 writer,
380 " No available parent in the spec to compare against. This can happen if the driver has more nodes than the spec has parents."
381 )?;
382 }
383 }
384 }
385 Ok(())
386}
387
388async fn diagnose_driver(
389 driver: &fdf::DriverInfo,
390 driver_dev_proxy: &fdd::ManagerProxy,
391 writer: &mut dyn io::Write,
392) -> Result<()> {
393 let driver_url = driver.url.as_deref().unwrap_or("unknown");
394 writeln!(writer, "Diagnosing driver {}", driver_url)?;
395 if let Some(bytecode) = &driver.bind_rules_bytecode {
396 let rules = DecodedRules::new(bytecode.clone())?;
397
398 match &rules {
399 DecodedRules::Normal(r) => {
400 writeln!(writer, "\nFuzzy matching against all unbound nodes...")?;
401 let nodes = fdev::get_device_info(driver_dev_proxy, &[], false).await?;
402 let unbound_nodes = nodes.into_iter().filter(|n| is_node_unbound(n)).collect_vec();
403 for node in unbound_nodes {
404 let props = node_to_bind_properties(node.node_property_list.as_deref());
405 if is_fuzzy_match(&rules, &props) {
406 writeln!(
407 writer,
408 "\n------------------------------------------------------------\nPotential match found: node {}",
409 node.moniker.as_deref().unwrap_or("unknown")
410 )?;
411 if match_bind(
412 MatchBindData {
413 symbol_table: &r.symbol_table,
414 instructions: &r.instructions,
415 },
416 &props,
417 )
418 .unwrap_or(false)
419 {
420 writeln!(writer, " Matches!")?;
421 } else {
422 let diags =
423 evaluate_rules_bytecode(&r.symbol_table, &r.instructions, &props);
424 report_diagnostics(&diags, writer, 2)?;
425 }
426 }
427 }
428 }
429 DecodedRules::Composite(r) => {
430 writeln!(writer, "\nFuzzy matching against all unmatched composite node specs...")?;
431 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
432 let unmatched_specs =
433 specs.into_iter().filter(|s| s.matched_driver.is_none()).collect_vec();
434 for spec_info in unmatched_specs {
435 if let Some(spec) = &spec_info.spec {
436 let mut potential_match = false;
437 if let Some(parents) = &spec.parents {
438 if parents.iter().any(|p| is_fuzzy_match(&rules, &p.to_properties())) {
439 potential_match = true;
440 }
441 } else if let Some(parents2) = &spec.parents2 {
442 if parents2.iter().any(|p| is_fuzzy_match(&rules, &p.to_properties())) {
443 potential_match = true;
444 }
445 }
446
447 if potential_match {
448 writeln!(
449 writer,
450 "\n------------------------------------------------------------\nPotential match found: spec {}",
451 spec.name.as_deref().unwrap_or("unknown")
452 )?;
453 if let Some(parents) = &spec.parents {
454 diagnose_composite_match(r, parents, writer)?;
455 } else if let Some(parents2) = &spec.parents2 {
456 diagnose_composite_match(r, parents2, writer)?;
457 }
458 }
459 }
460 }
461 }
462 }
463 }
464 Ok(())
465}
466
467fn is_fuzzy_match(rules: &DecodedRules, properties: &DeviceProperties) -> bool {
468 let keys = match rules {
469 DecodedRules::Normal(r) => get_keys_from_instructions(&r.decoded_instructions),
470 DecodedRules::Composite(r) => {
471 let mut k = get_keys_from_instructions(&r.primary_node.decoded_instructions);
472 for n in &r.additional_nodes {
473 k.extend(get_keys_from_instructions(&n.decoded_instructions));
474 }
475 k
476 }
477 };
478 keys.iter().any(|k| properties.contains_key(k))
479}
480
481fn get_keys_from_instructions(
482 insts: &[bind::interpreter::instruction_decoder::DecodedInstruction],
483) -> Vec<PropertyKey> {
484 let mut keys = Vec::new();
485 for inst in insts {
486 if let bind::interpreter::instruction_decoder::DecodedInstruction::Condition(cond) = inst {
487 match &cond.lhs {
488 Symbol::NumberValue(v) => keys.push(PropertyKey::NumberKey(*v)),
489 Symbol::StringValue(v) => keys.push(PropertyKey::StringKey(v.clone())),
490 Symbol::Key(v, _) => keys.push(PropertyKey::StringKey(v.clone())),
491 _ => {}
492 }
493 }
494 }
495 keys
496}
497
498fn is_node_unbound(node: &fdd::NodeInfo) -> bool {
499 match &node.bound_driver_url {
500 None => true,
501 Some(url) => url == "unbound",
502 }
503}
504
505async fn diagnose_node_info(
506 node: &fdd::NodeInfo,
507 driver_dev_proxy: &fdd::ManagerProxy,
508 writer: &mut dyn io::Write,
509) -> Result<()> {
510 if !is_node_unbound(node) {
511 writeln!(
512 writer,
513 " Node is bound to {}",
514 node.bound_driver_url.as_deref().unwrap_or("unknown")
515 )?;
516 } else {
517 writeln!(writer, " Node is UNBOUND.")?;
518 if node.quarantined == Some(true) {
519 writeln!(writer, " Node is QUARANTINED (driver failed to start). Check driver logs.")?;
520 }
521
522 let properties = node_to_bind_properties(node.node_property_list.as_deref());
523
524 writeln!(writer, "\nFuzzy matching against all drivers...")?;
525 let drivers = fdev::get_driver_info(driver_dev_proxy, &[]).await?;
526 for driver in drivers {
527 if let Some(bytecode) = &driver.bind_rules_bytecode {
528 if let Ok(rules) = DecodedRules::new(bytecode.clone()) {
529 if is_fuzzy_match(&rules, &properties) {
530 writeln!(
531 writer,
532 "\n------------------------------------------------------------\nPotential match: driver {}",
533 driver.url.as_deref().unwrap_or("unknown")
534 )?;
535 match rules {
536 DecodedRules::Normal(r) => {
537 if match_bind(
538 MatchBindData {
539 symbol_table: &r.symbol_table,
540 instructions: &r.instructions,
541 },
542 &properties,
543 )
544 .unwrap_or(false)
545 {
546 writeln!(writer, " Matches!")?;
547 } else {
548 let diags = evaluate_rules_bytecode(
549 &r.symbol_table,
550 &r.instructions,
551 &properties,
552 );
553 report_diagnostics(&diags, writer, 2)?;
554 }
555 }
556 DecodedRules::Composite(_) => {
557 writeln!(
558 writer,
559 " This is a composite driver. For a detailed analysis, run `ffx driver doctor --driver {}` or use --composite-node-spec if you know which spec it should match.",
560 driver.url.as_deref().unwrap_or("unknown")
561 )?;
562 }
563 }
564 }
565 }
566 }
567 }
568
569 writeln!(writer, "\nFuzzy matching against all composite node specs...")?;
570 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
571 for spec_info in specs {
572 if let Some(spec) = &spec_info.spec {
573 if let Some(parents) = &spec.parents {
574 for (i, parent) in parents.iter().enumerate() {
575 if parent.is_fuzzy_match(&properties) {
576 let spec_name = spec.name.as_deref().unwrap_or("unknown");
577 writeln!(
578 writer,
579 "\n------------------------------------------------------------\nPotential match: spec {} parent {}",
580 spec_name, i
581 )?;
582 let diags = parent.evaluate_bind_rules(&properties);
583 report_diagnostics(&diags, writer, 2)?;
584 }
585 }
586 } else if let Some(parents2) = &spec.parents2 {
587 for (i, parent) in parents2.iter().enumerate() {
588 if parent.is_fuzzy_match(&properties) {
589 let spec_name = spec.name.as_deref().unwrap_or("unknown");
590 writeln!(
591 writer,
592 "\n------------------------------------------------------------\nPotential match: spec {} parent {}",
593 spec_name, i
594 )?;
595 let diags = parent.evaluate_bind_rules(&properties);
596 report_diagnostics(&diags, writer, 2)?;
597 }
598 }
599 }
600 }
601 }
602 }
603 Ok(())
604}
605
606async fn diagnose_node(
607 node_moniker: &str,
608 driver_dev_proxy: &fdd::ManagerProxy,
609 writer: &mut dyn io::Write,
610) -> Result<()> {
611 writeln!(writer, "Diagnosing node {}", node_moniker)?;
612 let nodes = fdev::get_device_info(driver_dev_proxy, &[node_moniker.to_string()], true).await?;
613 if nodes.is_empty() {
614 writeln!(writer, " ERROR: Node not found.")?;
615 return Ok(());
616 }
617 diagnose_node_info(&nodes[0], driver_dev_proxy, writer).await
618}
619
620fn is_spec_fuzzy_match(rules: &[fdf::BindRule], properties: &DeviceProperties) -> bool {
621 rules.iter().any(|rule| {
622 let key = match &rule.key {
623 fdf::NodePropertyKey::IntValue(v) => PropertyKey::NumberKey(*v as u64),
624 fdf::NodePropertyKey::StringValue(v) => PropertyKey::StringKey(v.clone()),
625 };
626 properties.contains_key(&key)
627 })
628}
629
630fn is_spec_fuzzy_match2(rules: &[fdf::BindRule2], properties: &DeviceProperties) -> bool {
631 rules.iter().any(|rule| {
632 let key = PropertyKey::StringKey(rule.key.clone());
633 properties.contains_key(&key)
634 })
635}
636
637async fn diagnose_spec_info(
638 spec_info: &fdf::CompositeInfo,
639 driver_dev_proxy: &fdd::ManagerProxy,
640 writer: &mut dyn io::Write,
641) -> Result<()> {
642 if let Some(driver) = spec_info
643 .matched_driver
644 .as_ref()
645 .and_then(|m| m.composite_driver.as_ref())
646 .and_then(|cd| cd.driver_info.as_ref())
647 .and_then(|di| di.url.as_deref())
648 {
649 writeln!(writer, " Spec matched to driver: {}", driver)?;
650 } else {
651 writeln!(writer, " Spec did NOT match any driver.")?;
652 writeln!(writer, "\nFuzzy matching against all composite drivers...")?;
653 if let Some(spec) = &spec_info.spec {
654 let drivers = fdev::get_driver_info(driver_dev_proxy, &[]).await?;
655 for driver in drivers {
656 if let Some(bytecode) = &driver.bind_rules_bytecode {
657 if let Ok(DecodedRules::Composite(rules)) = DecodedRules::new(bytecode.clone())
658 {
659 let mut potential_match = false;
660 if let Some(parents) = &spec.parents {
661 if parents.iter().any(|p| {
662 is_fuzzy_match(
663 &DecodedRules::Composite(rules.clone()),
664 &p.to_properties(),
665 )
666 }) {
667 potential_match = true;
668 }
669 } else if let Some(parents2) = &spec.parents2 {
670 if parents2.iter().any(|p| {
671 is_fuzzy_match(
672 &DecodedRules::Composite(rules.clone()),
673 &p.to_properties(),
674 )
675 }) {
676 potential_match = true;
677 }
678 }
679
680 if potential_match {
681 writeln!(
682 writer,
683 "\n------------------------------------------------------------\nPotential match: driver {}",
684 driver.url.as_deref().unwrap_or("unknown")
685 )?;
686 if let Some(parents) = &spec.parents {
687 diagnose_composite_match(&rules, parents, writer)?;
688 } else if let Some(parents2) = &spec.parents2 {
689 diagnose_composite_match(&rules, parents2, writer)?;
690 }
691 }
692 }
693 }
694 }
695 }
696 }
697 Ok(())
698}
699
700async fn diagnose_spec(
701 spec_name: &str,
702 driver_dev_proxy: &fdd::ManagerProxy,
703 writer: &mut dyn io::Write,
704) -> Result<()> {
705 writeln!(writer, "Diagnosing composite node spec {}", spec_name)?;
706 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
707 let spec = specs.iter().find(|s| {
708 s.spec
709 .as_ref()
710 .and_then(|spec| spec.name.as_ref())
711 .map(|name| name == spec_name)
712 .unwrap_or(false)
713 });
714
715 match spec {
716 None => writeln!(writer, " ERROR: Composite node spec not found.")?,
717 Some(s) => {
718 diagnose_spec_info(s, driver_dev_proxy, writer).await?;
719 }
720 }
721
722 Ok(())
723}
724
725async fn diagnose_all(
726 driver_dev_proxy: &fdd::ManagerProxy,
727 writer: &mut dyn io::Write,
728) -> Result<()> {
729 writeln!(writer, "Diagnosing all unbound nodes")?;
730 let nodes = fdev::get_device_info(driver_dev_proxy, &[], false).await?;
731 let unbound_nodes = nodes.into_iter().filter(|n| is_node_unbound(n)).collect_vec();
732
733 if unbound_nodes.is_empty() {
734 writeln!(writer, "No unbound nodes found.")?;
735 } else {
736 for node in unbound_nodes {
737 writeln!(writer, "Diagnosing node {}", node.moniker.as_deref().unwrap_or("unknown"))?;
738 diagnose_node_info(&node, driver_dev_proxy, writer).await?;
739 }
740 }
741
742 writeln!(writer, "\n\nDiagnosing all unmatched composite node specs")?;
743 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
744 let unmatched_specs = specs.into_iter().filter(|s| s.matched_driver.is_none()).collect_vec();
745
746 if unmatched_specs.is_empty() {
747 writeln!(writer, "No unmatched composite node specs found.")?;
748 } else {
749 for spec in unmatched_specs {
750 let name = spec.spec.as_ref().and_then(|s| s.name.as_deref()).unwrap_or("unknown");
751 writeln!(writer, "Diagnosing composite node spec {}", name)?;
752 diagnose_spec_info(&spec, driver_dev_proxy, writer).await?;
753 }
754 }
755
756 Ok(())
757}
758
759fn node_to_bind_properties(node_props: Option<&[fdf::NodeProperty]>) -> DeviceProperties {
760 let mut props = HashMap::new();
761 if let Some(node_props) = node_props {
762 for prop in node_props {
763 let key = match &prop.key {
764 fdf::NodePropertyKey::IntValue(v) => PropertyKey::NumberKey(*v as u64),
765 fdf::NodePropertyKey::StringValue(v) => PropertyKey::StringKey(v.clone()),
766 };
767 let value = match &prop.value {
768 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
769 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
770 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
771 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
772 _ => continue,
773 };
774 props.insert(key, value);
775 }
776 }
777 props
778}
779
780fn node_to_bind_properties2(node_props: Option<&[fdf::NodeProperty2]>) -> DeviceProperties {
781 let mut props = HashMap::new();
782 if let Some(node_props) = node_props {
783 for prop in node_props {
784 let key = PropertyKey::StringKey(prop.key.clone());
785 let value = match &prop.value {
786 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
787 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
788 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
789 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
790 _ => continue,
791 };
792 props.insert(key, value);
793 }
794 }
795 props
796}
797
798#[derive(Debug)]
799enum Diagnostic {
800 Mismatch { key: PropertyKey, expected: Symbol, actual: Option<Symbol>, is_equal: bool },
801 AbortReached,
802}
803
804fn evaluate_rules_bytecode(
805 symbol_table: &HashMap<u32, String>,
806 instructions: &[u8],
807 properties: &DeviceProperties,
808) -> Vec<Diagnostic> {
809 let mut diagnostics = Vec::new();
810 let mut iter = instructions.iter();
811
812 while let Some(byte) = iter.next() {
813 let op_byte_val = *byte;
814 if op_byte_val == RawOp::EqualCondition as u8
815 || op_byte_val == RawOp::InequalCondition as u8
816 {
817 let is_equal = op_byte_val == RawOp::EqualCondition as u8;
818 let (res, diag) =
819 read_and_evaluate_values(&mut iter, symbol_table, properties, is_equal);
820 if !res {
821 if let Some(d) = diag {
822 diagnostics.push(d);
823 }
824 break;
825 }
826 } else if op_byte_val == RawOp::Abort as u8 {
827 diagnostics.push(Diagnostic::AbortReached);
828 break;
829 } else if op_byte_val == RawOp::UnconditionalJump as u8 {
830 let offset = match next_u32(&mut iter) {
831 Ok(o) => o,
832 Err(_) => break,
833 };
834 for _ in 0..offset {
835 if iter.next().is_none() {
836 break;
837 }
838 }
839 if iter.next() != Some(&(RawOp::JumpLandPad as u8)) {
840 break;
841 }
842 } else if op_byte_val == RawOp::JumpIfEqual as u8
843 || op_byte_val == RawOp::JumpIfNotEqual as u8
844 {
845 let is_equal = op_byte_val == RawOp::JumpIfEqual as u8;
846 let offset = match next_u32(&mut iter) {
847 Ok(o) => o,
848 Err(_) => break,
849 };
850 let (res, _) = read_and_evaluate_values(&mut iter, symbol_table, properties, is_equal);
851 if res {
852 for _ in 0..offset {
853 if iter.next().is_none() {
854 break;
855 }
856 }
857 if iter.next() != Some(&(RawOp::JumpLandPad as u8)) {
858 break;
859 }
860 }
861 } else if op_byte_val == RawOp::JumpLandPad as u8 {
862 } else {
864 break;
866 }
867 }
868 diagnostics
869}
870
871fn read_and_evaluate_values(
872 iter: &mut BytecodeIter<'_>,
873 symbol_table: &HashMap<u32, String>,
874 properties: &DeviceProperties,
875 is_equal: bool,
876) -> (bool, Option<Diagnostic>) {
877 let lhs = match read_next_value(iter, symbol_table) {
878 Ok(v) => v,
879 Err(_) => return (false, None),
880 };
881 let rhs = match read_next_value(iter, symbol_table) {
882 Ok(v) => v,
883 Err(_) => return (false, None),
884 };
885
886 let key = match lhs {
887 Symbol::NumberValue(v) => PropertyKey::NumberKey(v),
888 Symbol::StringValue(v) => PropertyKey::StringKey(v),
889 Symbol::Key(v, _) => PropertyKey::StringKey(v),
890 _ => return (false, None),
891 };
892
893 let actual = properties.get(&key).cloned();
894 let mut effective_actual = actual;
895 if effective_actual.is_none() {
896 if let PropertyKey::NumberKey(int_key) = &key {
897 if let Some(str_key) = get_deprecated_key_identifier(*int_key as u32) {
898 effective_actual = properties.get(&PropertyKey::StringKey(str_key)).cloned();
899 }
900 }
901 }
902
903 let matches = match &effective_actual {
904 Some(val) => {
905 let mut val_for_compare = val.clone();
906 if let Symbol::EnumValue(v) = val {
907 val_for_compare = Symbol::StringValue(v.clone());
908 }
909 let mut rhs_for_compare = rhs.clone();
910 if let Symbol::EnumValue(v) = &rhs {
911 rhs_for_compare = Symbol::StringValue(v.clone());
912 }
913
914 val_for_compare == rhs_for_compare
915 }
916 None => &bind::compiler::Symbol::NumberValue(0) == &rhs,
917 };
918
919 if matches == is_equal {
920 (true, None)
921 } else {
922 (
923 false,
924 Some(Diagnostic::Mismatch { key, expected: rhs, actual: effective_actual, is_equal }),
925 )
926 }
927}
928
929fn read_next_value(
930 iter: &mut BytecodeIter<'_>,
931 symbol_table: &HashMap<u32, String>,
932) -> Result<Symbol, ()> {
933 let value_type_byte = next_u8(iter).map_err(|_| ())?;
934 let value_type_val = *value_type_byte;
935 let value = next_u32(iter).map_err(|_| ())?;
936
937 if value_type_val == RawValueType::NumberValue as u8 {
938 Ok(Symbol::NumberValue(value as u64))
939 } else if value_type_val == RawValueType::Key as u8 {
940 let name = symbol_table.get(&value).ok_or(())?.clone();
941 Ok(Symbol::Key(name, bind::parser::bind_library::ValueType::Str))
942 } else if value_type_val == RawValueType::StringValue as u8 {
943 let val = symbol_table.get(&value).ok_or(())?.clone();
944 Ok(Symbol::StringValue(val))
945 } else if value_type_val == RawValueType::BoolValue as u8 {
946 match value {
947 FALSE_VAL => Ok(Symbol::BoolValue(false)),
948 TRUE_VAL => Ok(Symbol::BoolValue(true)),
949 _ => Err(()),
950 }
951 } else if value_type_val == RawValueType::EnumValue as u8 {
952 let val = symbol_table.get(&value).ok_or(())?.clone();
953 Ok(Symbol::EnumValue(val))
954 } else {
955 Err(())
956 }
957}
958
959fn evaluate_bind_rules(rules: &[fdf::BindRule], properties: &DeviceProperties) -> Vec<Diagnostic> {
960 let mut diagnostics = Vec::new();
961 for rule in rules {
962 let key = match &rule.key {
963 fdf::NodePropertyKey::IntValue(v) => PropertyKey::NumberKey(*v as u64),
964 fdf::NodePropertyKey::StringValue(v) => PropertyKey::StringKey(v.clone()),
965 };
966 let actual = properties.get(&key).cloned();
967
968 let mut matched = false;
969 for val in &rule.values {
970 let symbol_val = match val {
971 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
972 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
973 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
974 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
975 _ => continue,
976 };
977 if actual.as_ref() == Some(&symbol_val) {
978 matched = true;
979 break;
980 }
981 }
982
983 match rule.condition {
984 fdf::Condition::Accept => {
985 if !matched {
986 diagnostics.push(Diagnostic::Mismatch {
987 key,
988 expected: Symbol::StringValue("one of requested values".to_string()),
989 actual,
990 is_equal: true,
991 });
992 }
993 }
994 fdf::Condition::Reject => {
995 if matched {
996 diagnostics.push(Diagnostic::Mismatch {
997 key,
998 expected: Symbol::StringValue("none of requested values".to_string()),
999 actual,
1000 is_equal: false,
1001 });
1002 }
1003 }
1004 _ => {}
1005 }
1006 }
1007 diagnostics
1008}
1009
1010fn evaluate_bind_rules2(
1011 rules: &[fdf::BindRule2],
1012 properties: &DeviceProperties,
1013) -> Vec<Diagnostic> {
1014 let mut diagnostics = Vec::new();
1015 for rule in rules {
1016 let key = PropertyKey::StringKey(rule.key.clone());
1017 let actual = properties.get(&key).cloned();
1018
1019 let mut matched = false;
1020 for val in &rule.values {
1021 let symbol_val = match val {
1022 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
1023 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
1024 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
1025 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
1026 _ => continue,
1027 };
1028 if actual.as_ref() == Some(&symbol_val) {
1029 matched = true;
1030 break;
1031 }
1032 }
1033
1034 match rule.condition {
1035 fdf::Condition::Accept => {
1036 if !matched {
1037 diagnostics.push(Diagnostic::Mismatch {
1038 key,
1039 expected: Symbol::StringValue("one of requested values".to_string()),
1040 actual,
1041 is_equal: true,
1042 });
1043 }
1044 }
1045 fdf::Condition::Reject => {
1046 if matched {
1047 diagnostics.push(Diagnostic::Mismatch {
1048 key,
1049 expected: Symbol::StringValue("none of requested values".to_string()),
1050 actual,
1051 is_equal: false,
1052 });
1053 }
1054 }
1055 _ => {}
1056 }
1057 }
1058 diagnostics
1059}
1060
1061fn report_diagnostics(
1062 diagnostics: &[Diagnostic],
1063 writer: &mut dyn io::Write,
1064 indent: usize,
1065) -> Result<()> {
1066 let indent_str = " ".repeat(indent);
1067 if diagnostics.is_empty() {
1068 writeln!(writer, "{}No issues found with direct property matching.", indent_str)?;
1069 } else {
1070 for diag in diagnostics {
1071 match diag {
1072 Diagnostic::Mismatch { key, expected, actual, is_equal } => {
1073 let key_str = match key {
1074 PropertyKey::NumberKey(v) => format!("{:#x}", v),
1075 PropertyKey::StringKey(v) => v.clone(),
1076 };
1077 let actual_str = match actual {
1078 Some(v) => v.to_string(),
1079 None => "missing".to_string(),
1080 };
1081 if *is_equal {
1082 writeln!(
1083 writer,
1084 "{}Mismatch: key {} expected {} but found {}",
1085 indent_str, key_str, expected, actual_str
1086 )?;
1087 } else {
1088 writeln!(
1089 writer,
1090 "{}Mismatch: key {} expected NOT {} but found {}",
1091 indent_str, key_str, expected, actual_str
1092 )?;
1093 }
1094 }
1095 Diagnostic::AbortReached => {
1096 writeln!(writer, "{}Unconditional Abort reached in bind rules.", indent_str)?;
1097 }
1098 }
1099 }
1100 }
1101 Ok(())
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106 use super::*;
1107 use bind::interpreter::instruction_decoder::{DecodedCondition, DecodedInstruction};
1108 use bind::parser::bind_library::ValueType;
1109
1110 #[test]
1111 fn test_evaluate_rules_mismatch() {
1112 let symbol_table = HashMap::new();
1113 let mut instructions = vec![RawOp::EqualCondition as u8];
1114 instructions.push(RawValueType::NumberValue as u8);
1115 instructions.extend_from_slice(&1u32.to_le_bytes());
1116 instructions.push(RawValueType::NumberValue as u8);
1117 instructions.extend_from_slice(&100u32.to_le_bytes());
1118
1119 let mut properties = HashMap::new();
1120 properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(200));
1121
1122 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1123 assert_eq!(diagnostics.len(), 1);
1124 match &diagnostics[0] {
1125 Diagnostic::Mismatch { key, expected, actual, is_equal } => {
1126 assert_eq!(key, &PropertyKey::NumberKey(1));
1127 assert_eq!(expected, &Symbol::NumberValue(100));
1128 assert_eq!(actual, &Some(Symbol::NumberValue(200)));
1129 assert!(is_equal);
1130 }
1131 _ => panic!("Expected Mismatch"),
1132 }
1133 }
1134
1135 #[test]
1136 fn test_evaluate_rules_match() {
1137 let symbol_table = HashMap::new();
1138 let mut instructions = vec![RawOp::EqualCondition as u8];
1139 instructions.push(RawValueType::NumberValue as u8);
1140 instructions.extend_from_slice(&1u32.to_le_bytes());
1141 instructions.push(RawValueType::NumberValue as u8);
1142 instructions.extend_from_slice(&100u32.to_le_bytes());
1143
1144 let mut properties = HashMap::new();
1145 properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(100));
1146
1147 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1148 assert_eq!(diagnostics.len(), 0);
1149 }
1150
1151 #[test]
1152 fn test_evaluate_rules_missing_property() {
1153 let mut symbol_table = HashMap::new();
1154 symbol_table.insert(1, "key1".to_string());
1155
1156 let mut instructions = vec![RawOp::EqualCondition as u8];
1158 instructions.push(RawValueType::StringValue as u8);
1159 instructions.extend_from_slice(&1u32.to_le_bytes());
1160 instructions.push(RawValueType::NumberValue as u8);
1161 instructions.extend_from_slice(&0u32.to_le_bytes());
1162
1163 let properties = HashMap::new();
1164
1165 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1166 assert_eq!(diagnostics.len(), 0);
1167
1168 let mut instructions = vec![RawOp::InequalCondition as u8];
1170 instructions.push(RawValueType::StringValue as u8);
1171 instructions.extend_from_slice(&1u32.to_le_bytes());
1172 instructions.push(RawValueType::NumberValue as u8);
1173 instructions.extend_from_slice(&0u32.to_le_bytes());
1174
1175 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1176 assert_eq!(diagnostics.len(), 1);
1177 match &diagnostics[0] {
1178 Diagnostic::Mismatch { key, expected, actual, is_equal } => {
1179 assert_eq!(key, &PropertyKey::StringKey("key1".to_string()));
1180 assert_eq!(expected, &Symbol::NumberValue(0));
1181 assert_eq!(actual, &None);
1182 assert!(!is_equal);
1183 }
1184 _ => panic!("Expected Mismatch"),
1185 }
1186 }
1187
1188 #[test]
1189 fn test_evaluate_rules_control_flow() {
1190 let mut symbol_table = HashMap::new();
1191 symbol_table.insert(1, "protocol".to_string());
1192 symbol_table.insert(2, "vendor_id".to_string());
1193
1194 let mut instructions = vec![RawOp::JumpIfNotEqual as u8];
1202 instructions.extend_from_slice(&11u32.to_le_bytes()); instructions.push(RawValueType::StringValue as u8);
1204 instructions.extend_from_slice(&1u32.to_le_bytes());
1205 instructions.push(RawValueType::NumberValue as u8);
1206 instructions.extend_from_slice(&10u32.to_le_bytes());
1207
1208 instructions.push(RawOp::EqualCondition as u8);
1209 instructions.push(RawValueType::StringValue as u8);
1210 instructions.extend_from_slice(&2u32.to_le_bytes());
1211 instructions.push(RawValueType::NumberValue as u8);
1212 instructions.extend_from_slice(&0x1234u32.to_le_bytes());
1213
1214 instructions.push(RawOp::JumpLandPad as u8);
1215
1216 let mut properties = HashMap::new();
1218 properties.insert(PropertyKey::StringKey("protocol".to_string()), Symbol::NumberValue(20));
1219 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1220 assert_eq!(diagnostics.len(), 0);
1221
1222 let mut properties = HashMap::new();
1224 properties.insert(PropertyKey::StringKey("protocol".to_string()), Symbol::NumberValue(10));
1225 properties
1226 .insert(PropertyKey::StringKey("vendor_id".to_string()), Symbol::NumberValue(0x5678));
1227 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1228 assert_eq!(diagnostics.len(), 1);
1229 match &diagnostics[0] {
1230 Diagnostic::Mismatch { key, .. } => {
1231 assert_eq!(key, &PropertyKey::StringKey("vendor_id".to_string()));
1232 }
1233 _ => panic!("Expected vendor_id mismatch"),
1234 }
1235 }
1236
1237 #[test]
1238 fn test_evaluate_rules_abort() {
1239 let symbol_table = HashMap::new();
1240 let instructions = vec![RawOp::Abort as u8];
1241 let properties = HashMap::new();
1242
1243 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1244 assert_eq!(diagnostics.len(), 1);
1245 match &diagnostics[0] {
1246 Diagnostic::AbortReached => {}
1247 _ => panic!("Expected AbortReached"),
1248 }
1249 }
1250
1251 #[test]
1252 fn test_evaluate_rules_deprecated_key() {
1253 let symbol_table = HashMap::new();
1254 let mut instructions = vec![RawOp::EqualCondition as u8];
1256 instructions.push(RawValueType::NumberValue as u8);
1257 instructions.extend_from_slice(&1u32.to_le_bytes());
1258 instructions.push(RawValueType::NumberValue as u8);
1259 instructions.extend_from_slice(&85u32.to_le_bytes());
1260
1261 let mut properties = HashMap::new();
1262 properties.insert(
1263 PropertyKey::StringKey("fuchsia.BIND_PROTOCOL".to_string()),
1264 Symbol::NumberValue(85),
1265 );
1266
1267 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1268 assert_eq!(diagnostics.len(), 0);
1269 }
1270
1271 #[test]
1272 fn test_evaluate_bind_rules_mismatch() {
1273 let rules = vec![fdf::BindRule {
1274 key: fdf::NodePropertyKey::IntValue(1),
1275 condition: fdf::Condition::Accept,
1276 values: vec![fdf::NodePropertyValue::IntValue(100)],
1277 }];
1278 let mut properties = HashMap::new();
1279 properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(200));
1280
1281 let diagnostics = evaluate_bind_rules(&rules, &properties);
1282 assert_eq!(diagnostics.len(), 1);
1283 }
1284
1285 #[test]
1286 fn test_evaluate_bind_rules2_mismatch() {
1287 let rules = vec![fdf::BindRule2 {
1288 key: "key1".to_string(),
1289 condition: fdf::Condition::Accept,
1290 values: vec![fdf::NodePropertyValue::IntValue(100)],
1291 }];
1292 let mut properties = HashMap::new();
1293 properties.insert(PropertyKey::StringKey("key1".to_string()), Symbol::NumberValue(200));
1294
1295 let diagnostics = evaluate_bind_rules2(&rules, &properties);
1296 assert_eq!(diagnostics.len(), 1);
1297 match &diagnostics[0] {
1298 Diagnostic::Mismatch { key, expected: _, actual, is_equal: _ } => {
1299 assert_eq!(key, &PropertyKey::StringKey("key1".to_string()));
1300 assert_eq!(actual, &Some(Symbol::NumberValue(200)));
1301 }
1302 _ => panic!("Expected Mismatch"),
1303 }
1304 }
1305
1306 #[test]
1307 fn test_is_fuzzy_match() {
1308 let mut symbol_table = HashMap::new();
1309 symbol_table.insert(1, "key1".to_string());
1310 let rules = DecodedRules::Normal(bind::interpreter::decode_bind_rules::DecodedBindRules {
1311 symbol_table,
1312 instructions: vec![],
1313 decoded_instructions: vec![DecodedInstruction::Condition(DecodedCondition {
1314 lhs: Symbol::Key("key1".to_string(), ValueType::Str),
1315 rhs: Symbol::NumberValue(100),
1316 is_equal: true,
1317 })],
1318 debug_info: None,
1319 });
1320 let mut properties = HashMap::new();
1321 properties.insert(PropertyKey::StringKey("key1".to_string()), Symbol::NumberValue(100));
1322 assert!(is_fuzzy_match(&rules, &properties));
1323 }
1324
1325 #[test]
1326 fn test_get_keys_from_instructions() {
1327 let instructions = vec![
1328 DecodedInstruction::Condition(DecodedCondition {
1329 lhs: Symbol::NumberValue(1),
1330 rhs: Symbol::NumberValue(100),
1331 is_equal: true,
1332 }),
1333 DecodedInstruction::Condition(DecodedCondition {
1334 lhs: Symbol::StringValue("key_str".to_string()),
1335 rhs: Symbol::NumberValue(200),
1336 is_equal: true,
1337 }),
1338 ];
1339 let keys = get_keys_from_instructions(&instructions);
1340 assert_eq!(keys.len(), 2);
1341 assert!(keys.contains(&PropertyKey::NumberKey(1)));
1342 assert!(keys.contains(&PropertyKey::StringKey("key_str".to_string())));
1343 }
1344}