1use std::collections::{BTreeMap, BTreeSet};
9use std::time::Instant;
10
11use super::records::{
12 Carrier, Line, LineKind, LineLife, LineState, Link, LinkCounters, LinkState, Mount,
13 StationActivity,
14};
15mod document;
16mod service_lines;
17use crate::server::connection::is_console_subject;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum TopologyChange {
22 Service {
24 subject: String,
26 present: bool,
28 },
29 Channel {
31 name: String,
33 life: LineLife,
35 state: LineState,
37 },
38 ConnectionOpened {
40 id: u64,
42 mount: Mount,
44 participant: Option<String>,
46 },
47 ConnectionClosed {
49 id: u64,
51 },
52 Worker {
55 connection: u64,
57 identity: String,
59 subjects: Vec<String>,
61 activities: Vec<StationActivity>,
63 },
64 Flow {
66 connection: u64,
68 line: String,
70 role: FlowRole,
72 present: bool,
74 },
75 Link {
77 link_id: String,
79 peer: String,
81 carrier: Option<Carrier>,
83 state: LinkState,
85 reason: Option<String>,
87 counters: LinkCounters,
89 },
90 Peer {
92 name: String,
94 present: bool,
96 },
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
103pub enum FlowRole {
104 Publishes,
106 Subscribes,
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct ThisDevice {
113 pub id: String,
115 pub liminal: String,
117 pub started: Instant,
119 pub depot: bool,
122}
123
124#[derive(Clone, Debug)]
126pub struct TopologyModel {
127 this: ThisDevice,
128 rev: u64,
129 stations: BTreeSet<String>,
130 lines: BTreeMap<String, (LineLife, LineState)>,
131 connections: BTreeMap<u64, (Mount, Option<String>)>,
132 peers: BTreeSet<String>,
133 links: BTreeMap<String, Link>,
134 parent: Option<String>,
135 workers: BTreeMap<u64, WorkerStation>,
137 flows: BTreeMap<String, BTreeSet<(FlowRole, u64)>>,
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
143struct WorkerStation {
144 identity: String,
145 subjects: Vec<String>,
146 activities: Vec<StationActivity>,
147}
148
149impl TopologyModel {
150 #[must_use]
153 pub fn new(
154 this: ThisDevice,
155 stations: impl IntoIterator<Item = String>,
156 lines: impl IntoIterator<Item = (String, LineLife, LineState)>,
157 connections: impl IntoIterator<Item = (u64, Mount, Option<String>)>,
158 peers: impl IntoIterator<Item = String>,
159 ) -> Self {
160 Self {
161 this,
162 rev: 1,
163 stations: stations.into_iter().collect(),
164 lines: lines
165 .into_iter()
166 .filter(|(name, _, _)| !is_console_subject(name))
167 .map(|(name, life, state)| (name, (life, state)))
168 .collect(),
169 connections: connections
170 .into_iter()
171 .map(|(id, mount, participant)| (id, (mount, participant)))
172 .collect(),
173 peers: peers.into_iter().collect(),
174 links: BTreeMap::new(),
175 parent: None,
176 workers: BTreeMap::new(),
177 flows: BTreeMap::new(),
178 }
179 }
180
181 #[must_use]
183 pub fn with_machine(mut self, machine: &crate::config::types::MachineConfig) -> Self {
184 self.parent.clone_from(&machine.parent);
185 self
186 }
187
188 #[must_use]
190 pub const fn depot(&self) -> bool {
191 self.this.depot
192 }
193
194 #[must_use]
196 pub const fn rev(&self) -> u64 {
197 self.rev
198 }
199
200 pub(crate) fn ensure_revision(&mut self, revision: u64) {
201 self.rev = self.rev.max(revision);
202 }
203
204 pub fn apply(&mut self, change: TopologyChange) -> bool {
209 let changed = match change {
210 TopologyChange::Service { subject, present } => {
211 if present {
212 self.stations.insert(subject)
213 } else {
214 self.stations.remove(&subject)
215 }
216 }
217 TopologyChange::Channel { name, life, state } => {
218 !is_console_subject(&name)
219 && self.lines.insert(name, (life, state)) != Some((life, state))
220 }
221 TopologyChange::ConnectionOpened {
222 id,
223 mount,
224 participant,
225 } => {
226 let entry = (mount, participant);
227 if self.connections.get(&id) == Some(&entry) {
228 false
229 } else {
230 self.connections.insert(id, entry);
231 true
232 }
233 }
234 TopologyChange::ConnectionClosed { id } => {
235 let closed = self.connections.remove(&id).is_some();
236 let worker = self.workers.remove(&id).is_some();
237 for users in self.flows.values_mut() {
238 users.retain(|(_, connection)| *connection != id);
239 }
240 self.flows.retain(|_, users| !users.is_empty());
241 closed || worker
242 }
243 TopologyChange::Worker {
244 connection,
245 identity,
246 subjects,
247 activities,
248 } => {
249 let worker = WorkerStation {
250 identity,
251 subjects,
252 activities,
253 };
254 self.workers.insert(connection, worker.clone()) != Some(worker)
255 }
256 TopologyChange::Flow {
257 connection,
258 line,
259 role,
260 present,
261 } => {
262 if is_console_subject(&line) {
263 false
264 } else {
265 let moved = if present {
266 self.flows
267 .entry(line)
268 .or_default()
269 .insert((role, connection))
270 } else if let Some(users) = self.flows.get_mut(&line) {
271 let removed = users.remove(&(role, connection));
272 if users.is_empty() {
273 self.flows.remove(&line);
274 }
275 removed
276 } else {
277 false
278 };
279 moved && self.workers.contains_key(&connection)
280 }
281 }
282 TopologyChange::Link {
283 link_id,
284 peer,
285 carrier,
286 state,
287 reason,
288 counters,
289 } => {
290 let link = Link::federated(&self.this.id, &peer, carrier, state, reason, counters);
291 self.apply_link(link_id, link)
292 }
293 TopologyChange::Peer { name, present } => {
294 if present {
295 self.peers.insert(name)
296 } else {
297 self.peers.remove(&name)
298 }
299 }
300 };
301 if changed {
302 self.rev = self.rev.saturating_add(1);
303 }
304 changed
305 }
306
307 fn apply_link(&mut self, link_id: String, mut link: Link) -> bool {
309 if link.id != link_id || link.name.is_empty() || link.name == self.this.id {
310 tracing::error!(%link_id, peer = %link.name, "topology link refused: invalid endpoint or link id");
311 false
312 } else {
313 if let Some(previous) = self.links.get(&link_id) {
314 link.retain_carrier_refusals(previous);
315 link.crossed = link.crossed.or(previous.crossed);
316 link.refused = link.refused.or(previous.refused);
317 link.already_crossed = link.already_crossed.or(previous.already_crossed);
318 link.lifecycle = link.lifecycle.or(previous.lifecycle);
319 }
320 self.links.insert(link_id, link.clone()) != Some(link)
321 }
322 }
323
324 fn stops(&self, line: &str, line_life: LineLife) -> Vec<String> {
330 let mut stops: Vec<String> = Vec::new();
331 let depot = (self.this.depot && line_life == LineLife::Kept).then(|| self.depot_id());
332 let mut own = depot.into_iter().chain([self.this.id.clone()]);
334 let users = self.flows.get(line).into_iter().flatten();
335 for (role, connection) in users {
336 if *role == FlowRole::Subscribes {
337 stops.extend(own.by_ref());
338 }
339 if let Some(identity) = self.workers.get(connection).map(|worker| &worker.identity)
340 && self.reserved(identity).is_none()
341 && !stops.contains(identity)
342 {
343 stops.push(identity.clone());
344 }
345 }
346 stops.extend(own);
347 stops
348 }
349
350 #[must_use]
355 pub fn reserved(&self, id: &str) -> Option<&'static str> {
356 if id == self.this.id {
357 Some("exchange")
358 } else if self.this.depot
359 && id
360 .strip_prefix(self.this.id.as_str())
361 .is_some_and(|rest| rest == ".depot")
362 {
363 Some("depot")
364 } else {
365 None
366 }
367 }
368
369 fn depot_id(&self) -> String {
371 format!("{}.depot", self.this.id)
372 }
373
374 pub fn incarnation_started(&mut self, started: Instant) -> bool {
377 let moved = self.this.started != started;
378 self.this.started = started;
379 moved
380 }
381
382 #[cfg(test)]
384 pub(crate) fn participants(&self) -> Vec<Option<String>> {
385 self.connections
386 .values()
387 .map(|(_, participant)| participant.clone())
388 .collect()
389 }
390
391 #[cfg(test)]
393 pub(crate) fn connection_count(&self) -> usize {
394 self.connections.len()
395 }
396}