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 Ok(())
400}
401
402async fn diagnose_driver(
403 driver: &fdf::DriverInfo,
404 driver_dev_proxy: &fdd::ManagerProxy,
405 writer: &mut dyn io::Write,
406) -> Result<()> {
407 let driver_url = driver.url.as_deref().unwrap_or("unknown");
408 writeln!(writer, "Diagnosing driver {}", driver_url)?;
409
410 let nodes = fdev::get_device_info(driver_dev_proxy, &[], false).await?;
411 let bound_nodes = nodes
412 .iter()
413 .filter_map(|n| {
414 if let Some(bound_url) = &n.bound_driver_url {
415 if bound_url == driver_url {
416 return Some(n.moniker.as_deref().unwrap_or("unknown"));
417 }
418 }
419 None
420 })
421 .collect_vec();
422
423 if !bound_nodes.is_empty() {
424 writeln!(writer, "\nDriver {} is bound to the following nodes:", driver_url)?;
425 for node in bound_nodes {
426 writeln!(writer, " {}", node)?;
427 }
428 }
429
430 if let Some(bytecode) = &driver.bind_rules_bytecode {
431 let rules = DecodedRules::new(bytecode.clone())?;
432
433 match &rules {
434 DecodedRules::Normal(r) => {
435 writeln!(writer, "\nFuzzy matching against all unbound nodes...")?;
436 let unbound_nodes = nodes.into_iter().filter(|n| is_node_unbound(n)).collect_vec();
437 for node in unbound_nodes {
438 let props = node_to_bind_properties(node.node_property_list.as_deref());
439 if is_fuzzy_match(&rules, &props) {
440 writeln!(
441 writer,
442 "\n------------------------------------------------------------\nPotential match found: node {}",
443 node.moniker.as_deref().unwrap_or("unknown")
444 )?;
445 if match_bind(
446 MatchBindData {
447 symbol_table: &r.symbol_table,
448 instructions: &r.instructions,
449 },
450 &props,
451 )
452 .unwrap_or(false)
453 {
454 writeln!(writer, " Matches!")?;
455 } else {
456 let diags =
457 evaluate_rules_bytecode(&r.symbol_table, &r.instructions, &props);
458 report_diagnostics(&diags, writer, 2)?;
459 }
460 }
461 }
462 }
463 DecodedRules::Composite(r) => {
464 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
465 let (matched_specs, unmatched_specs): (Vec<_>, Vec<_>) =
466 specs.into_iter().partition(|s| {
467 if let Some(matched) = &s.matched_driver {
468 if let Some(composite_driver) = &matched.composite_driver {
469 if let Some(info) = &composite_driver.driver_info {
470 if let Some(url) = &info.url {
471 return url == driver_url;
472 }
473 }
474 }
475 }
476 false
477 });
478
479 if !matched_specs.is_empty() {
480 writeln!(writer, "\nDriver is matched to the following composite node specs:")?;
481 for spec_info in matched_specs {
482 let spec_name = spec_info
483 .spec
484 .as_ref()
485 .and_then(|s| s.name.as_deref())
486 .unwrap_or("unknown");
487 writeln!(
488 writer,
489 " {}\n (Run `ffx driver doctor --composite-node-spec {}` to diagnose parents)",
490 spec_name, spec_name
491 )?;
492 }
493 }
494
495 writeln!(writer, "\nFuzzy matching against all unmatched composite node specs...")?;
496 let unmatched_specs = unmatched_specs
497 .into_iter()
498 .filter(|s| s.matched_driver.is_none())
499 .collect_vec();
500 for spec_info in unmatched_specs {
501 if let Some(spec) = &spec_info.spec {
502 let mut potential_match = false;
503 if let Some(parents) = &spec.parents {
504 if parents.iter().any(|p| is_fuzzy_match(&rules, &p.to_properties())) {
505 potential_match = true;
506 }
507 } else if let Some(parents2) = &spec.parents2 {
508 if parents2.iter().any(|p| is_fuzzy_match(&rules, &p.to_properties())) {
509 potential_match = true;
510 }
511 }
512
513 if potential_match {
514 writeln!(
515 writer,
516 "\n------------------------------------------------------------\nPotential match found: spec {}",
517 spec.name.as_deref().unwrap_or("unknown")
518 )?;
519 if let Some(parents) = &spec.parents {
520 diagnose_composite_match(r, parents, writer)?;
521 } else if let Some(parents2) = &spec.parents2 {
522 diagnose_composite_match(r, parents2, writer)?;
523 }
524 }
525 }
526 }
527 }
528 }
529 }
530 Ok(())
531}
532
533fn is_fuzzy_match(rules: &DecodedRules, properties: &DeviceProperties) -> bool {
534 let keys = match rules {
535 DecodedRules::Normal(r) => get_keys_from_instructions(&r.decoded_instructions),
536 DecodedRules::Composite(r) => {
537 let mut k = get_keys_from_instructions(&r.primary_parent.decoded_instructions);
538 for n in &r.additional_parents {
539 k.extend(get_keys_from_instructions(&n.decoded_instructions));
540 }
541 k
542 }
543 };
544 keys.iter().any(|k| properties.contains_key(k))
545}
546
547fn get_keys_from_instructions(
548 insts: &[bind::interpreter::instruction_decoder::DecodedInstruction],
549) -> Vec<PropertyKey> {
550 let mut keys = Vec::new();
551 for inst in insts {
552 if let bind::interpreter::instruction_decoder::DecodedInstruction::Condition(cond) = inst {
553 match &cond.lhs {
554 Symbol::NumberValue(v) => keys.push(PropertyKey::NumberKey(*v)),
555 Symbol::StringValue(v) => keys.push(PropertyKey::StringKey(v.clone())),
556 Symbol::Key(v, _) => keys.push(PropertyKey::StringKey(v.clone())),
557 _ => {}
558 }
559 }
560 }
561 keys
562}
563
564fn is_node_unbound(node: &fdd::NodeInfo) -> bool {
565 match &node.bound_driver_url {
566 None => true,
567 Some(url) => url == "unbound",
568 }
569}
570
571async fn diagnose_node_info(
572 node: &fdd::NodeInfo,
573 driver_dev_proxy: &fdd::ManagerProxy,
574 writer: &mut dyn io::Write,
575) -> Result<()> {
576 if !is_node_unbound(node) {
577 writeln!(
578 writer,
579 " Node is bound to {}",
580 node.bound_driver_url.as_deref().unwrap_or("unknown")
581 )?;
582 } else {
583 writeln!(writer, " Node is UNBOUND.")?;
584 if node.quarantined == Some(true) {
585 writeln!(writer, " Node is QUARANTINED (driver failed to start). Check driver logs.")?;
586 }
587
588 let properties = node_to_bind_properties(node.node_property_list.as_deref());
589
590 writeln!(writer, "\nFuzzy matching against all drivers...")?;
591 let drivers = fdev::get_driver_info(driver_dev_proxy, &[]).await?;
592 for driver in drivers {
593 if let Some(bytecode) = &driver.bind_rules_bytecode {
594 if let Ok(rules) = DecodedRules::new(bytecode.clone()) {
595 if is_fuzzy_match(&rules, &properties) {
596 writeln!(
597 writer,
598 "\n------------------------------------------------------------\nPotential match: driver {}",
599 driver.url.as_deref().unwrap_or("unknown")
600 )?;
601 match rules {
602 DecodedRules::Normal(r) => {
603 if match_bind(
604 MatchBindData {
605 symbol_table: &r.symbol_table,
606 instructions: &r.instructions,
607 },
608 &properties,
609 )
610 .unwrap_or(false)
611 {
612 writeln!(writer, " Matches!")?;
613 } else {
614 let diags = evaluate_rules_bytecode(
615 &r.symbol_table,
616 &r.instructions,
617 &properties,
618 );
619 report_diagnostics(&diags, writer, 2)?;
620 }
621 }
622 DecodedRules::Composite(_) => {
623 writeln!(
624 writer,
625 " 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.",
626 driver.url.as_deref().unwrap_or("unknown")
627 )?;
628 }
629 }
630 }
631 }
632 }
633 }
634
635 writeln!(writer, "\nFuzzy matching against all composite node specs...")?;
636 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
637 for spec_info in specs {
638 if let Some(spec) = &spec_info.spec {
639 if let Some(parents) = &spec.parents {
640 for (i, parent) in parents.iter().enumerate() {
641 if parent.is_fuzzy_match(&properties) {
642 let spec_name = spec.name.as_deref().unwrap_or("unknown");
643 writeln!(
644 writer,
645 "\n------------------------------------------------------------\nPotential match: spec {} parent {}",
646 spec_name, i
647 )?;
648 let diags = parent.evaluate_bind_rules(&properties);
649 report_diagnostics(&diags, writer, 2)?;
650 }
651 }
652 } else if let Some(parents2) = &spec.parents2 {
653 for (i, parent) in parents2.iter().enumerate() {
654 if parent.is_fuzzy_match(&properties) {
655 let spec_name = spec.name.as_deref().unwrap_or("unknown");
656 writeln!(
657 writer,
658 "\n------------------------------------------------------------\nPotential match: spec {} parent {}",
659 spec_name, i
660 )?;
661 let diags = parent.evaluate_bind_rules(&properties);
662 report_diagnostics(&diags, writer, 2)?;
663 }
664 }
665 }
666 }
667 }
668 }
669 Ok(())
670}
671
672async fn diagnose_node(
673 node_moniker: &str,
674 driver_dev_proxy: &fdd::ManagerProxy,
675 writer: &mut dyn io::Write,
676) -> Result<()> {
677 writeln!(writer, "Diagnosing node {}", node_moniker)?;
678 let nodes = fdev::get_device_info(driver_dev_proxy, &[node_moniker.to_string()], true).await?;
679 if nodes.is_empty() {
680 writeln!(writer, " ERROR: Node not found.")?;
681 return Ok(());
682 }
683 diagnose_node_info(&nodes[0], driver_dev_proxy, writer).await
684}
685
686fn is_spec_fuzzy_match(rules: &[fdf::BindRule], properties: &DeviceProperties) -> bool {
687 rules.iter().any(|rule| {
688 let key = match &rule.key {
689 fdf::NodePropertyKey::IntValue(v) => PropertyKey::NumberKey(*v as u64),
690 fdf::NodePropertyKey::StringValue(v) => PropertyKey::StringKey(v.clone()),
691 };
692 properties.contains_key(&key)
693 })
694}
695
696fn is_spec_fuzzy_match2(rules: &[fdf::BindRule2], properties: &DeviceProperties) -> bool {
697 rules.iter().any(|rule| {
698 let key = PropertyKey::StringKey(rule.key.clone());
699 properties.contains_key(&key)
700 })
701}
702
703async fn get_moniker_from_path(path: &str, proxy: &fdd::ManagerProxy) -> Result<String> {
704 let nodes = fdev::get_device_info(proxy, &[path.to_string()], true).await.unwrap_or_default();
705 if let Some(node) = nodes.first() {
706 if let Some(moniker) = &node.moniker {
707 return Ok(moniker.clone());
708 }
709 }
710 Ok(path.to_string())
711}
712
713async fn diagnose_spec_info(
714 spec_info: &fdf::CompositeInfo,
715 driver_dev_proxy: &fdd::ManagerProxy,
716 writer: &mut dyn io::Write,
717) -> Result<()> {
718 if let Some(driver) = spec_info
719 .matched_driver
720 .as_ref()
721 .and_then(|m| m.composite_driver.as_ref())
722 .and_then(|cd| cd.driver_info.as_ref())
723 .and_then(|di| di.url.as_deref())
724 {
725 writeln!(writer, " Spec matched to driver: {}", driver)?;
726 } else {
727 writeln!(writer, " Spec did NOT match any driver.")?;
728 writeln!(writer, "\nFuzzy matching against all composite drivers...")?;
729 if let Some(spec) = &spec_info.spec {
730 let drivers = fdev::get_driver_info(driver_dev_proxy, &[]).await?;
731 for driver in drivers {
732 if let Some(bytecode) = &driver.bind_rules_bytecode {
733 if let Ok(DecodedRules::Composite(rules)) = DecodedRules::new(bytecode.clone())
734 {
735 let mut potential_match = false;
736 if let Some(parents) = &spec.parents {
737 if parents.iter().any(|p| {
738 is_fuzzy_match(
739 &DecodedRules::Composite(rules.clone()),
740 &p.to_properties(),
741 )
742 }) {
743 potential_match = true;
744 }
745 } else if let Some(parents2) = &spec.parents2 {
746 if parents2.iter().any(|p| {
747 is_fuzzy_match(
748 &DecodedRules::Composite(rules.clone()),
749 &p.to_properties(),
750 )
751 }) {
752 potential_match = true;
753 }
754 }
755
756 if potential_match {
757 writeln!(
758 writer,
759 "\n------------------------------------------------------------\nPotential match: driver {}",
760 driver.url.as_deref().unwrap_or("unknown")
761 )?;
762 if let Some(parents) = &spec.parents {
763 diagnose_composite_match(&rules, parents, writer)?;
764 } else if let Some(parents2) = &spec.parents2 {
765 diagnose_composite_match(&rules, parents2, writer)?;
766 }
767 }
768 }
769 }
770 }
771 }
772 }
773
774 writeln!(writer, "\nFuzzy matching spec parents against all unbound nodes...")?;
775
776 let mut parent_paths: Vec<Option<String>> = Vec::new();
778 let mut parent_monikers: Vec<Option<String>> = Vec::new();
779 if let Some(spec) = &spec_info.spec {
780 let (iterator, iterator_server) =
781 driver_dev_proxy.domain().create_proxy::<fdd::CompositeInfoIteratorMarker>();
782 if driver_dev_proxy.get_composite_info(iterator_server).is_ok() {
783 loop {
784 if let Ok(composite_list) = iterator.get_next().await {
785 if composite_list.is_empty() {
786 break;
787 }
788 for composite_node in composite_list {
789 if let Some(fdd::CompositeInfo::Composite(node_info)) =
790 composite_node.composite
791 {
792 if node_info.spec.and_then(|s| s.name) == spec.name {
793 parent_paths =
794 composite_node.parent_topological_paths.unwrap_or_default();
795 parent_monikers =
796 composite_node.parent_monikers.unwrap_or_default();
797 break;
798 }
799 }
800 }
801 if !parent_paths.is_empty() || !parent_monikers.is_empty() {
802 break;
803 }
804 } else {
805 break;
806 }
807 }
808 }
809 }
810
811 let nodes = fdev::get_device_info(driver_dev_proxy, &[], false).await?;
812 let unbound_nodes = nodes.into_iter().filter(|n| is_node_unbound(n)).collect_vec();
813
814 let parent_names = spec_info.matched_driver.as_ref().and_then(|m| m.parent_names.as_ref());
815
816 if let Some(spec) = &spec_info.spec {
817 if let Some(parents) = &spec.parents {
818 for (i, parent) in parents.iter().enumerate() {
819 let name =
820 parent_names.and_then(|n| n.get(i)).map(|s| s.as_str()).unwrap_or("unknown");
821 writeln!(writer, " Parent {} ({}):", i, name)?;
822
823 let mut bound_node = None;
824 if let Some(Some(moniker)) = parent_monikers.get(i) {
825 bound_node = Some(moniker.clone());
826 } else if let Some(Some(path)) = parent_paths.get(i) {
827 bound_node = Some(
828 get_moniker_from_path(path, driver_dev_proxy)
829 .await
830 .unwrap_or_else(|_| path.clone()),
831 );
832 }
833
834 if let Some(bound_name) = bound_node {
835 writeln!(writer, " Already bound to node: {}", bound_name)?;
836 } else {
837 let mut found_match = false;
838 for node in &unbound_nodes {
839 let properties =
840 node_to_bind_properties(node.node_property_list.as_deref());
841 if parent.is_fuzzy_match(&properties) {
842 found_match = true;
843 writeln!(
844 writer,
845 " Potential match: node {}",
846 node.moniker.as_deref().unwrap_or("unknown")
847 )?;
848 let diags = parent.evaluate_bind_rules(&properties);
849 report_diagnostics(&diags, writer, 4)?;
850 }
851 }
852 if !found_match {
853 writeln!(writer, " No unbound nodes matched this parent.")?;
854 }
855 }
856 }
857 } else if let Some(parents2) = &spec.parents2 {
858 for (i, parent) in parents2.iter().enumerate() {
859 let name =
860 parent_names.and_then(|n| n.get(i)).map(|s| s.as_str()).unwrap_or("unknown");
861 writeln!(writer, " Parent {} ({}):", i, name)?;
862
863 let mut bound_node = None;
864 if let Some(Some(moniker)) = parent_monikers.get(i) {
865 bound_node = Some(moniker.clone());
866 } else if let Some(Some(path)) = parent_paths.get(i) {
867 bound_node = Some(
868 get_moniker_from_path(path, driver_dev_proxy)
869 .await
870 .unwrap_or_else(|_| path.clone()),
871 );
872 }
873
874 if let Some(bound_name) = bound_node {
875 writeln!(writer, " Already bound to node: {}", bound_name)?;
876 } else {
877 let mut found_match = false;
878 for node in &unbound_nodes {
879 let properties =
880 node_to_bind_properties(node.node_property_list.as_deref());
881 if parent.is_fuzzy_match(&properties) {
882 found_match = true;
883 writeln!(
884 writer,
885 " Potential match: node {}",
886 node.moniker.as_deref().unwrap_or("unknown")
887 )?;
888 let diags = parent.evaluate_bind_rules(&properties);
889 report_diagnostics(&diags, writer, 4)?;
890 }
891 }
892 if !found_match {
893 writeln!(writer, " No unbound nodes matched this parent.")?;
894 }
895 }
896 }
897 }
898 }
899
900 Ok(())
901}
902
903async fn diagnose_spec(
904 spec_name: &str,
905 driver_dev_proxy: &fdd::ManagerProxy,
906 writer: &mut dyn io::Write,
907) -> Result<()> {
908 writeln!(writer, "Diagnosing composite node spec {}", spec_name)?;
909 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
910 let spec = specs.iter().find(|s| {
911 s.spec
912 .as_ref()
913 .and_then(|spec| spec.name.as_ref())
914 .map(|name| name == spec_name)
915 .unwrap_or(false)
916 });
917
918 match spec {
919 None => writeln!(writer, " ERROR: Composite node spec not found.")?,
920 Some(s) => {
921 diagnose_spec_info(s, driver_dev_proxy, writer).await?;
922 }
923 }
924
925 Ok(())
926}
927
928async fn diagnose_all(
929 driver_dev_proxy: &fdd::ManagerProxy,
930 writer: &mut dyn io::Write,
931) -> Result<()> {
932 writeln!(writer, "Diagnosing all unbound nodes")?;
933 let nodes = fdev::get_device_info(driver_dev_proxy, &[], false).await?;
934 let unbound_nodes = nodes.into_iter().filter(|n| is_node_unbound(n)).collect_vec();
935
936 if unbound_nodes.is_empty() {
937 writeln!(writer, "No unbound nodes found.")?;
938 } else {
939 for node in unbound_nodes {
940 writeln!(writer, "Diagnosing node {}", node.moniker.as_deref().unwrap_or("unknown"))?;
941 diagnose_node_info(&node, driver_dev_proxy, writer).await?;
942 }
943 }
944
945 writeln!(writer, "\n\nDiagnosing all unmatched composite node specs")?;
946 let specs = fdev::get_composite_node_specs(driver_dev_proxy, None).await?;
947 let unmatched_specs = specs.into_iter().filter(|s| s.matched_driver.is_none()).collect_vec();
948
949 if unmatched_specs.is_empty() {
950 writeln!(writer, "No unmatched composite node specs found.")?;
951 } else {
952 for spec in unmatched_specs {
953 let name = spec.spec.as_ref().and_then(|s| s.name.as_deref()).unwrap_or("unknown");
954 writeln!(writer, "Diagnosing composite node spec {}", name)?;
955 diagnose_spec_info(&spec, driver_dev_proxy, writer).await?;
956 }
957 }
958
959 Ok(())
960}
961
962fn node_to_bind_properties(node_props: Option<&[fdf::NodeProperty]>) -> DeviceProperties {
963 let mut props = HashMap::new();
964 if let Some(node_props) = node_props {
965 for prop in node_props {
966 let key = match &prop.key {
967 fdf::NodePropertyKey::IntValue(v) => PropertyKey::NumberKey(*v as u64),
968 fdf::NodePropertyKey::StringValue(v) => PropertyKey::StringKey(v.clone()),
969 };
970 let value = match &prop.value {
971 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
972 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
973 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
974 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
975 _ => continue,
976 };
977 props.insert(key, value);
978 }
979 }
980 props
981}
982
983fn node_to_bind_properties2(node_props: Option<&[fdf::NodeProperty2]>) -> DeviceProperties {
984 let mut props = HashMap::new();
985 if let Some(node_props) = node_props {
986 for prop in node_props {
987 let key = PropertyKey::StringKey(prop.key.clone());
988 let value = match &prop.value {
989 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
990 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
991 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
992 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
993 _ => continue,
994 };
995 props.insert(key, value);
996 }
997 }
998 props
999}
1000
1001#[derive(Debug)]
1002enum Diagnostic {
1003 Mismatch { key: PropertyKey, expected: Symbol, actual: Option<Symbol>, is_equal: bool },
1004 AbortReached,
1005}
1006
1007fn evaluate_rules_bytecode(
1008 symbol_table: &HashMap<u32, String>,
1009 instructions: &[u8],
1010 properties: &DeviceProperties,
1011) -> Vec<Diagnostic> {
1012 let mut diagnostics = Vec::new();
1013 let mut iter = instructions.iter();
1014
1015 while let Some(byte) = iter.next() {
1016 let op_byte_val = *byte;
1017 if op_byte_val == RawOp::EqualCondition as u8
1018 || op_byte_val == RawOp::InequalCondition as u8
1019 {
1020 let is_equal = op_byte_val == RawOp::EqualCondition as u8;
1021 let (res, diag) =
1022 read_and_evaluate_values(&mut iter, symbol_table, properties, is_equal);
1023 if !res {
1024 if let Some(d) = diag {
1025 diagnostics.push(d);
1026 }
1027 break;
1028 }
1029 } else if op_byte_val == RawOp::Abort as u8 {
1030 diagnostics.push(Diagnostic::AbortReached);
1031 break;
1032 } else if op_byte_val == RawOp::UnconditionalJump as u8 {
1033 let offset = match next_u32(&mut iter) {
1034 Ok(o) => o,
1035 Err(_) => break,
1036 };
1037 for _ in 0..offset {
1038 if iter.next().is_none() {
1039 break;
1040 }
1041 }
1042 if iter.next() != Some(&(RawOp::JumpLandPad as u8)) {
1043 break;
1044 }
1045 } else if op_byte_val == RawOp::JumpIfEqual as u8
1046 || op_byte_val == RawOp::JumpIfNotEqual as u8
1047 {
1048 let is_equal = op_byte_val == RawOp::JumpIfEqual as u8;
1049 let offset = match next_u32(&mut iter) {
1050 Ok(o) => o,
1051 Err(_) => break,
1052 };
1053 let (res, _) = read_and_evaluate_values(&mut iter, symbol_table, properties, is_equal);
1054 if res {
1055 for _ in 0..offset {
1056 if iter.next().is_none() {
1057 break;
1058 }
1059 }
1060 if iter.next() != Some(&(RawOp::JumpLandPad as u8)) {
1061 break;
1062 }
1063 }
1064 } else if op_byte_val == RawOp::JumpLandPad as u8 {
1065 } else {
1067 break;
1069 }
1070 }
1071 diagnostics
1072}
1073
1074fn read_and_evaluate_values(
1075 iter: &mut BytecodeIter<'_>,
1076 symbol_table: &HashMap<u32, String>,
1077 properties: &DeviceProperties,
1078 is_equal: bool,
1079) -> (bool, Option<Diagnostic>) {
1080 let lhs = match read_next_value(iter, symbol_table) {
1081 Ok(v) => v,
1082 Err(_) => return (false, None),
1083 };
1084 let rhs = match read_next_value(iter, symbol_table) {
1085 Ok(v) => v,
1086 Err(_) => return (false, None),
1087 };
1088
1089 let key = match lhs {
1090 Symbol::NumberValue(v) => PropertyKey::NumberKey(v),
1091 Symbol::StringValue(v) => PropertyKey::StringKey(v),
1092 Symbol::Key(v, _) => PropertyKey::StringKey(v),
1093 _ => return (false, None),
1094 };
1095
1096 let actual = properties.get(&key).cloned();
1097 let mut effective_actual = actual;
1098 if effective_actual.is_none() {
1099 if let PropertyKey::NumberKey(int_key) = &key {
1100 if let Some(str_key) = get_deprecated_key_identifier(*int_key as u32) {
1101 effective_actual = properties.get(&PropertyKey::StringKey(str_key)).cloned();
1102 }
1103 }
1104 }
1105
1106 let matches = match &effective_actual {
1107 Some(val) => {
1108 let mut val_for_compare = val.clone();
1109 if let Symbol::EnumValue(v) = val {
1110 val_for_compare = Symbol::StringValue(v.clone());
1111 }
1112 let mut rhs_for_compare = rhs.clone();
1113 if let Symbol::EnumValue(v) = &rhs {
1114 rhs_for_compare = Symbol::StringValue(v.clone());
1115 }
1116
1117 val_for_compare == rhs_for_compare
1118 }
1119 None => &bind::compiler::Symbol::NumberValue(0) == &rhs,
1120 };
1121
1122 if matches == is_equal {
1123 (true, None)
1124 } else {
1125 (
1126 false,
1127 Some(Diagnostic::Mismatch { key, expected: rhs, actual: effective_actual, is_equal }),
1128 )
1129 }
1130}
1131
1132fn read_next_value(
1133 iter: &mut BytecodeIter<'_>,
1134 symbol_table: &HashMap<u32, String>,
1135) -> Result<Symbol, ()> {
1136 let value_type_byte = next_u8(iter).map_err(|_| ())?;
1137 let value_type_val = *value_type_byte;
1138 let value = next_u32(iter).map_err(|_| ())?;
1139
1140 if value_type_val == RawValueType::NumberValue as u8 {
1141 Ok(Symbol::NumberValue(value as u64))
1142 } else if value_type_val == RawValueType::Key as u8 {
1143 let name = symbol_table.get(&value).ok_or(())?.clone();
1144 Ok(Symbol::Key(name, bind::parser::bind_library::ValueType::Str))
1145 } else if value_type_val == RawValueType::StringValue as u8 {
1146 let val = symbol_table.get(&value).ok_or(())?.clone();
1147 Ok(Symbol::StringValue(val))
1148 } else if value_type_val == RawValueType::BoolValue as u8 {
1149 match value {
1150 FALSE_VAL => Ok(Symbol::BoolValue(false)),
1151 TRUE_VAL => Ok(Symbol::BoolValue(true)),
1152 _ => Err(()),
1153 }
1154 } else if value_type_val == RawValueType::EnumValue as u8 {
1155 let val = symbol_table.get(&value).ok_or(())?.clone();
1156 Ok(Symbol::EnumValue(val))
1157 } else {
1158 Err(())
1159 }
1160}
1161
1162fn evaluate_bind_rules(rules: &[fdf::BindRule], properties: &DeviceProperties) -> Vec<Diagnostic> {
1163 let mut diagnostics = Vec::new();
1164 for rule in rules {
1165 let key = match &rule.key {
1166 fdf::NodePropertyKey::IntValue(v) => PropertyKey::NumberKey(*v as u64),
1167 fdf::NodePropertyKey::StringValue(v) => PropertyKey::StringKey(v.clone()),
1168 };
1169 let actual = properties.get(&key).cloned();
1170
1171 let mut matched = false;
1172 for val in &rule.values {
1173 let symbol_val = match val {
1174 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
1175 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
1176 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
1177 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
1178 _ => continue,
1179 };
1180 if actual.as_ref() == Some(&symbol_val) {
1181 matched = true;
1182 break;
1183 }
1184 }
1185
1186 match rule.condition {
1187 fdf::Condition::Accept => {
1188 if !matched {
1189 diagnostics.push(Diagnostic::Mismatch {
1190 key,
1191 expected: Symbol::StringValue("one of requested values".to_string()),
1192 actual,
1193 is_equal: true,
1194 });
1195 }
1196 }
1197 fdf::Condition::Reject => {
1198 if matched {
1199 diagnostics.push(Diagnostic::Mismatch {
1200 key,
1201 expected: Symbol::StringValue("none of requested values".to_string()),
1202 actual,
1203 is_equal: false,
1204 });
1205 }
1206 }
1207 _ => {}
1208 }
1209 }
1210 diagnostics
1211}
1212
1213fn evaluate_bind_rules2(
1214 rules: &[fdf::BindRule2],
1215 properties: &DeviceProperties,
1216) -> Vec<Diagnostic> {
1217 let mut diagnostics = Vec::new();
1218 for rule in rules {
1219 let key = PropertyKey::StringKey(rule.key.clone());
1220 let actual = properties.get(&key).cloned();
1221
1222 let mut matched = false;
1223 for val in &rule.values {
1224 let symbol_val = match val {
1225 fdf::NodePropertyValue::IntValue(v) => Symbol::NumberValue(*v as u64),
1226 fdf::NodePropertyValue::StringValue(v) => Symbol::StringValue(v.clone()),
1227 fdf::NodePropertyValue::BoolValue(v) => Symbol::BoolValue(*v),
1228 fdf::NodePropertyValue::EnumValue(v) => Symbol::EnumValue(v.clone()),
1229 _ => continue,
1230 };
1231 if actual.as_ref() == Some(&symbol_val) {
1232 matched = true;
1233 break;
1234 }
1235 }
1236
1237 match rule.condition {
1238 fdf::Condition::Accept => {
1239 if !matched {
1240 diagnostics.push(Diagnostic::Mismatch {
1241 key,
1242 expected: Symbol::StringValue("one of requested values".to_string()),
1243 actual,
1244 is_equal: true,
1245 });
1246 }
1247 }
1248 fdf::Condition::Reject => {
1249 if matched {
1250 diagnostics.push(Diagnostic::Mismatch {
1251 key,
1252 expected: Symbol::StringValue("none of requested values".to_string()),
1253 actual,
1254 is_equal: false,
1255 });
1256 }
1257 }
1258 _ => {}
1259 }
1260 }
1261 diagnostics
1262}
1263
1264fn report_diagnostics(
1265 diagnostics: &[Diagnostic],
1266 writer: &mut dyn io::Write,
1267 indent: usize,
1268) -> Result<()> {
1269 let indent_str = " ".repeat(indent);
1270 if diagnostics.is_empty() {
1271 writeln!(writer, "{}No issues found with direct property matching.", indent_str)?;
1272 } else {
1273 for diag in diagnostics {
1274 match diag {
1275 Diagnostic::Mismatch { key, expected, actual, is_equal } => {
1276 let key_str = match key {
1277 PropertyKey::NumberKey(v) => format!("{:#x}", v),
1278 PropertyKey::StringKey(v) => v.clone(),
1279 };
1280 let actual_str = match actual {
1281 Some(v) => v.to_string(),
1282 None => "missing".to_string(),
1283 };
1284 if *is_equal {
1285 writeln!(
1286 writer,
1287 "{}Mismatch: key {} expected {} but found {}",
1288 indent_str, key_str, expected, actual_str
1289 )?;
1290 } else {
1291 writeln!(
1292 writer,
1293 "{}Mismatch: key {} expected NOT {} but found {}",
1294 indent_str, key_str, expected, actual_str
1295 )?;
1296 }
1297 }
1298 Diagnostic::AbortReached => {
1299 writeln!(writer, "{}Unconditional Abort reached in bind rules.", indent_str)?;
1300 }
1301 }
1302 }
1303 }
1304 Ok(())
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309 use super::*;
1310 use bind::interpreter::instruction_decoder::{DecodedCondition, DecodedInstruction};
1311 use bind::parser::bind_library::ValueType;
1312
1313 #[test]
1314 fn test_evaluate_rules_mismatch() {
1315 let symbol_table = HashMap::new();
1316 let mut instructions = vec![RawOp::EqualCondition as u8];
1317 instructions.push(RawValueType::NumberValue as u8);
1318 instructions.extend_from_slice(&1u32.to_le_bytes());
1319 instructions.push(RawValueType::NumberValue as u8);
1320 instructions.extend_from_slice(&100u32.to_le_bytes());
1321
1322 let mut properties = HashMap::new();
1323 properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(200));
1324
1325 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1326 assert_eq!(diagnostics.len(), 1);
1327 match &diagnostics[0] {
1328 Diagnostic::Mismatch { key, expected, actual, is_equal } => {
1329 assert_eq!(key, &PropertyKey::NumberKey(1));
1330 assert_eq!(expected, &Symbol::NumberValue(100));
1331 assert_eq!(actual, &Some(Symbol::NumberValue(200)));
1332 assert!(is_equal);
1333 }
1334 _ => panic!("Expected Mismatch"),
1335 }
1336 }
1337
1338 #[test]
1339 fn test_evaluate_rules_match() {
1340 let symbol_table = HashMap::new();
1341 let mut instructions = vec![RawOp::EqualCondition as u8];
1342 instructions.push(RawValueType::NumberValue as u8);
1343 instructions.extend_from_slice(&1u32.to_le_bytes());
1344 instructions.push(RawValueType::NumberValue as u8);
1345 instructions.extend_from_slice(&100u32.to_le_bytes());
1346
1347 let mut properties = HashMap::new();
1348 properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(100));
1349
1350 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1351 assert_eq!(diagnostics.len(), 0);
1352 }
1353
1354 #[test]
1355 fn test_evaluate_rules_missing_property() {
1356 let mut symbol_table = HashMap::new();
1357 symbol_table.insert(1, "key1".to_string());
1358
1359 let mut instructions = vec![RawOp::EqualCondition as u8];
1361 instructions.push(RawValueType::StringValue as u8);
1362 instructions.extend_from_slice(&1u32.to_le_bytes());
1363 instructions.push(RawValueType::NumberValue as u8);
1364 instructions.extend_from_slice(&0u32.to_le_bytes());
1365
1366 let properties = HashMap::new();
1367
1368 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1369 assert_eq!(diagnostics.len(), 0);
1370
1371 let mut instructions = vec![RawOp::InequalCondition as u8];
1373 instructions.push(RawValueType::StringValue as u8);
1374 instructions.extend_from_slice(&1u32.to_le_bytes());
1375 instructions.push(RawValueType::NumberValue as u8);
1376 instructions.extend_from_slice(&0u32.to_le_bytes());
1377
1378 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1379 assert_eq!(diagnostics.len(), 1);
1380 match &diagnostics[0] {
1381 Diagnostic::Mismatch { key, expected, actual, is_equal } => {
1382 assert_eq!(key, &PropertyKey::StringKey("key1".to_string()));
1383 assert_eq!(expected, &Symbol::NumberValue(0));
1384 assert_eq!(actual, &None);
1385 assert!(!is_equal);
1386 }
1387 _ => panic!("Expected Mismatch"),
1388 }
1389 }
1390
1391 #[test]
1392 fn test_evaluate_rules_control_flow() {
1393 let mut symbol_table = HashMap::new();
1394 symbol_table.insert(1, "protocol".to_string());
1395 symbol_table.insert(2, "vendor_id".to_string());
1396
1397 let mut instructions = vec![RawOp::JumpIfNotEqual as u8];
1405 instructions.extend_from_slice(&11u32.to_le_bytes()); instructions.push(RawValueType::StringValue as u8);
1407 instructions.extend_from_slice(&1u32.to_le_bytes());
1408 instructions.push(RawValueType::NumberValue as u8);
1409 instructions.extend_from_slice(&10u32.to_le_bytes());
1410
1411 instructions.push(RawOp::EqualCondition as u8);
1412 instructions.push(RawValueType::StringValue as u8);
1413 instructions.extend_from_slice(&2u32.to_le_bytes());
1414 instructions.push(RawValueType::NumberValue as u8);
1415 instructions.extend_from_slice(&0x1234u32.to_le_bytes());
1416
1417 instructions.push(RawOp::JumpLandPad as u8);
1418
1419 let mut properties = HashMap::new();
1421 properties.insert(PropertyKey::StringKey("protocol".to_string()), Symbol::NumberValue(20));
1422 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1423 assert_eq!(diagnostics.len(), 0);
1424
1425 let mut properties = HashMap::new();
1427 properties.insert(PropertyKey::StringKey("protocol".to_string()), Symbol::NumberValue(10));
1428 properties
1429 .insert(PropertyKey::StringKey("vendor_id".to_string()), Symbol::NumberValue(0x5678));
1430 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1431 assert_eq!(diagnostics.len(), 1);
1432 match &diagnostics[0] {
1433 Diagnostic::Mismatch { key, .. } => {
1434 assert_eq!(key, &PropertyKey::StringKey("vendor_id".to_string()));
1435 }
1436 _ => panic!("Expected vendor_id mismatch"),
1437 }
1438 }
1439
1440 #[test]
1441 fn test_evaluate_rules_abort() {
1442 let symbol_table = HashMap::new();
1443 let instructions = vec![RawOp::Abort as u8];
1444 let properties = HashMap::new();
1445
1446 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1447 assert_eq!(diagnostics.len(), 1);
1448 match &diagnostics[0] {
1449 Diagnostic::AbortReached => {}
1450 _ => panic!("Expected AbortReached"),
1451 }
1452 }
1453
1454 #[test]
1455 fn test_evaluate_rules_deprecated_key() {
1456 let symbol_table = HashMap::new();
1457 let mut instructions = vec![RawOp::EqualCondition as u8];
1459 instructions.push(RawValueType::NumberValue as u8);
1460 instructions.extend_from_slice(&1u32.to_le_bytes());
1461 instructions.push(RawValueType::NumberValue as u8);
1462 instructions.extend_from_slice(&85u32.to_le_bytes());
1463
1464 let mut properties = HashMap::new();
1465 properties.insert(
1466 PropertyKey::StringKey("fuchsia.BIND_PROTOCOL".to_string()),
1467 Symbol::NumberValue(85),
1468 );
1469
1470 let diagnostics = evaluate_rules_bytecode(&symbol_table, &instructions, &properties);
1471 assert_eq!(diagnostics.len(), 0);
1472 }
1473
1474 #[test]
1475 fn test_evaluate_bind_rules_mismatch() {
1476 let rules = vec![fdf::BindRule {
1477 key: fdf::NodePropertyKey::IntValue(1),
1478 condition: fdf::Condition::Accept,
1479 values: vec![fdf::NodePropertyValue::IntValue(100)],
1480 }];
1481 let mut properties = HashMap::new();
1482 properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(200));
1483
1484 let diagnostics = evaluate_bind_rules(&rules, &properties);
1485 assert_eq!(diagnostics.len(), 1);
1486 }
1487
1488 #[test]
1489 fn test_evaluate_bind_rules2_mismatch() {
1490 let rules = vec![fdf::BindRule2 {
1491 key: "key1".to_string(),
1492 condition: fdf::Condition::Accept,
1493 values: vec![fdf::NodePropertyValue::IntValue(100)],
1494 }];
1495 let mut properties = HashMap::new();
1496 properties.insert(PropertyKey::StringKey("key1".to_string()), Symbol::NumberValue(200));
1497
1498 let diagnostics = evaluate_bind_rules2(&rules, &properties);
1499 assert_eq!(diagnostics.len(), 1);
1500 match &diagnostics[0] {
1501 Diagnostic::Mismatch { key, expected: _, actual, is_equal: _ } => {
1502 assert_eq!(key, &PropertyKey::StringKey("key1".to_string()));
1503 assert_eq!(actual, &Some(Symbol::NumberValue(200)));
1504 }
1505 _ => panic!("Expected Mismatch"),
1506 }
1507 }
1508
1509 #[test]
1510 fn test_is_fuzzy_match() {
1511 let mut symbol_table = HashMap::new();
1512 symbol_table.insert(1, "key1".to_string());
1513 let rules = DecodedRules::Normal(bind::interpreter::decode_bind_rules::DecodedBindRules {
1514 symbol_table,
1515 instructions: vec![],
1516 decoded_instructions: vec![DecodedInstruction::Condition(DecodedCondition {
1517 lhs: Symbol::Key("key1".to_string(), ValueType::Str),
1518 rhs: Symbol::NumberValue(100),
1519 is_equal: true,
1520 })],
1521 debug_info: None,
1522 });
1523 let mut properties = HashMap::new();
1524 properties.insert(PropertyKey::StringKey("key1".to_string()), Symbol::NumberValue(100));
1525 assert!(is_fuzzy_match(&rules, &properties));
1526 }
1527
1528 #[test]
1529 fn test_get_keys_from_instructions() {
1530 let instructions = vec![
1531 DecodedInstruction::Condition(DecodedCondition {
1532 lhs: Symbol::NumberValue(1),
1533 rhs: Symbol::NumberValue(100),
1534 is_equal: true,
1535 }),
1536 DecodedInstruction::Condition(DecodedCondition {
1537 lhs: Symbol::StringValue("key_str".to_string()),
1538 rhs: Symbol::NumberValue(200),
1539 is_equal: true,
1540 }),
1541 ];
1542 let keys = get_keys_from_instructions(&instructions);
1543 assert_eq!(keys.len(), 2);
1544 assert!(keys.contains(&PropertyKey::NumberKey(1)));
1545 assert!(keys.contains(&PropertyKey::StringKey("key_str".to_string())));
1546 }
1547}