1use fidl_fuchsia_hardware_interconnect as icc;
6use log::error;
7use std::collections::{BTreeMap, BTreeSet, VecDeque};
8use zx::Status;
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
11pub struct NodeId(pub u32);
12
13#[derive(Debug, PartialEq, Eq)]
14struct IncomingEdge {
15 weight: u32,
17}
18
19#[derive(Debug, PartialEq, Eq)]
20struct BandwidthRequest {
21 average_bandwidth_bps: u64,
22 peak_bandwidth_bps: u64,
23}
24
25#[derive(Debug, PartialEq, Eq)]
26struct InterconnectNode {
27 name: String,
28 id: NodeId,
29 incoming_edges: BTreeMap<NodeId, IncomingEdge>,
31 path_bandwidth_requests: BTreeMap<PathId, BandwidthRequest>,
33 initial_avg_bandwidth_bps: u64,
34 initial_peak_bandwidth_bps: u64,
35}
36
37#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
38pub struct PathId(pub u32);
39
40#[derive(Debug, PartialEq, Eq)]
41pub struct Path {
42 name: String,
43 id: PathId,
44 nodes: Vec<NodeId>,
45}
46
47impl Path {
48 pub fn name(&self) -> &str {
49 &self.name
50 }
51
52 pub fn id(&self) -> PathId {
53 self.id
54 }
55}
56
57#[derive(Debug, PartialEq, Eq)]
58pub struct NodeGraph {
59 nodes: BTreeMap<NodeId, InterconnectNode>,
62}
63
64impl NodeGraph {
65 pub fn new(nodes: Vec<icc::Node>, edges: Vec<icc::Edge>) -> Result<Self, Status> {
66 struct ParsedNode {
68 id: NodeId,
69 name: String,
70 #[allow(unused)]
71 interconnect_name: Option<String>,
72 initial_avg_bandwidth_bps: u64,
73 initial_peak_bandwidth_bps: u64,
74 }
75 let nodes: Vec<_> = Result::from_iter(nodes.into_iter().map(|node| {
76 Ok::<_, Status>(ParsedNode {
77 id: NodeId(node.id.ok_or(Status::INVALID_ARGS)?),
78 name: node.name.ok_or(Status::INVALID_ARGS)?,
79 interconnect_name: node.interconnect_name,
80 initial_avg_bandwidth_bps: node.initial_avg_bandwidth_bps.unwrap_or(0),
81 initial_peak_bandwidth_bps: node.initial_peak_bandwidth_bps.unwrap_or(0),
82 })
83 }))?;
84
85 struct ParsedEdge {
86 src_node_id: NodeId,
87 dst_node_id: NodeId,
88 weight: u32,
89 }
90 let edges: Vec<_> = Result::from_iter(edges.into_iter().map(|edge| {
91 Ok::<_, Status>(ParsedEdge {
92 src_node_id: NodeId(edge.src_node_id.ok_or(Status::INVALID_ARGS)?),
93 dst_node_id: NodeId(edge.dst_node_id.ok_or(Status::INVALID_ARGS)?),
94 weight: edge.weight.unwrap_or(1),
95 })
96 }))?;
97
98 let valid_node_ids: BTreeSet<_> = nodes.iter().map(|node| node.id).collect();
100 if valid_node_ids.len() != nodes.len() {
101 error!("Node list contains duplicate entries.");
102 return Err(Status::INVALID_ARGS);
103 }
104
105 let edge_pairs: BTreeSet<_> =
106 edges.iter().map(|edge| (edge.src_node_id, edge.dst_node_id)).collect();
107 if edge_pairs.len() != edges.len() {
108 error!("Edge list contains duplicate pairs of source to destination node ids.");
109 return Err(Status::INVALID_ARGS);
110 }
111
112 for edge in edges.iter() {
113 if !valid_node_ids.contains(&edge.src_node_id) {
114 error!("Edge contains an invalid src node id {:?}.", edge.src_node_id);
115 return Err(Status::INVALID_ARGS);
116 }
117 if !valid_node_ids.contains(&edge.dst_node_id) {
118 error!("Edge contains an invalid dst node id {:?}.", edge.dst_node_id);
119 return Err(Status::INVALID_ARGS);
120 }
121 if edge.src_node_id == edge.dst_node_id {
122 error!("Edge cannot have src and dst node ids be the same.");
123 return Err(Status::INVALID_ARGS);
124 }
125 }
126
127 let mut graph = BTreeMap::new();
129 for node in nodes {
130 let incoming_edges: BTreeMap<_, _> = edges
135 .iter()
136 .filter(|edge| edge.dst_node_id == node.id)
137 .map(|edge| (edge.src_node_id, IncomingEdge { weight: edge.weight }))
138 .collect();
139 graph.insert(
140 node.id,
141 InterconnectNode {
142 name: node.name,
143 id: node.id,
144 incoming_edges,
145 path_bandwidth_requests: BTreeMap::new(),
146 initial_avg_bandwidth_bps: node.initial_avg_bandwidth_bps,
147 initial_peak_bandwidth_bps: node.initial_peak_bandwidth_bps,
148 },
149 );
150 }
151
152 Ok(NodeGraph { nodes: graph })
153 }
154
155 pub fn update_path(
157 &mut self,
158 path: &Path,
159 average_bandwidth_bps: u64,
160 peak_bandwidth_bps: u64,
161 ) {
162 for node in &path.nodes {
163 let node = self.nodes.get_mut(&node).expect("path is already validated");
164 let request = node
165 .path_bandwidth_requests
166 .get_mut(&path.id)
167 .expect("path was created from graph");
168 request.average_bandwidth_bps = average_bandwidth_bps;
169 request.peak_bandwidth_bps = peak_bandwidth_bps;
170 }
171 }
172
173 pub fn make_bandwidth_requests(&self, path: &Path) -> Vec<icc::NodeBandwidth> {
176 path.nodes
177 .iter()
178 .map(|node_id| {
179 let node = self.nodes.get(&node_id).expect("path is already validated");
180 let requests: Vec<_> = node
181 .path_bandwidth_requests
182 .iter()
183 .map(|(_, request)| icc::BandwidthRequest {
184 average_bandwidth_bps: Some(request.average_bandwidth_bps),
185 peak_bandwidth_bps: Some(request.peak_bandwidth_bps),
186 ..Default::default()
187 })
188 .collect();
189 icc::NodeBandwidth {
190 node_id: Some(node_id.0),
191 requests: Some(requests),
192 ..Default::default()
193 }
194 })
195 .collect()
196 }
197
198 pub fn make_path(
200 &mut self,
201 path_id: PathId,
202 path_name: impl Into<String>,
203 src_node_id: NodeId,
204 dst_node_id: NodeId,
205 ) -> Result<Path, Status> {
206 let mut queue = VecDeque::new();
207 let mut visited = BTreeSet::new();
209 queue.push_back((dst_node_id, Vec::new()));
210 while let Some((node_id, mut path)) = queue.pop_front() {
212 if visited.contains(&node_id) {
213 continue;
214 }
215 visited.insert(node_id);
216 path.push(node_id);
217 if node_id == src_node_id {
218 path.reverse();
219 for node_id in path.iter() {
221 let node = self.nodes.get(&node_id).unwrap();
222 if node.path_bandwidth_requests.contains_key(&path_id) {
223 error!("Duplicate path id {path_id:?}");
224 return Err(Status::ALREADY_EXISTS);
225 }
226 }
227 for node_id in path.iter() {
228 let node = self.nodes.get_mut(&node_id).unwrap();
229 node.path_bandwidth_requests.insert(
230 path_id,
231 BandwidthRequest {
232 average_bandwidth_bps: node.initial_avg_bandwidth_bps,
233 peak_bandwidth_bps: node.initial_peak_bandwidth_bps,
234 },
235 );
236 }
237 return Ok(Path { name: path_name.into(), id: path_id, nodes: path });
238 }
239 for (node_id, _) in &self.nodes.get(&node_id).unwrap().incoming_edges {
241 queue.push_back((*node_id, path.clone()));
242 }
243 }
244 error!("Path between {src_node_id:?} and {dst_node_id:?} not found");
245 Err(Status::NOT_FOUND)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[fuchsia::test]
254 fn test_node_graph_simple() {
255 let nodes = vec![
256 icc::Node {
257 id: Some(0),
258 name: Some("zero".to_string()),
259 interconnect_name: None,
260 ..Default::default()
261 },
262 icc::Node {
263 id: Some(1),
264 name: Some("one".to_string()),
265 interconnect_name: None,
266 ..Default::default()
267 },
268 ];
269 let edges = vec![icc::Edge {
270 src_node_id: Some(0),
271 dst_node_id: Some(1),
272 weight: None,
273 ..Default::default()
274 }];
275 let graph = NodeGraph::new(nodes, edges).unwrap();
276 assert_eq!(
277 graph,
278 NodeGraph {
279 nodes: BTreeMap::from([
280 (
281 NodeId(0),
282 InterconnectNode {
283 name: "zero".to_string(),
284 id: NodeId(0),
285 incoming_edges: BTreeMap::new(),
286 path_bandwidth_requests: BTreeMap::new(),
287 initial_avg_bandwidth_bps: 0,
288 initial_peak_bandwidth_bps: 0,
289 }
290 ),
291 (
292 NodeId(1),
293 InterconnectNode {
294 name: "one".to_string(),
295 id: NodeId(1),
296 incoming_edges: BTreeMap::from([(
297 NodeId(0),
298 IncomingEdge { weight: 1 }
299 )]),
300 path_bandwidth_requests: BTreeMap::new(),
301 initial_avg_bandwidth_bps: 0,
302 initial_peak_bandwidth_bps: 0,
303 }
304 ),
305 ]),
306 }
307 );
308 }
309
310 #[fuchsia::test]
311 fn test_node_graph_missing_id() {
312 let nodes = vec![
313 icc::Node {
314 id: None,
315 name: Some("zero".to_string()),
316 interconnect_name: None,
317 ..Default::default()
318 },
319 icc::Node {
320 id: Some(1),
321 name: Some("one".to_string()),
322 interconnect_name: None,
323 ..Default::default()
324 },
325 ];
326 let edges = vec![icc::Edge {
327 src_node_id: Some(0),
328 dst_node_id: Some(1),
329 weight: Some(1),
330 ..Default::default()
331 }];
332 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
333 }
334
335 #[fuchsia::test]
336 fn test_node_graph_missing_name() {
337 let nodes = vec![
338 icc::Node { id: Some(0), name: None, interconnect_name: None, ..Default::default() },
339 icc::Node {
340 id: Some(1),
341 name: Some("one".to_string()),
342 interconnect_name: None,
343 ..Default::default()
344 },
345 ];
346 let edges = vec![icc::Edge {
347 src_node_id: Some(0),
348 dst_node_id: Some(1),
349 weight: Some(1),
350 ..Default::default()
351 }];
352 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
353 }
354
355 #[fuchsia::test]
356 fn test_node_graph_missing_src() {
357 let nodes = vec![
358 icc::Node {
359 id: Some(0),
360 name: Some("zero".to_string()),
361 interconnect_name: None,
362 ..Default::default()
363 },
364 icc::Node {
365 id: Some(1),
366 name: Some("one".to_string()),
367 interconnect_name: None,
368 ..Default::default()
369 },
370 ];
371 let edges = vec![icc::Edge {
372 src_node_id: None,
373 dst_node_id: Some(1),
374 weight: Some(1),
375 ..Default::default()
376 }];
377 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
378 }
379
380 #[fuchsia::test]
381 fn test_node_graph_missing_dst() {
382 let nodes = vec![
383 icc::Node {
384 id: Some(0),
385 name: Some("zero".to_string()),
386 interconnect_name: None,
387 ..Default::default()
388 },
389 icc::Node {
390 id: Some(1),
391 name: Some("one".to_string()),
392 interconnect_name: None,
393 ..Default::default()
394 },
395 ];
396 let edges = vec![icc::Edge {
397 src_node_id: Some(0),
398 dst_node_id: None,
399 weight: Some(1),
400 ..Default::default()
401 }];
402 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
403 }
404
405 #[fuchsia::test]
406 fn test_node_graph_invalid_edge() {
407 let nodes = vec![
408 icc::Node {
409 id: Some(0),
410 name: Some("zero".to_string()),
411 interconnect_name: None,
412 ..Default::default()
413 },
414 icc::Node {
415 id: Some(1),
416 name: Some("one".to_string()),
417 interconnect_name: None,
418 ..Default::default()
419 },
420 ];
421 let edges = vec![icc::Edge {
422 src_node_id: Some(0),
423 dst_node_id: Some(0),
424 weight: Some(1),
425 ..Default::default()
426 }];
427 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
428 }
429
430 #[fuchsia::test]
431 fn test_node_graph_missing_dst_node() {
432 let nodes = vec![icc::Node {
433 id: Some(0),
434 name: Some("zero".to_string()),
435 interconnect_name: None,
436 ..Default::default()
437 }];
438 let edges = vec![icc::Edge {
439 src_node_id: Some(0),
440 dst_node_id: Some(1),
441 weight: Some(1),
442 ..Default::default()
443 }];
444 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
445 }
446
447 #[fuchsia::test]
448 fn test_node_graph_missing_src_node() {
449 let nodes = vec![icc::Node {
450 id: Some(0),
451 name: Some("zero".to_string()),
452 interconnect_name: None,
453 ..Default::default()
454 }];
455 let edges = vec![icc::Edge {
456 src_node_id: Some(1),
457 dst_node_id: Some(0),
458 weight: Some(1),
459 ..Default::default()
460 }];
461 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
462 }
463
464 #[fuchsia::test]
465 fn test_node_graph_missing_duplicate_node() {
466 let nodes = vec![
467 icc::Node {
468 id: Some(0),
469 name: Some("zero".to_string()),
470 interconnect_name: None,
471 ..Default::default()
472 },
473 icc::Node {
474 id: Some(0),
475 name: Some("zero-2".to_string()),
476 interconnect_name: None,
477 ..Default::default()
478 },
479 icc::Node {
480 id: Some(1),
481 name: Some("one".to_string()),
482 interconnect_name: None,
483 ..Default::default()
484 },
485 ];
486 let edges = vec![icc::Edge {
487 src_node_id: Some(0),
488 dst_node_id: Some(1),
489 weight: Some(1),
490 ..Default::default()
491 }];
492 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
493 }
494
495 #[fuchsia::test]
496 fn test_node_graph_missing_duplicate_edge() {
497 let nodes = vec![
498 icc::Node {
499 id: Some(0),
500 name: Some("zero".to_string()),
501 interconnect_name: None,
502 ..Default::default()
503 },
504 icc::Node {
505 id: Some(1),
506 name: Some("one".to_string()),
507 interconnect_name: None,
508 ..Default::default()
509 },
510 ];
511 let edges = vec![
512 icc::Edge {
513 src_node_id: Some(0),
514 dst_node_id: Some(1),
515 weight: Some(1),
516 ..Default::default()
517 },
518 icc::Edge {
519 src_node_id: Some(0),
520 dst_node_id: Some(1),
521 weight: Some(4),
522 ..Default::default()
523 },
524 ];
525 assert_eq!(NodeGraph::new(nodes, edges), Err(Status::INVALID_ARGS));
526 }
527
528 #[fuchsia::test]
529 fn test_node_graph_complex() {
530 let nodes = vec![
531 icc::Node {
532 id: Some(0),
533 name: Some("zero".to_string()),
534 interconnect_name: None,
535 ..Default::default()
536 },
537 icc::Node {
538 id: Some(1),
539 name: Some("one".to_string()),
540 interconnect_name: None,
541 ..Default::default()
542 },
543 icc::Node {
544 id: Some(2),
545 name: Some("two".to_string()),
546 interconnect_name: None,
547 ..Default::default()
548 },
549 icc::Node {
550 id: Some(3),
551 name: Some("three".to_string()),
552 interconnect_name: None,
553 ..Default::default()
554 },
555 ];
556 let edges = vec![
557 icc::Edge {
558 src_node_id: Some(0),
559 dst_node_id: Some(1),
560 weight: Some(1),
561 ..Default::default()
562 },
563 icc::Edge {
564 src_node_id: Some(1),
565 dst_node_id: Some(2),
566 weight: Some(1),
567 ..Default::default()
568 },
569 icc::Edge {
570 src_node_id: Some(1),
571 dst_node_id: Some(3),
572 weight: Some(1),
573 ..Default::default()
574 },
575 icc::Edge {
576 src_node_id: Some(2),
577 dst_node_id: Some(1),
578 weight: Some(1),
579 ..Default::default()
580 },
581 ];
582 let graph = NodeGraph::new(nodes, edges).unwrap();
583 assert_eq!(
584 graph,
585 NodeGraph {
586 nodes: BTreeMap::from([
587 (
588 NodeId(0),
589 InterconnectNode {
590 name: "zero".to_string(),
591 id: NodeId(0),
592 incoming_edges: BTreeMap::new(),
593 path_bandwidth_requests: BTreeMap::new(),
594 initial_avg_bandwidth_bps: 0,
595 initial_peak_bandwidth_bps: 0,
596 }
597 ),
598 (
599 NodeId(1),
600 InterconnectNode {
601 name: "one".to_string(),
602 id: NodeId(1),
603 incoming_edges: BTreeMap::from([
604 (NodeId(0), IncomingEdge { weight: 1 }),
605 (NodeId(2), IncomingEdge { weight: 1 }),
606 ]),
607 path_bandwidth_requests: BTreeMap::new(),
608 initial_avg_bandwidth_bps: 0,
609 initial_peak_bandwidth_bps: 0,
610 }
611 ),
612 (
613 NodeId(2),
614 InterconnectNode {
615 name: "two".to_string(),
616 id: NodeId(2),
617 incoming_edges: BTreeMap::from([(
618 NodeId(1),
619 IncomingEdge { weight: 1 }
620 )]),
621 path_bandwidth_requests: BTreeMap::new(),
622 initial_avg_bandwidth_bps: 0,
623 initial_peak_bandwidth_bps: 0,
624 }
625 ),
626 (
627 NodeId(3),
628 InterconnectNode {
629 name: "three".to_string(),
630 id: NodeId(3),
631 incoming_edges: BTreeMap::from([(
632 NodeId(1),
633 IncomingEdge { weight: 1 }
634 )]),
635 path_bandwidth_requests: BTreeMap::new(),
636 initial_avg_bandwidth_bps: 0,
637 initial_peak_bandwidth_bps: 0,
638 }
639 ),
640 ]),
641 }
642 );
643 }
644
645 #[fuchsia::test]
646 fn test_find_full_path() {
647 let nodes = vec![
648 icc::Node {
649 id: Some(0),
650 name: Some("zero".to_string()),
651 interconnect_name: None,
652 ..Default::default()
653 },
654 icc::Node {
655 id: Some(1),
656 name: Some("one".to_string()),
657 interconnect_name: None,
658 ..Default::default()
659 },
660 icc::Node {
661 id: Some(2),
662 name: Some("two".to_string()),
663 interconnect_name: None,
664 ..Default::default()
665 },
666 icc::Node {
667 id: Some(3),
668 name: Some("three".to_string()),
669 interconnect_name: None,
670 ..Default::default()
671 },
672 ];
673 let edges = vec![
674 icc::Edge {
675 src_node_id: Some(0),
676 dst_node_id: Some(1),
677 weight: Some(1),
678 ..Default::default()
679 },
680 icc::Edge {
681 src_node_id: Some(1),
682 dst_node_id: Some(2),
683 weight: Some(1),
684 ..Default::default()
685 },
686 icc::Edge {
687 src_node_id: Some(1),
688 dst_node_id: Some(3),
689 weight: Some(1),
690 ..Default::default()
691 },
692 icc::Edge {
693 src_node_id: Some(2),
694 dst_node_id: Some(1),
695 weight: Some(1),
696 ..Default::default()
697 },
698 ];
699 let mut graph = NodeGraph::new(nodes, edges).unwrap();
700
701 static PATH_NAME: &str = "";
702
703 let path = graph.make_path(PathId(0), PATH_NAME, NodeId(0), NodeId(2)).unwrap();
704 assert_eq!(
705 path,
706 Path {
707 id: PathId(0),
708 name: PATH_NAME.to_string(),
709 nodes: vec![NodeId(0), NodeId(1), NodeId(2)]
710 }
711 );
712 assert_eq!(
713 graph.make_path(PathId(1), PATH_NAME, NodeId(0), NodeId(3)),
714 Ok(Path {
715 id: PathId(1),
716 name: PATH_NAME.to_string(),
717 nodes: vec![NodeId(0), NodeId(1), NodeId(3)],
718 })
719 );
720 assert_eq!(
721 graph.make_path(PathId(2), PATH_NAME, NodeId(2), NodeId(3)),
722 Ok(Path {
723 id: PathId(2),
724 name: PATH_NAME.to_string(),
725 nodes: vec![NodeId(2), NodeId(1), NodeId(3)]
726 })
727 );
728 assert_eq!(
729 graph.make_path(PathId(2), PATH_NAME, NodeId(2), NodeId(3)),
730 Err(Status::ALREADY_EXISTS)
731 );
732 assert_eq!(
733 graph.make_path(PathId(3), PATH_NAME, NodeId(1), NodeId(0)),
734 Err(Status::NOT_FOUND)
735 );
736 assert_eq!(
737 graph.make_path(PathId(4), PATH_NAME, NodeId(2), NodeId(0)),
738 Err(Status::NOT_FOUND)
739 );
740 assert_eq!(
741 graph.make_path(PathId(5), PATH_NAME, NodeId(3), NodeId(2)),
742 Err(Status::NOT_FOUND)
743 );
744
745 graph.update_path(&path, 100, 500);
746
747 assert_eq!(
748 graph.make_bandwidth_requests(&path),
749 vec![
750 icc::NodeBandwidth {
751 node_id: Some(0),
752 requests: Some(vec![
753 icc::BandwidthRequest {
754 average_bandwidth_bps: Some(100),
755 peak_bandwidth_bps: Some(500),
756 ..Default::default()
757 },
758 icc::BandwidthRequest {
759 average_bandwidth_bps: Some(0),
760 peak_bandwidth_bps: Some(0),
761 ..Default::default()
762 },
763 ]),
764 ..Default::default()
765 },
766 icc::NodeBandwidth {
767 node_id: Some(1),
768 requests: Some(vec![
769 icc::BandwidthRequest {
770 average_bandwidth_bps: Some(100),
771 peak_bandwidth_bps: Some(500),
772 ..Default::default()
773 },
774 icc::BandwidthRequest {
775 average_bandwidth_bps: Some(0),
776 peak_bandwidth_bps: Some(0),
777 ..Default::default()
778 },
779 icc::BandwidthRequest {
780 average_bandwidth_bps: Some(0),
781 peak_bandwidth_bps: Some(0),
782 ..Default::default()
783 },
784 ]),
785 ..Default::default()
786 },
787 icc::NodeBandwidth {
788 node_id: Some(2),
789 requests: Some(vec![
790 icc::BandwidthRequest {
791 average_bandwidth_bps: Some(100),
792 peak_bandwidth_bps: Some(500),
793 ..Default::default()
794 },
795 icc::BandwidthRequest {
796 average_bandwidth_bps: Some(0),
797 peak_bandwidth_bps: Some(0),
798 ..Default::default()
799 },
800 ]),
801 ..Default::default()
802 },
803 ]
804 );
805 }
806}