1use std::time::Duration;
4
5use crate::OpNumber;
6use ozzy_proto::RequestId;
7
8use super::{
9 Channel, FlowError, Operation, PipelineLimits, Prefix, Probe, ProbeError, ProbeScheduler,
10 ProbeTiming, Report, Scope, Sender, VecDeque, ledger,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct OpenRequest {
19 request_id: Option<RequestId>,
20 report: Report,
21}
22
23impl OpenRequest {
24 pub const fn report(self) -> Report {
26 self.report
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Repair {
33 pub channel: Channel,
35 pub after: Prefix,
37 pub through: Prefix,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum StatusOutcome {
44 Ignored,
46 Observed,
48 Verify,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
54pub enum TransmitError {
55 #[error(transparent)]
57 Flow(#[from] FlowError),
58 #[error(transparent)]
60 Probe(#[from] ProbeError),
61}
62
63#[derive(Debug)]
70pub struct Transmitter {
71 scope: Scope,
72 limits: PipelineLimits,
73 probes: ProbeScheduler,
74 sender: Option<Sender>,
75 announced_channel: Option<Channel>,
76 spare: VecDeque<Operation>,
78 active: bool,
79 candidate: Option<OpenRequest>,
80 candidate_limit: Option<OpNumber>,
81 repair: Option<Repair>,
82 broadcast: bool,
83 catch_up: Option<OpNumber>,
84 receipt_progress: ozzy_core::live::LiveProgress,
85}
86
87impl Transmitter {
88 pub fn new(
91 scope: Scope,
92 limits: PipelineLimits,
93 first: RequestId,
94 timing: ProbeTiming,
95 now: Duration,
96 ) -> Result<Self, TransmitError> {
97 let probes = ProbeScheduler::new(first, timing, now)?;
99 Ok(Self {
100 scope,
101 limits,
102 probes,
103 sender: None,
104 announced_channel: None,
105 spare: ledger(limits)?,
106 active: false,
107 candidate: None,
108 candidate_limit: None,
109 repair: None,
110 broadcast: false,
111 catch_up: None,
112 receipt_progress: ozzy_core::live::LiveProgress::new(timing.maximum),
113 })
114 }
115
116 pub fn enable_broadcast(&mut self) {
119 self.broadcast = true;
120 self.probes.pace_new_work();
121 }
122
123 pub fn needs_catch_up(&self) -> bool {
126 self.catch_up.is_some_and(|target| {
127 self.sender()
128 .is_some_and(|sender| sender.sent().op < target)
129 })
130 }
131
132 pub const fn catch_up_through(&self) -> Option<OpNumber> {
134 self.catch_up
135 }
136
137 pub fn sender(&self) -> Option<&Sender> {
139 self.sender.as_ref().filter(|_| self.active)
140 }
141
142 pub const fn candidate(&self) -> Option<OpenRequest> {
144 self.candidate
145 }
146
147 pub const fn pending_probe(&self) -> Option<Probe> {
149 self.probes.pending()
150 }
151
152 pub fn replace_session(&mut self, now: Duration) -> Result<(), TransmitError> {
157 self.probes.invalidate(now)?;
158 self.announced_channel = None;
159 self.candidate = None;
160 self.candidate_limit = None;
161 Ok(())
162 }
163
164 pub fn change_scope(&mut self, scope: Scope, now: Duration) -> Result<(), TransmitError> {
167 if scope == self.scope {
168 return Ok(());
169 }
170 self.probes.invalidate(now)?;
171 self.scope = scope;
172 self.active = false;
173 self.announced_channel = None;
174 self.candidate = None;
175 self.candidate_limit = None;
176 self.repair = None;
177 self.catch_up = None;
178 self.receipt_progress.reset();
179 Ok(())
180 }
181
182 pub fn next_probe(&self) -> Option<Duration> {
185 self.repair.is_none().then(|| self.probes.due())
186 }
187
188 pub fn poll_probe(
195 &mut self,
196 available: Prefix,
197 transport_pending: bool,
198 now: Duration,
199 ) -> Result<Option<Probe>, TransmitError> {
200 if transport_pending || self.repair.is_some() {
201 return Ok(None);
202 }
203 let tail = if self.broadcast {
204 available
205 } else {
206 self.sender().map_or(available, Sender::sent)
207 };
208 Ok(self
209 .probes
210 .poll(self.scope, tail, available.op.max(tail.op), now)?)
211 }
212
213 pub fn record_send(&mut self, operations: &[Operation]) -> Result<(), FlowError> {
216 let Some(sender) = self.sender.as_mut().filter(|_| self.active) else {
217 return Err(FlowError::Channel);
218 };
219 if self.repair.is_some()
220 || self.broadcast
221 && self.catch_up.is_some_and(|through| {
222 operations.last().is_some_and(|op| op.prefix.op > through)
223 })
224 {
225 return Err(FlowError::Capacity);
226 }
227 sender.record_send(sender.channel(), operations)
228 }
229
230 pub fn observe(
233 &mut self,
234 report: Report,
235 request_id: Option<RequestId>,
236 now: Duration,
237 ) -> Result<StatusOutcome, TransmitError> {
238 self.observe_with_repair_limit(report, request_id, None, now)
239 }
240
241 pub fn observe_with_repair_limit(
244 &mut self,
245 report: Report,
246 request_id: Option<RequestId>,
247 repair_limit: Option<OpNumber>,
248 now: Duration,
249 ) -> Result<StatusOutcome, TransmitError> {
250 if report.channel.scope != self.scope {
251 return Ok(StatusOutcome::Ignored);
252 }
253 let correlated = if let Some(id) = request_id {
254 let Some(probe) = self.probes.pending().filter(|probe| probe.request_id == id) else {
255 return Ok(StatusOutcome::Ignored);
256 };
257 self.probes.completion_deadline(probe.scope, id, now)?;
258 Some(probe)
259 } else {
260 None
261 };
262 report.validate_shape()?;
263 let request = OpenRequest { request_id, report };
264 if self
265 .sender()
266 .is_none_or(|sender| sender.channel() != report.channel)
267 {
268 if correlated.is_none() {
269 if self.sender().is_some() && self.announced_channel != Some(report.channel) {
273 self.probes.expedite(now)?;
274 self.announced_channel = Some(report.channel);
275 }
276 return Ok(StatusOutcome::Ignored);
277 }
278 return Ok(self.hold(request, repair_limit));
279 }
280 if self.superseded(report) {
281 return Ok(StatusOutcome::Ignored);
282 }
283 if self.broadcast
284 && self
285 .sender()
286 .is_some_and(|sender| report.received.op > sender.sent().op)
287 {
288 return Ok(self.hold(request, repair_limit));
289 }
290 let changed = self.observe_receipt(report, now)?;
291 if let Some(probe) = correlated {
292 self.answer_probe(probe, repair_limit, now)?;
293 }
294 self.trim_repair();
295 Ok(if changed || correlated.is_some() {
296 StatusOutcome::Observed
297 } else {
298 StatusOutcome::Ignored
299 })
300 }
301
302 fn hold(&mut self, request: OpenRequest, repair_limit: Option<OpNumber>) -> StatusOutcome {
305 if self.superseded(request.report) {
306 return StatusOutcome::Ignored;
307 }
308 self.candidate = Some(request);
309 self.candidate_limit = repair_limit;
310 StatusOutcome::Verify
311 }
312
313 fn superseded(&self, report: Report) -> bool {
314 self.candidate.is_some_and(|old| {
315 old.report.channel == report.channel && old.report.revision > report.revision
316 })
317 }
318
319 fn clear_candidate(&mut self) {
320 self.candidate = None;
321 self.candidate_limit = None;
322 }
323
324 fn observe_receipt(&mut self, report: Report, now: Duration) -> Result<bool, FlowError> {
326 let Some(sender) = self.sender.as_mut() else {
327 return Ok(false);
328 };
329 let before = sender.received();
330 let changed = sender.observe(report)?;
331 if sender.received().op > before.op {
332 self.receipt_progress.advanced(sender.received().op.0, now);
333 }
334 Ok(changed)
335 }
336
337 fn answer_probe(
340 &mut self,
341 probe: Probe,
342 repair_limit: Option<OpNumber>,
343 now: Duration,
344 ) -> Result<(), TransmitError> {
345 if let Some(sender) = self.sender.as_ref() {
346 let target = if self.broadcast {
347 repair_limit.unwrap_or(probe.tail.op).min(probe.tail.op)
348 } else {
349 probe.tail.op
350 };
351 if self.broadcast {
352 self.catch_up = (sender.received().op < target).then_some(target);
353 }
354 let through = sender
355 .outstanding()
356 .take_while(|op| op.prefix.op <= target)
357 .last()
358 .map_or(sender.received(), |op| op.prefix);
359 let quiet = !self.broadcast
362 || self
363 .receipt_progress
364 .repair_limit(sender.received().op.0, None, now)
365 .is_none();
366 self.repair = if quiet && sender.received().op < through.op {
367 Some(Repair {
368 channel: sender.channel(),
369 after: sender.received(),
370 through,
371 })
372 } else {
373 None
374 };
375 }
376 self.probes.complete(probe.scope, probe.request_id, now)?;
377 self.clear_candidate();
378 Ok(())
379 }
380
381 pub fn open_verified(
387 &mut self,
388 request: OpenRequest,
389 base: Prefix,
390 received: Prefix,
391 now: Duration,
392 ) -> Result<bool, TransmitError> {
393 if self.candidate != Some(request) {
394 return Ok(false);
395 }
396 let probe = if let Some(id) = request.request_id {
397 let Some(probe) = self.probes.pending().filter(|probe| {
398 probe.scope == request.report.channel.scope && probe.request_id == id
399 }) else {
400 return Ok(false);
401 };
402 self.probes.completion_deadline(probe.scope, id, now)?;
403 Some(probe)
404 } else {
405 if !self.broadcast
406 || self
407 .sender()
408 .is_none_or(|sender| sender.channel() != request.report.channel)
409 {
410 return Ok(false);
411 }
412 None
413 };
414 let report = request.report;
415 if report.base != base || report.received != received {
416 return Err(FlowError::History.into());
417 }
418 report.validate_shape()?;
419 let same_channel = self
420 .sender()
421 .is_some_and(|sender| sender.channel() == report.channel);
422 let previous = self.sender().map(|sender| sender.received().op);
423 if !self.adopt(report, received, same_channel)? {
424 self.clear_candidate();
425 return Ok(false);
426 }
427 if let Some(probe) = probe {
428 self.probes.complete(probe.scope, probe.request_id, now)?;
429 if self.broadcast {
430 let target = self
431 .candidate_limit
432 .unwrap_or(probe.tail.op)
433 .min(probe.tail.op);
434 self.catch_up = (report.received.op < target).then_some(target);
435 }
436 }
437 self.active = true;
438 self.clear_candidate();
439 if !same_channel {
440 self.repair = None;
441 self.receipt_progress.reset();
442 }
443 if !same_channel || previous.is_none_or(|op| received.op > op) {
444 self.receipt_progress.advanced(received.op.0, now);
445 }
446 self.trim_repair();
447 Ok(true)
448 }
449
450 fn adopt(
454 &mut self,
455 report: Report,
456 received: Prefix,
457 same_channel: bool,
458 ) -> Result<bool, FlowError> {
459 match &mut self.sender {
460 Some(sender) if self.broadcast && same_channel => {
461 sender.observe_publication(report, received)
462 }
463 Some(sender) => sender.reopen(report, received).map(|()| true),
464 None => {
465 self.sender = Some(Sender::from_checked_report(
466 report,
467 self.limits,
468 std::mem::take(&mut self.spare),
469 ));
470 Ok(true)
471 }
472 }
473 }
474
475 pub fn repair(&self, transport_pending: bool) -> Option<Repair> {
477 if transport_pending { None } else { self.repair }
478 }
479
480 pub fn record_repair(&mut self, repair: Repair, through: Prefix) -> Result<(), FlowError> {
483 if self.repair != Some(repair) {
484 return Err(FlowError::Channel);
485 }
486 if through.op <= repair.after.op
487 || through.op > repair.through.op
488 || self
489 .sender()
490 .is_none_or(|sender| !sender.outstanding().any(|op| op.prefix == through))
491 {
492 return Err(FlowError::History);
493 }
494 self.repair = if through == repair.through {
495 None
496 } else {
497 Some(Repair {
498 after: through,
499 ..repair
500 })
501 };
502 Ok(())
503 }
504
505 fn trim_repair(&mut self) {
506 if self.catch_up.is_some_and(|target| {
507 self.sender()
508 .is_some_and(|sender| sender.received().op >= target)
509 }) {
510 self.catch_up = None;
511 }
512 let (Some(repair), Some(received)) = (self.repair, self.sender().map(Sender::received))
513 else {
514 return;
515 };
516 self.repair = if received.op >= repair.through.op {
517 None
518 } else if received.op > repair.after.op {
519 Some(Repair {
520 after: received,
521 ..repair
522 })
523 } else {
524 Some(repair)
525 };
526 }
527}