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