Skip to content

Commit b516b5c

Browse files
committed
northbound: detect list entry mismatches at compile time
The state retrieval framework used to dispatch through a handwritten, type-erased ListEntry enum, with every callback downcasting its input via as_xxx().unwrap(). A mismatch between what a parent list yields and what a child expects was only caught at runtime, bringing down the protocol instance. YangList and YangContainer now carry ListEntry and ParentListEntry associated types, and the per-protocol ListEntry enum is auto-generated as the sum of every list's ListEntry type. The generated glue passes each entry yielded by a parent list directly to its children, turning any mismatch into a build error. As a bonus, list iterators are no longer boxed, and lists that relay to child tasks can override YangList::child_task instead of implementing ListEntryKind by hand. Signed-off-by: Renato Westphal <renatowestphal@gmail.com>
1 parent 1aef440 commit b516b5c

19 files changed

Lines changed: 2860 additions & 2253 deletions

File tree

holo-bfd/src/northbound/state.rs

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
use std::borrow::Cow;
88
use std::sync::atomic;
99

10-
use enum_as_inner::EnumAsInner;
11-
use holo_northbound::state::{ListEntryKind, Provider, YangContainer, YangList, YangOps};
10+
use holo_northbound::state::{ListIterator, Provider, YangContainer, YangList, YangOps};
1211
use holo_utils::bfd::{PathType, State};
1312
use holo_utils::num::SaturatingInto;
1413
use holo_utils::option::OptionExt;
@@ -23,30 +22,20 @@ use crate::packet::DiagnosticCode;
2322
use crate::session::Session;
2423

2524
impl Provider for Master {
26-
type ListEntry<'a> = ListEntry<'a>;
25+
type ListEntry<'a> = yang_gen::ops::ListEntry<'a>;
2726
const YANG_OPS: YangOps<Self> = yang_gen::ops::YANG_OPS_STATE;
2827

2928
fn top_level_node(&self) -> String {
3029
format!("/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='{}'][name='main']/ietf-bfd:bfd", Protocol::BFD.to_yang(),)
3130
}
3231
}
3332

34-
#[derive(Debug, Default)]
35-
#[derive(EnumAsInner)]
36-
pub enum ListEntry<'a> {
37-
#[default]
38-
None,
39-
Session(&'a Session),
40-
}
41-
42-
pub type ListIterator<'a> = Box<dyn Iterator<Item = ListEntry<'a>> + 'a>;
43-
44-
impl ListEntryKind for ListEntry<'_> {}
45-
4633
// ===== YANG impls =====
4734

4835
impl<'a> YangContainer<'a, Master> for bfd::summary::Summary {
49-
fn new(master: &'a Master, _list_entry: &ListEntry<'a>) -> Option<Self> {
36+
type ParentListEntry = ();
37+
38+
fn new(master: &'a Master, _: &Self::ParentListEntry) -> Option<Self> {
5039
Some(Self {
5140
number_of_sessions: Some(master.sessions_count(None, None).saturating_into()),
5241
number_of_sessions_up: Some(master.sessions_count(None, Some(State::Up)).saturating_into()),
@@ -57,7 +46,9 @@ impl<'a> YangContainer<'a, Master> for bfd::summary::Summary {
5746
}
5847

5948
impl<'a> YangContainer<'a, Master> for bfd::ip_mh::summary::Summary {
60-
fn new(master: &'a Master, _list_entry: &ListEntry<'a>) -> Option<Self> {
49+
type ParentListEntry = ();
50+
51+
fn new(master: &'a Master, _: &Self::ParentListEntry) -> Option<Self> {
6152
let path_type = Some(PathType::IpMultihop);
6253
Some(Self {
6354
number_of_sessions: Some(master.sessions_count(path_type, None).saturating_into()),
@@ -69,13 +60,15 @@ impl<'a> YangContainer<'a, Master> for bfd::ip_mh::summary::Summary {
6960
}
7061

7162
impl<'a> YangList<'a, Master> for bfd::ip_mh::session_groups::session_group::SessionGroup {
72-
fn iter(master: &'a Master, _list_entry: &ListEntry<'a>) -> Option<ListIterator<'a>> {
73-
let iter = master.sessions.iter().filter(|sess| sess.key.is_ip_multihop()).map(ListEntry::Session);
74-
Some(Box::new(iter))
63+
type ParentListEntry = ();
64+
type ListEntry = &'a Session;
65+
66+
fn iter(master: &'a Master, _: &Self::ParentListEntry) -> Option<impl ListIterator<'a, Self::ListEntry>> {
67+
let iter = master.sessions.iter().filter(|sess| sess.key.is_ip_multihop());
68+
Some(iter)
7569
}
7670

77-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Self {
78-
let sess = list_entry.as_session().unwrap();
71+
fn new(_master: &'a Master, sess: &Self::ListEntry) -> Self {
7972
let (src, dst) = sess.key.as_ip_multihop().unwrap();
8073
Self {
8174
source_addr: *src,
@@ -85,13 +78,15 @@ impl<'a> YangList<'a, Master> for bfd::ip_mh::session_groups::session_group::Ses
8578
}
8679

8780
impl<'a> YangList<'a, Master> for bfd::ip_mh::session_groups::session_group::sessions::Sessions<'a> {
88-
fn iter(_master: &'a Master, list_entry: &ListEntry<'a>) -> Option<ListIterator<'a>> {
89-
let sess = list_entry.as_session().unwrap();
90-
Some(Box::new(std::iter::once(ListEntry::Session(sess))))
81+
type ParentListEntry = &'a Session;
82+
type ListEntry = &'a Session;
83+
84+
fn iter(_master: &'a Master, sess: &Self::ParentListEntry) -> Option<impl ListIterator<'a, Self::ListEntry>> {
85+
let iter = std::iter::once(*sess);
86+
Some(iter)
9187
}
9288

93-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Self {
94-
let sess = list_entry.as_session().unwrap();
89+
fn new(_master: &'a Master, sess: &Self::ListEntry) -> Self {
9590
Self {
9691
path_type: Some(sess.key.path_type().to_yang()),
9792
ip_encapsulation: Some(true),
@@ -105,8 +100,9 @@ impl<'a> YangList<'a, Master> for bfd::ip_mh::session_groups::session_group::ses
105100
}
106101

107102
impl<'a> YangContainer<'a, Master> for bfd::ip_mh::session_groups::session_group::sessions::session_running::SessionRunning<'a> {
108-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Option<Self> {
109-
let sess = list_entry.as_session().unwrap();
103+
type ParentListEntry = &'a Session;
104+
105+
fn new(_master: &'a Master, sess: &Self::ParentListEntry) -> Option<Self> {
110106
Some(Self {
111107
session_index: Some(sess.id as u32),
112108
local_state: Some(sess.state.local_state),
@@ -123,8 +119,9 @@ impl<'a> YangContainer<'a, Master> for bfd::ip_mh::session_groups::session_group
123119
}
124120

125121
impl<'a> YangContainer<'a, Master> for bfd::ip_mh::session_groups::session_group::sessions::session_statistics::SessionStatistics {
126-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Option<Self> {
127-
let sess = list_entry.as_session().unwrap();
122+
type ParentListEntry = &'a Session;
123+
124+
fn new(_master: &'a Master, sess: &Self::ParentListEntry) -> Option<Self> {
128125
Some(Self {
129126
create_time: Some(sess.statistics.create_time).ignore_in_testing(),
130127
last_down_time: sess.statistics.last_down_time.ignore_in_testing(),
@@ -140,7 +137,9 @@ impl<'a> YangContainer<'a, Master> for bfd::ip_mh::session_groups::session_group
140137
}
141138

142139
impl<'a> YangContainer<'a, Master> for bfd::ip_sh::summary::Summary {
143-
fn new(master: &'a Master, _list_entry: &ListEntry<'a>) -> Option<Self> {
140+
type ParentListEntry = ();
141+
142+
fn new(master: &'a Master, _: &Self::ParentListEntry) -> Option<Self> {
144143
let path_type = Some(PathType::IpSingleHop);
145144
Some(Self {
146145
number_of_sessions: Some(master.sessions_count(path_type, None).saturating_into()),
@@ -152,13 +151,15 @@ impl<'a> YangContainer<'a, Master> for bfd::ip_sh::summary::Summary {
152151
}
153152

154153
impl<'a> YangList<'a, Master> for bfd::ip_sh::sessions::session::Session<'a> {
155-
fn iter(master: &'a Master, _list_entry: &ListEntry<'a>) -> Option<ListIterator<'a>> {
156-
let iter = master.sessions.iter().filter(|sess| sess.key.is_ip_single_hop()).map(ListEntry::Session);
157-
Some(Box::new(iter))
154+
type ParentListEntry = ();
155+
type ListEntry = &'a Session;
156+
157+
fn iter(master: &'a Master, _: &Self::ParentListEntry) -> Option<impl ListIterator<'a, Self::ListEntry>> {
158+
let iter = master.sessions.iter().filter(|sess| sess.key.is_ip_single_hop());
159+
Some(iter)
158160
}
159161

160-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Self {
161-
let sess = list_entry.as_session().unwrap();
162+
fn new(_master: &'a Master, sess: &Self::ListEntry) -> Self {
162163
let (ifname, dst) = sess.key.as_ip_single_hop().unwrap();
163164
Self {
164165
interface: Cow::Borrowed(ifname),
@@ -175,8 +176,9 @@ impl<'a> YangList<'a, Master> for bfd::ip_sh::sessions::session::Session<'a> {
175176
}
176177

177178
impl<'a> YangContainer<'a, Master> for bfd::ip_sh::sessions::session::session_running::SessionRunning<'a> {
178-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Option<Self> {
179-
let sess = list_entry.as_session().unwrap();
179+
type ParentListEntry = &'a Session;
180+
181+
fn new(_master: &'a Master, sess: &Self::ParentListEntry) -> Option<Self> {
180182
Some(Self {
181183
session_index: Some(sess.id as u32),
182184
local_state: Some(sess.state.local_state),
@@ -193,8 +195,9 @@ impl<'a> YangContainer<'a, Master> for bfd::ip_sh::sessions::session::session_ru
193195
}
194196

195197
impl<'a> YangContainer<'a, Master> for bfd::ip_sh::sessions::session::session_statistics::SessionStatistics {
196-
fn new(_master: &'a Master, list_entry: &ListEntry<'a>) -> Option<Self> {
197-
let sess = list_entry.as_session().unwrap();
198+
type ParentListEntry = &'a Session;
199+
200+
fn new(_master: &'a Master, sess: &Self::ParentListEntry) -> Option<Self> {
198201
Some(Self {
199202
create_time: Some(sess.statistics.create_time).ignore_in_testing(),
200203
last_down_time: sess.statistics.last_down_time.ignore_in_testing(),

0 commit comments

Comments
 (0)