1mod open;
4
5use std::time::Duration;
6
7use crate::OpNumber;
8use ozzy_proto::RequestId;
9
10use super::{
11 Channel, FlowError, Operation, PipelineLimits, Prefix, Probe, ProbeError, ProbeScheduler,
12 ProbeTiming, Report, Scope, Sender, VecDeque, ledger,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct OpenRequest {
21 request_id: Option<RequestId>,
22 report: Report,
23}
24
25impl OpenRequest {
26 pub const fn report(self) -> Report {
28 self.report
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Repair {
35 pub channel: Channel,
37 pub after: Prefix,
39 pub through: Prefix,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum StatusOutcome {
46 Ignored,
48 Observed,
50 Verify,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
56pub enum TransmitError {
57 #[error(transparent)]
59 Flow(#[from] FlowError),
60 #[error(transparent)]
62 Probe(#[from] ProbeError),
63}
64
65#[derive(Debug)]
72pub struct Transmitter {
73 scope: Scope,
74 limits: PipelineLimits,
75 probes: ProbeScheduler,
76 sender: Option<Sender>,
77 announced_channel: Option<Channel>,
78 spare: VecDeque<Operation>,
80 active: bool,
81 candidate: Option<OpenRequest>,
82 candidate_limit: Option<OpNumber>,
83 repair: Option<Repair>,
84 broadcast: bool,
85 catch_up: Option<OpNumber>,
86 receipt_progress: ozzy_core::live::LiveProgress,
87}
88
89impl Transmitter {
90 pub fn new(
93 scope: Scope,
94 limits: PipelineLimits,
95 first: RequestId,
96 timing: ProbeTiming,
97 now: Duration,
98 ) -> Result<Self, TransmitError> {
99 let probes = ProbeScheduler::new(first, timing, now)?;
101 Ok(Self {
102 scope,
103 limits,
104 probes,
105 sender: None,
106 announced_channel: None,
107 spare: ledger(limits)?,
108 active: false,
109 candidate: None,
110 candidate_limit: None,
111 repair: None,
112 broadcast: false,
113 catch_up: None,
114 receipt_progress: ozzy_core::live::LiveProgress::new(timing.maximum),
115 })
116 }
117
118 pub fn enable_broadcast(&mut self) {
121 self.broadcast = true;
122 self.probes.pace_new_work();
123 }
124
125 pub fn needs_catch_up(&self) -> bool {
128 self.catch_up.is_some_and(|target| {
129 self.sender()
130 .is_some_and(|sender| sender.sent().op < target)
131 })
132 }
133
134 pub const fn catch_up_through(&self) -> Option<OpNumber> {
136 self.catch_up
137 }
138
139 pub fn sender(&self) -> Option<&Sender> {
141 self.sender.as_ref().filter(|_| self.active)
142 }
143
144 pub const fn candidate(&self) -> Option<OpenRequest> {
146 self.candidate
147 }
148
149 pub const fn pending_probe(&self) -> Option<Probe> {
151 self.probes.pending()
152 }
153
154 pub fn replace_session(&mut self, now: Duration) -> Result<(), TransmitError> {
159 self.probes.invalidate(now)?;
160 self.announced_channel = None;
161 self.candidate = None;
162 self.candidate_limit = None;
163 Ok(())
164 }
165
166 pub fn change_scope(&mut self, scope: Scope, now: Duration) -> Result<(), TransmitError> {
169 if scope == self.scope {
170 return Ok(());
171 }
172 self.probes.invalidate(now)?;
173 self.scope = scope;
174 self.active = false;
175 self.announced_channel = None;
176 self.candidate = None;
177 self.candidate_limit = None;
178 self.repair = None;
179 self.catch_up = None;
180 self.receipt_progress.reset();
181 Ok(())
182 }
183
184 pub fn next_probe(&self) -> Option<Duration> {
187 self.repair.is_none().then(|| self.probes.due())
188 }
189
190 pub fn poll_probe(
197 &mut self,
198 available: Prefix,
199 transport_pending: bool,
200 now: Duration,
201 ) -> Result<Option<Probe>, TransmitError> {
202 if transport_pending || self.repair.is_some() {
203 return Ok(None);
204 }
205 let tail = if self.broadcast {
206 available
207 } else {
208 self.sender().map_or(available, Sender::sent)
209 };
210 Ok(self
211 .probes
212 .poll(self.scope, tail, available.op.max(tail.op), now)?)
213 }
214
215 pub fn record_send(&mut self, operations: &[Operation]) -> Result<(), FlowError> {
218 let Some(sender) = self.sender.as_mut().filter(|_| self.active) else {
219 return Err(FlowError::Channel);
220 };
221 if self.repair.is_some()
222 || self.broadcast
223 && self.catch_up.is_some_and(|through| {
224 operations.last().is_some_and(|op| op.prefix.op > through)
225 })
226 {
227 return Err(FlowError::Capacity);
228 }
229 sender.record_send(sender.channel(), operations)
230 }
231
232 pub fn observe(
235 &mut self,
236 report: Report,
237 request_id: Option<RequestId>,
238 now: Duration,
239 ) -> Result<StatusOutcome, TransmitError> {
240 self.observe_with_repair_limit(report, request_id, None, now)
241 }
242
243 pub fn observe_with_repair_limit(
246 &mut self,
247 report: Report,
248 request_id: Option<RequestId>,
249 repair_limit: Option<OpNumber>,
250 now: Duration,
251 ) -> Result<StatusOutcome, TransmitError> {
252 if report.channel.scope != self.scope {
253 return Ok(StatusOutcome::Ignored);
254 }
255 let correlated = if let Some(id) = request_id {
256 let Some(probe) = self.probes.pending().filter(|probe| probe.request_id == id) else {
257 return Ok(StatusOutcome::Ignored);
258 };
259 self.probes.completion_deadline(probe.scope, id, now)?;
260 Some(probe)
261 } else {
262 None
263 };
264 report.validate_shape()?;
265 let request = OpenRequest { request_id, report };
266 if self
267 .sender()
268 .is_none_or(|sender| sender.channel() != report.channel)
269 {
270 if correlated.is_none() {
271 if self.sender().is_some() && self.announced_channel != Some(report.channel) {
275 self.probes.expedite(now)?;
276 self.announced_channel = Some(report.channel);
277 }
278 return Ok(StatusOutcome::Ignored);
279 }
280 return Ok(self.hold(request, repair_limit));
281 }
282 if self.superseded(report) {
283 return Ok(StatusOutcome::Ignored);
284 }
285 if self.broadcast
286 && self
287 .sender()
288 .is_some_and(|sender| report.received.op > sender.sent().op)
289 {
290 return Ok(self.hold(request, repair_limit));
291 }
292 let changed = self.observe_receipt(report, now)?;
293 if let Some(probe) = correlated {
294 self.answer_probe(probe, repair_limit, now)?;
295 }
296 self.trim_repair();
297 Ok(if changed || correlated.is_some() {
298 StatusOutcome::Observed
299 } else {
300 StatusOutcome::Ignored
301 })
302 }
303
304 fn hold(&mut self, request: OpenRequest, repair_limit: Option<OpNumber>) -> StatusOutcome {
307 if self.superseded(request.report) {
308 return StatusOutcome::Ignored;
309 }
310 self.candidate = Some(request);
311 self.candidate_limit = repair_limit;
312 StatusOutcome::Verify
313 }
314
315 fn superseded(&self, report: Report) -> bool {
316 self.candidate.is_some_and(|old| {
317 old.report.channel == report.channel && old.report.revision > report.revision
318 })
319 }
320
321 fn clear_candidate(&mut self) {
322 self.candidate = None;
323 self.candidate_limit = None;
324 }
325
326 pub fn forget_candidate(&mut self, request: OpenRequest) {
329 if self.candidate == Some(request) {
330 self.clear_candidate();
331 }
332 }
333
334 fn observe_receipt(&mut self, report: Report, now: Duration) -> Result<bool, FlowError> {
336 let Some(sender) = self.sender.as_mut() else {
337 return Ok(false);
338 };
339 let before = sender.received();
340 let changed = sender.observe(report)?;
341 if sender.received().op > before.op {
342 self.receipt_progress.advanced(sender.received().op.0, now);
343 }
344 Ok(changed)
345 }
346
347 fn answer_probe(
350 &mut self,
351 probe: Probe,
352 repair_limit: Option<OpNumber>,
353 now: Duration,
354 ) -> Result<(), TransmitError> {
355 if let Some(sender) = self.sender.as_ref() {
356 let target = if self.broadcast {
357 repair_limit.unwrap_or(probe.tail.op).min(probe.tail.op)
358 } else {
359 probe.tail.op
360 };
361 if self.broadcast {
362 self.catch_up = (sender.received().op < target).then_some(target);
363 }
364 let through = sender
365 .outstanding()
366 .take_while(|op| op.prefix.op <= target)
367 .last()
368 .map_or(sender.received(), |op| op.prefix);
369 let quiet = !self.broadcast
372 || self
373 .receipt_progress
374 .repair_limit(sender.received().op.0, None, now)
375 .is_none();
376 self.repair = if quiet && sender.received().op < through.op {
377 Some(Repair {
378 channel: sender.channel(),
379 after: sender.received(),
380 through,
381 })
382 } else {
383 None
384 };
385 }
386 self.probes.complete(probe.scope, probe.request_id, now)?;
387 self.clear_candidate();
388 Ok(())
389 }
390
391 pub fn repair(&self, transport_pending: bool) -> Option<Repair> {
393 if transport_pending { None } else { self.repair }
394 }
395
396 pub fn record_repair(&mut self, repair: Repair, through: Prefix) -> Result<(), FlowError> {
399 if self.repair != Some(repair) {
400 return Err(FlowError::Channel);
401 }
402 if through.op <= repair.after.op
403 || through.op > repair.through.op
404 || self
405 .sender()
406 .is_none_or(|sender| !sender.outstanding().any(|op| op.prefix == through))
407 {
408 return Err(FlowError::History);
409 }
410 self.repair = if through == repair.through {
411 None
412 } else {
413 Some(Repair {
414 after: through,
415 ..repair
416 })
417 };
418 Ok(())
419 }
420
421 fn trim_repair(&mut self) {
422 if self.catch_up.is_some_and(|target| {
423 self.sender()
424 .is_some_and(|sender| sender.received().op >= target)
425 }) {
426 self.catch_up = None;
427 }
428 let (Some(repair), Some(received)) = (self.repair, self.sender().map(Sender::received))
429 else {
430 return;
431 };
432 self.repair = if received.op >= repair.through.op {
433 None
434 } else if received.op > repair.after.op {
435 Some(Repair {
436 after: received,
437 ..repair
438 })
439 } else {
440 Some(repair)
441 };
442 }
443}