1use super::*;
2
3#[derive(Debug)]
8pub struct MultipleRefusal {
9 pub error: Error,
11 pub refused: Option<usize>,
15 pub committed: Vec<CovSubscriptionSnapshot>,
18}
19
20impl From<Error> for MultipleRefusal {
21 fn from(error: Error) -> Self {
23 Self {
24 error,
25 refused: None,
26 committed: Vec::new(),
27 }
28 }
29}
30
31#[derive(Clone, Copy)]
33enum Cap {
34 Peer,
36 Table,
38}
39
40impl CovSubscriptionTable {
41 pub fn subscribe(&mut self, sub: CovSubscription) -> Result<CovSubscriptionSnapshot, Error> {
46 let key = sub.key()?;
47 if key.multiple_context().is_some() {
48 return Err(Error::Encoding(
49 "Multiple references must be admitted through subscribe_multiple".into(),
50 ));
51 }
52 self.purge_expired();
53 let existing = self.subs.get(&key).cloned();
54 self.check_admission(
55 &sub.recipient(),
56 sub.expires_at.is_none(),
57 existing.as_deref(),
58 )?;
59 let generation = self.reserve_generations(1)?;
60 Ok(self.publish(key, sub, generation, None, None, None))
61 }
62
63 pub fn subscribe_multiple(
90 &mut self,
91 context: &MultipleContextKey,
92 route: &SubscriberEndpoint,
93 expires_at: Instant,
94 max_notification_delay: u32,
95 subscriber_max_apdu: Option<u16>,
96 mut subscriptions: Vec<CovSubscription>,
97 ) -> Result<Vec<CovSubscriptionSnapshot>, MultipleRefusal> {
98 context.recipient.validate()?;
99 let recipient = CovRecipient::from_endpoint(&route.mac, route.network.as_ref());
100 recipient.validate()?;
101 if recipient != context.recipient {
102 return Err(
103 Error::Encoding("Multiple route does not match its recipient".into()).into(),
104 );
105 }
106 for sub in &subscriptions {
107 if sub.key()?.multiple_context() != Some(context)
108 || sub.expires_at != Some(expires_at)
109 || sub.endpoint() != *route
110 {
111 return Err(Error::Encoding(
112 "Multiple subscription does not match its context/route/expiry".into(),
113 )
114 .into());
115 }
116 }
117 self.purge_expired();
118 let overflow = self
119 .first_overflow(&recipient, &subscriptions)
120 .map(|(position, cap)| (position, self.refuse_new(cap)));
121 match overflow {
122 Some((0, error)) => {
123 return Err(MultipleRefusal {
124 error,
125 refused: Some(0),
126 committed: Vec::new(),
127 });
128 }
129 Some((position, _)) => subscriptions.truncate(position),
130 None => {}
131 }
132 subscriptions.reverse();
134 let mut keys = std::collections::HashSet::new();
135 subscriptions.retain(|sub| keys.insert(sub.key().expect("validated identity")));
136 subscriptions.reverse();
137 let first_generation = self.reserve_generations(subscriptions.len())?;
138 let (flight, replaced) = self.context_flight(context, route, !subscriptions.is_empty());
140 let subscriber_max_apdu = subscriber_max_apdu.or_else(|| {
143 self.subs
144 .values()
145 .find(|entry| entry.key.multiple_context() == Some(context))
146 .and_then(|entry| entry.subscriber_max_apdu)
147 });
148 let mut previously_indefinite = 0;
149 for entry in self.subs.values_mut() {
150 if entry.key.multiple_context() == Some(context) {
151 previously_indefinite += usize::from(entry.expires_at.is_none());
152 entry.subscription.expires_at = Some(expires_at);
153 entry.max_notification_delay = Some(max_notification_delay);
154 entry.subscriber_max_apdu = subscriber_max_apdu;
155 entry.subscription.subscriber_mac = route.mac.clone();
156 entry.subscription.subscriber_network = route.network.clone();
157 entry.flight = flight.clone();
158 }
159 }
160 if let Some(count) = self.peer_indefinite_counts.get_mut(&recipient) {
161 *count -= previously_indefinite;
162 if *count == 0 {
163 self.peer_indefinite_counts.remove(&recipient);
164 }
165 }
166 let accepted = subscriptions
167 .into_iter()
168 .enumerate()
169 .map(|(offset, sub)| {
170 self.publish(
171 sub.key().expect("validated identity"),
172 sub,
173 first_generation + offset as u64,
174 Some(max_notification_delay),
175 subscriber_max_apdu,
176 Some(flight.clone()),
177 )
178 })
179 .collect();
180 {
181 let mut timed = self.timed.lock();
182 timed.set_delay(context, max_notification_delay);
183 let time_remaining = CovTimeRemaining::at(Some(expires_at), runtime_clock::now())
186 .wire_seconds()
187 .unwrap_or(u32::MAX);
188 timed.set_sizing(context, subscriber_max_apdu, time_remaining);
189 }
190 if let Some(replaced) = replaced {
191 self.fence_context_flight(context, &replaced, &keys);
192 }
193 match overflow {
194 None => Ok(accepted),
195 Some((position, error)) => Err(MultipleRefusal {
196 error,
197 refused: Some(position),
198 committed: accepted,
199 }),
200 }
201 }
202
203 fn first_overflow(
207 &self,
208 recipient: &CovRecipient,
209 proposals: &[CovSubscription],
210 ) -> Option<(usize, Cap)> {
211 let (peer_room, table_room) = self.room(recipient);
212 let room = peer_room.min(table_room);
213 let mut added = std::collections::HashSet::new();
214 for (position, sub) in proposals.iter().enumerate() {
215 let key = sub.key().expect("validated identity");
216 if self.subs.contains_key(&key) || added.contains(&key) {
217 continue;
218 }
219 if added.len() == room {
220 let cap = self
221 .cap_passed(recipient, room + 1)
222 .expect("one past the room passes a cap");
223 return Some((position, cap));
224 }
225 added.insert(key);
226 }
227 None
228 }
229
230 #[cfg(test)]
235 pub(crate) fn admit_for_test(
236 &mut self,
237 sub: CovSubscription,
238 max_notification_delay: u32,
239 ) -> Result<CovSubscriptionSnapshot, Error> {
240 let Some(context) = sub.key()?.multiple_context().cloned() else {
241 return self.subscribe(sub);
242 };
243 let expires_at = sub
244 .expires_at
245 .expect("Multiple contexts always have a finite lifetime");
246 let mut accepted = self
247 .subscribe_multiple(
248 &context,
249 &sub.endpoint(),
250 expires_at,
251 max_notification_delay,
252 None,
253 vec![sub],
254 )
255 .map_err(|refusal| refusal.error)?;
256 Ok(accepted.remove(0))
257 }
258
259 fn reserve_generations(&mut self, count: usize) -> Result<u64, Error> {
260 if count == 0 {
261 return Ok(self.generation);
262 }
263 let last = u64::try_from(count)
264 .ok()
265 .and_then(|count| self.generation.checked_add(count));
266 let Some(last) = last else {
267 self.counters
268 .subscriptions_rejected_capacity
269 .fetch_add(1, Ordering::Relaxed);
270 return Err(Error::protocol(
271 ErrorClass::RESOURCES,
272 ErrorCode::NO_SPACE_TO_ADD_LIST_ELEMENT,
273 ));
274 };
275 let first = self.generation + 1;
276 self.generation = last;
277 Ok(first)
278 }
279
280 fn publish(
281 &mut self,
282 key: CovSubscriptionKey,
283 sub: CovSubscription,
284 generation: u64,
285 max_notification_delay: Option<u32>,
286 subscriber_max_apdu: Option<u16>,
287 context_flight: Option<super::confirmed::FlightMarker>,
288 ) -> CovSubscriptionSnapshot {
289 let snapshot = CovSubscriptionSnapshot {
290 key,
291 generation,
292 owner: Arc::clone(&self.owner),
293 last_successful_ticket: 0,
294 flight: context_flight.unwrap_or_default(),
295 subscription: sub.clone(),
296 max_notification_delay,
297 subscriber_max_apdu,
298 };
299 {
300 let mut timed = self.timed.lock();
301 let delay = max_notification_delay.unwrap_or_default();
302 match (snapshot.key.multiple_context(), sub.timestamped) {
303 (Some(_), true) => timed.reset(&snapshot.key, generation, delay),
304 (Some(_), false) => timed.reset_untimed(&snapshot.key, generation, delay),
305 (None, _) => timed.remove(&snapshot.key),
306 }
307 }
308 let peer = sub.recipient();
309 let new_indefinite = sub.expires_at.is_none();
310 match self.subs.insert(snapshot.key.clone(), snapshot.clone()) {
311 Some(old) => {
312 if !old.flight.same(&snapshot.flight) {
313 old.flight.fence();
316 }
317 let old_indefinite = old.expires_at.is_none();
318 if old_indefinite != new_indefinite {
319 if new_indefinite {
320 *self.peer_indefinite_counts.entry(peer).or_default() += 1;
321 } else if let Some(count) = self.peer_indefinite_counts.get_mut(&peer) {
322 *count = count.saturating_sub(1);
323 if *count == 0 {
324 self.peer_indefinite_counts.remove(&peer);
325 }
326 }
327 }
328 }
329 _ => {
330 *self.peer_counts.entry(peer.clone()).or_default() += 1;
331 if new_indefinite {
332 *self.peer_indefinite_counts.entry(peer).or_default() += 1;
333 }
334 self.counters
335 .subscriptions_created
336 .fetch_add(1, Ordering::Relaxed);
337 }
338 }
339 self.counters
340 .subscriptions_active
341 .store(self.subs.len() as u64, Ordering::Relaxed);
342 snapshot
343 }
344
345 pub(super) fn check_admission(
347 &mut self,
348 peer: &CovRecipient,
349 is_indefinite: bool,
350 existing: Option<&CovSubscription>,
351 ) -> Result<(), Error> {
352 self.purge_expired();
353
354 if is_indefinite && !self.policy.allow_indefinite_subscriptions {
355 self.counters
356 .subscriptions_rejected_indefinite
357 .fetch_add(1, Ordering::Relaxed);
358 return Err(Error::protocol(
359 ErrorClass::SERVICES,
360 ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
361 ));
362 }
363
364 let (new_count, new_indefinite) = match existing {
365 Some(sub) => {
366 let was_indefinite = sub.expires_at.is_none();
367 let add_indefinite = if is_indefinite && !was_indefinite {
368 1
369 } else {
370 0
371 };
372 (0, add_indefinite)
373 }
374 None => {
375 let add_indefinite = if is_indefinite { 1 } else { 0 };
376 (1, add_indefinite)
377 }
378 };
379
380 self.check_caps(peer, new_count, new_indefinite)
381 }
382
383 fn check_caps(
387 &mut self,
388 peer: &CovRecipient,
389 new_count: usize,
390 new_indefinite: usize,
391 ) -> Result<(), Error> {
392 self.purge_expired();
393
394 if new_indefinite > 0 {
395 if !self.policy.allow_indefinite_subscriptions {
396 self.counters
397 .subscriptions_rejected_indefinite
398 .fetch_add(1, Ordering::Relaxed);
399 return Err(Error::protocol(
400 ErrorClass::SERVICES,
401 ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
402 ));
403 }
404 let current_indefinite = self.peer_indefinite_counts.get(peer).copied().unwrap_or(0);
405 if current_indefinite + new_indefinite > self.policy.max_indefinite_per_peer {
406 self.counters
407 .subscriptions_rejected_indefinite
408 .fetch_add(1, Ordering::Relaxed);
409 return Err(Error::protocol(
410 ErrorClass::RESOURCES,
411 ErrorCode::NO_SPACE_TO_ADD_LIST_ELEMENT,
412 ));
413 }
414 }
415
416 match self.cap_passed(peer, new_count) {
417 Some(cap) => Err(self.refuse_new(cap)),
418 None => Ok(()),
419 }
420 }
421
422 fn room(&self, peer: &CovRecipient) -> (usize, usize) {
426 let current_peer = self.peer_counts.get(peer).copied().unwrap_or(0);
427 let peer_room = self
428 .policy
429 .max_subscriptions_per_peer
430 .saturating_sub(current_peer);
431 let mut capacity = self.policy.max_subscriptions_global;
432 if !self.policy.is_peer_reserved(peer) {
433 capacity = capacity.min(self.policy.effective_unreserved_capacity());
434 }
435 (peer_room, capacity.saturating_sub(self.subs.len()))
436 }
437
438 fn cap_passed(&self, peer: &CovRecipient, new_count: usize) -> Option<Cap> {
441 let (peer_room, table_room) = self.room(peer);
442 if new_count > peer_room {
443 Some(Cap::Peer)
444 } else if new_count > table_room {
445 Some(Cap::Table)
446 } else {
447 None
448 }
449 }
450
451 fn refuse_new(&self, cap: Cap) -> Error {
453 let counter = match cap {
454 Cap::Peer => &self.counters.subscriptions_rejected_quota,
455 Cap::Table => &self.counters.subscriptions_rejected_capacity,
456 };
457 counter.fetch_add(1, Ordering::Relaxed);
458 Error::protocol(
459 ErrorClass::RESOURCES,
460 ErrorCode::NO_SPACE_TO_ADD_LIST_ELEMENT,
461 )
462 }
463}