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