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) {
118 self.broadcast = true;
119 }
120
121 pub fn needs_catch_up(&self) -> bool {
124 self.catch_up.is_some_and(|target| {
125 self.sender()
126 .is_some_and(|sender| sender.sent().op < target)
127 })
128 }
129
130 pub const fn catch_up_through(&self) -> Option<OpNumber> {
132 self.catch_up
133 }
134
135 pub fn sender(&self) -> Option<&Sender> {
137 self.sender.as_ref().filter(|_| self.active)
138 }
139
140 pub const fn candidate(&self) -> Option<OpenRequest> {
142 self.candidate
143 }
144
145 pub const fn pending_probe(&self) -> Option<Probe> {
147 self.probes.pending()
148 }
149
150 pub fn replace_session(&mut self, now: Duration) -> Result<(), TransmitError> {
155 self.probes.invalidate(now)?;
156 self.announced_channel = None;
157 self.candidate = None;
158 self.candidate_limit = None;
159 Ok(())
160 }
161
162 pub fn change_scope(&mut self, scope: Scope, now: Duration) -> Result<(), TransmitError> {
165 if scope == self.scope {
166 return Ok(());
167 }
168 self.probes.invalidate(now)?;
169 self.scope = scope;
170 self.active = false;
171 self.announced_channel = None;
172 self.candidate = None;
173 self.candidate_limit = None;
174 self.repair = None;
175 self.catch_up = None;
176 self.receipt_progress.reset();
177 Ok(())
178 }
179
180 pub fn poll_probe(
187 &mut self,
188 available: Prefix,
189 transport_pending: bool,
190 now: Duration,
191 ) -> Result<Option<Probe>, TransmitError> {
192 if transport_pending || self.repair.is_some() {
193 return Ok(None);
194 }
195 let tail = if self.broadcast {
196 available
197 } else {
198 self.sender().map_or(available, Sender::sent)
199 };
200 Ok(self
201 .probes
202 .poll(self.scope, tail, available.op.max(tail.op), now)?)
203 }
204
205 pub fn record_send(&mut self, operations: &[Operation]) -> Result<(), FlowError> {
208 if !self.active {
209 return Err(FlowError::Channel);
210 }
211 if self.repair.is_some()
212 || self.broadcast
213 && self.catch_up.is_some_and(|through| {
214 operations.last().is_some_and(|op| op.prefix.op > through)
215 })
216 {
217 return Err(FlowError::Capacity);
218 }
219 let sender = self.sender.as_mut().expect("active ledger");
220 sender.record_send(sender.channel(), operations)
221 }
222
223 pub fn observe(
226 &mut self,
227 report: Report,
228 request_id: Option<RequestId>,
229 now: Duration,
230 ) -> Result<StatusOutcome, TransmitError> {
231 self.observe_with_repair_limit(report, request_id, None, now)
232 }
233
234 pub fn observe_with_repair_limit(
237 &mut self,
238 report: Report,
239 request_id: Option<RequestId>,
240 repair_limit: Option<OpNumber>,
241 now: Duration,
242 ) -> Result<StatusOutcome, TransmitError> {
243 if report.channel.scope != self.scope {
244 return Ok(StatusOutcome::Ignored);
245 }
246 let correlated = if let Some(id) = request_id {
247 let Some(probe) = self.probes.pending().filter(|probe| probe.request_id == id) else {
248 return Ok(StatusOutcome::Ignored);
249 };
250 self.probes.completion_deadline(probe.scope, id, now)?;
251 Some(probe)
252 } else {
253 None
254 };
255 report.validate_shape()?;
256 if self
257 .sender()
258 .is_some_and(|sender| sender.channel() == report.channel)
259 {
260 if self.candidate.is_some_and(|old| {
261 old.report.channel == report.channel && old.report.revision > report.revision
262 }) {
263 return Ok(StatusOutcome::Ignored);
264 }
265 if self.broadcast && report.received.op > self.sender().expect("active").sent().op {
266 self.candidate = Some(OpenRequest { request_id, report });
267 self.candidate_limit = repair_limit;
268 return Ok(StatusOutcome::Verify);
269 }
270 let sender = self.sender.as_mut().expect("active ledger");
271 let before = sender.received();
272 let changed = sender.observe(report)?;
273 if sender.received().op > before.op {
274 self.receipt_progress.advanced(sender.received().op.0, now);
275 }
276 if let Some(probe) = correlated {
277 let target = if self.broadcast {
278 repair_limit.unwrap_or(probe.tail.op).min(probe.tail.op)
279 } else {
280 probe.tail.op
281 };
282 if self.broadcast {
283 self.catch_up = (sender.received().op < target).then_some(target);
284 }
285 let through = sender
286 .outstanding()
287 .take_while(|op| op.prefix.op <= target)
288 .last()
289 .map_or(sender.received(), |op| op.prefix);
290 let quiet = !self.broadcast
293 || self
294 .receipt_progress
295 .repair_limit(sender.received().op.0, None, now)
296 .is_none();
297 self.repair = if quiet && sender.received().op < through.op {
298 Some(Repair {
299 channel: sender.channel(),
300 after: sender.received(),
301 through,
302 })
303 } else {
304 None
305 };
306 self.probes.complete(probe.scope, probe.request_id, now)?;
307 self.candidate = None;
308 self.candidate_limit = None;
309 }
310 self.trim_repair();
311 return Ok(if changed || correlated.is_some() {
312 StatusOutcome::Observed
313 } else {
314 StatusOutcome::Ignored
315 });
316 }
317 let Some(probe) = correlated else {
318 if self.sender().is_some() && self.announced_channel != Some(report.channel) {
322 self.probes.expedite(now)?;
323 self.announced_channel = Some(report.channel);
324 }
325 return Ok(StatusOutcome::Ignored);
326 };
327 if self.candidate.is_some_and(|old| {
328 old.report.channel == report.channel && old.report.revision > report.revision
329 }) {
330 return Ok(StatusOutcome::Ignored);
331 }
332 let candidate = OpenRequest {
333 request_id: Some(probe.request_id),
334 report,
335 };
336 self.candidate = Some(candidate);
337 self.candidate_limit = repair_limit;
338 Ok(StatusOutcome::Verify)
339 }
340
341 pub fn open_verified(
347 &mut self,
348 request: OpenRequest,
349 base: Prefix,
350 received: Prefix,
351 now: Duration,
352 ) -> Result<bool, TransmitError> {
353 if self.candidate != Some(request) {
354 return Ok(false);
355 }
356 let probe = if let Some(id) = request.request_id {
357 let Some(probe) = self.probes.pending().filter(|probe| {
358 probe.scope == request.report.channel.scope && probe.request_id == id
359 }) else {
360 return Ok(false);
361 };
362 self.probes.completion_deadline(probe.scope, id, now)?;
363 Some(probe)
364 } else {
365 if !self.broadcast
366 || self
367 .sender()
368 .is_none_or(|sender| sender.channel() != request.report.channel)
369 {
370 return Ok(false);
371 }
372 None
373 };
374 let report = request.report;
375 if report.base != base || report.received != received {
376 return Err(FlowError::History.into());
377 }
378 report.validate_shape()?;
379 let same_channel = self
380 .sender()
381 .is_some_and(|sender| sender.channel() == report.channel);
382 let previous = self.sender().map(|sender| sender.received().op);
383 if let Some(sender) = &mut self.sender {
384 if self.broadcast && same_channel {
385 if !sender.observe_publication(report, received)? {
386 self.candidate = None;
387 self.candidate_limit = None;
388 return Ok(false);
389 }
390 } else {
391 sender.reopen(report, received)?;
392 }
393 } else {
394 self.sender = Some(Sender::from_checked_report(
395 report,
396 self.limits,
397 std::mem::take(&mut self.spare),
398 ));
399 }
400 if let Some(probe) = probe {
401 self.probes.complete(probe.scope, probe.request_id, now)?;
402 if self.broadcast {
403 let target = self
404 .candidate_limit
405 .unwrap_or(probe.tail.op)
406 .min(probe.tail.op);
407 self.catch_up = (report.received.op < target).then_some(target);
408 }
409 }
410 self.active = true;
411 self.candidate = None;
412 self.candidate_limit = None;
413 if !same_channel {
414 self.repair = None;
415 self.receipt_progress.reset();
416 }
417 if !same_channel || previous.is_none_or(|op| received.op > op) {
418 self.receipt_progress.advanced(received.op.0, now);
419 }
420 self.trim_repair();
421 Ok(true)
422 }
423
424 pub fn repair(&self, transport_pending: bool) -> Option<Repair> {
426 if transport_pending { None } else { self.repair }
427 }
428
429 pub fn record_repair(&mut self, repair: Repair, through: Prefix) -> Result<(), FlowError> {
432 if self.repair != Some(repair) {
433 return Err(FlowError::Channel);
434 }
435 if through.op <= repair.after.op
436 || through.op > repair.through.op
437 || self
438 .sender()
439 .is_none_or(|sender| !sender.outstanding().any(|op| op.prefix == through))
440 {
441 return Err(FlowError::History);
442 }
443 self.repair = if through == repair.through {
444 None
445 } else {
446 Some(Repair {
447 after: through,
448 ..repair
449 })
450 };
451 Ok(())
452 }
453
454 fn trim_repair(&mut self) {
455 if self.catch_up.is_some_and(|target| {
456 self.sender()
457 .is_some_and(|sender| sender.received().op >= target)
458 }) {
459 self.catch_up = None;
460 }
461 let Some(repair) = self.repair else {
462 return;
463 };
464 let received = self
465 .sender()
466 .expect("repair has an active ledger")
467 .received();
468 self.repair = if received.op >= repair.through.op {
469 None
470 } else if received.op > repair.after.op {
471 Some(Repair {
472 after: received,
473 ..repair
474 })
475 } else {
476 Some(repair)
477 };
478 }
479}