1use super::control::{LineCursor, next_line};
20use super::encoded_stream::{Incoming, Outgoing, install};
21use super::framing::{IltpError, IltpItem, write_preface};
22use super::stream_encoding::{ENCODE_MIN_OUTPUT, EncodeMode, EncodeProgress};
23use crate::records::Record;
24
25const ENCODING_SLACK: usize = 64;
28
29pub(super) struct Output {
30 chunk_bytes: usize,
31 chunk: Vec<u8>,
33 filled: usize,
34 taken: usize,
36 in_place: Option<Record>,
38 line: Vec<u8>,
40 read: Vec<u8>,
42 read_used: usize,
43 read_state: ReadState,
44 batch: Option<Batch>,
45}
46
47enum ReadState {
48 Idle,
49 Requested(RecordRead),
51 Outstanding,
53}
54
55#[derive(Debug)]
57pub enum OutputBytes<'a> {
58 Write(&'a [u8]),
60 Read(RecordRead),
64 Complete,
66}
67
68#[derive(Debug)]
70pub struct RecordRead {
71 record: Record,
72 offset: usize,
73 buffer: Vec<u8>,
74}
75
76impl RecordRead {
77 pub fn run(mut self) -> std::io::Result<Self> {
79 let mut filled = 0;
80 while filled < self.buffer.len() {
81 match self
82 .record
83 .read_at(self.offset + filled, &mut self.buffer[filled..])?
84 {
85 0 => return Err(std::io::ErrorKind::UnexpectedEof.into()),
86 count => filled += count,
87 }
88 }
89 Ok(self)
90 }
91}
92
93struct Batch {
94 items: Vec<IltpItem>,
95 plaintext: usize,
97 step: Step,
98 offset: usize,
100 flush: bool,
102}
103
104#[derive(Debug, Clone, Copy)]
105enum Step {
106 Item(usize),
108 EndBeforePreface(usize),
110 Preface(usize),
112 Line(usize, LineCursor),
114 Record(usize),
115 RecordEnd(usize),
117 Flush,
118 End,
119 Done,
120}
121
122impl Output {
123 pub(super) fn new(chunk_bytes: usize) -> Self {
124 Self {
125 chunk_bytes,
126 chunk: Vec::new(),
127 filled: 0,
128 taken: 0,
129 in_place: None,
130 line: Vec::new(),
131 read: Vec::new(),
132 read_used: 0,
133 read_state: ReadState::Idle,
134 batch: None,
135 }
136 }
137
138 #[cfg(test)]
140 pub(super) fn chunk_len(&self) -> usize {
141 self.chunk.len()
142 }
143
144 pub(super) fn is_pending(&self) -> bool {
145 self.batch.is_some()
146 }
147
148 pub(super) fn accept(&mut self, items: Vec<IltpItem>, plaintext: usize) {
150 self.start(items, plaintext, Step::Item(0));
151 }
152
153 pub(super) fn accept_end(&mut self) {
155 self.start(Vec::new(), 0, Step::End);
156 }
157
158 fn start(&mut self, items: Vec<IltpItem>, plaintext: usize, step: Step) {
159 debug_assert!(self.batch.is_none());
160 self.batch = Some(Batch {
161 items,
162 plaintext,
163 step,
164 offset: 0,
165 flush: false,
166 });
167 }
168
169 pub(super) fn discard(&mut self) {
170 self.batch = None;
171 self.in_place = None;
172 self.filled = 0;
173 self.taken = 0;
174 self.read.clear();
175 self.read_used = 0;
176 self.read_state = ReadState::Idle;
177 }
178
179 pub(super) fn bytes(
182 &mut self,
183 outgoing: &mut Outgoing,
184 incoming: &mut Incoming,
185 ) -> Result<OutputBytes<'_>, IltpError> {
186 if matches!(self.read_state, ReadState::Outstanding) {
187 return Err(IltpError::Local("ILTP output read is outstanding"));
188 }
189 if self.in_place.is_none()
190 && self.taken == self.filled
191 && matches!(self.read_state, ReadState::Idle)
192 {
193 self.fill(outgoing, incoming)?;
194 }
195 if let Some(record) = &self.in_place {
196 let bytes = record
197 .contiguous_bytes()
198 .ok_or(IltpError::Local("in-place record is not contiguous"))?;
199 let rest = &bytes[self.taken..];
200 return Ok(OutputBytes::Write(
201 &rest[..rest.len().min(self.chunk_bytes)],
202 ));
203 }
204 if self.taken < self.filled {
205 return Ok(OutputBytes::Write(&self.chunk[self.taken..self.filled]));
206 }
207 Ok(
208 match std::mem::replace(&mut self.read_state, ReadState::Outstanding) {
209 ReadState::Requested(read) => OutputBytes::Read(read),
210 _ => {
211 self.read_state = ReadState::Idle;
212 OutputBytes::Complete
213 }
214 },
215 )
216 }
217
218 pub(super) fn complete_read(&mut self, read: RecordRead) -> Result<(), IltpError> {
220 let expected = match &self.batch {
221 Some(Batch {
222 step: Step::Record(_),
223 offset,
224 ..
225 }) => *offset,
226 None => return Ok(()),
228 _ => return Err(IltpError::Local("ILTP output read without its record")),
229 };
230 if !matches!(self.read_state, ReadState::Outstanding) || read.offset != expected {
231 return Err(IltpError::Local(
232 "ILTP output read does not match its request",
233 ));
234 }
235 self.read = read.buffer;
236 self.read_used = 0;
237 self.read_state = ReadState::Idle;
238 Ok(())
239 }
240
241 pub(super) fn advance(&mut self, count: usize) {
243 self.taken += count;
244 match &self.in_place {
245 Some(record) if self.taken >= record.canonical_len() => {
246 self.in_place = None;
247 self.taken = 0;
248 self.filled = 0;
249 }
250 Some(_) => {}
251 None => debug_assert!(self.taken <= self.filled),
252 }
253 self.finish_when_taken();
254 }
255
256 fn finish_when_taken(&mut self) {
257 if self.in_place.is_none()
258 && self.taken == self.filled
259 && matches!(&self.batch, Some(batch) if matches!(batch.step, Step::Done))
260 {
261 self.batch = None;
262 }
263 }
264
265 fn fill(&mut self, outgoing: &mut Outgoing, incoming: &mut Incoming) -> Result<(), IltpError> {
267 self.filled = 0;
268 self.taken = 0;
269 let Self {
270 chunk_bytes,
271 chunk,
272 filled,
273 taken,
274 in_place,
275 line,
276 read,
277 read_used,
278 read_state,
279 batch,
280 } = self;
281 let Some(batch) = batch else {
282 return Ok(());
283 };
284 let useful = batch
285 .plaintext
286 .saturating_add(ENCODING_SLACK)
287 .min(*chunk_bytes);
288 if chunk.len() < useful {
289 chunk.resize(useful, 0);
290 }
291 loop {
292 match batch.step {
293 Step::Item(index) => {
294 batch.offset = 0;
295 batch.step = match batch.items.get(index) {
296 None if batch.flush => Step::Flush,
297 None => Step::Done,
298 Some(IltpItem::SessionPreface(preface)) => {
299 line.clear();
300 write_preface(preface, line);
301 Step::EndBeforePreface(index)
302 }
303 Some(item) => {
304 batch.flush = true;
305 let mut cursor = LineCursor::default();
306 next_line(item, &mut cursor, line);
307 Step::Line(index, cursor)
308 }
309 };
310 }
311 Step::EndBeforePreface(index) => {
312 match encode_step(outgoing, chunk, filled, &[], EncodeMode::End)? {
313 Some(progress) if progress.done => batch.step = Step::Preface(index),
314 Some(_) => {}
315 None => break,
316 }
317 }
318 Step::Preface(index) => {
319 let count = copy(&line[batch.offset..], &mut chunk[*filled..]);
320 if count == 0 {
321 break;
322 }
323 *filled += count;
324 batch.offset += count;
325 if batch.offset == line.len() {
326 let IltpItem::SessionPreface(preface) = &batch.items[index] else {
327 unreachable!("preface step on another item");
328 };
329 *outgoing = Outgoing::Pending(preface.offers.clone());
330 if let Incoming::Pending(peer) = incoming {
331 let peer = peer.clone();
332 install(outgoing, incoming, &preface.offers, &peer, *chunk_bytes)?;
333 }
334 batch.flush = false;
335 batch.step = Step::Item(index + 1);
336 }
337 }
338 Step::Line(index, mut cursor) => {
339 let input = &line[batch.offset..];
340 match encode_step(outgoing, chunk, filled, input, EncodeMode::Write)? {
341 Some(progress) => batch.offset += progress.consumed,
342 None => break,
343 }
344 if batch.offset == line.len() {
345 batch.offset = 0;
346 let item = &batch.items[index];
347 batch.step = if next_line(item, &mut cursor, line) {
348 Step::Line(index, cursor)
349 } else if matches!(item, IltpItem::Record(..)) {
350 Step::Record(index)
351 } else {
352 Step::Item(index + 1)
353 };
354 }
355 }
356 Step::Record(index) => {
357 let IltpItem::Record(_, record) = &batch.items[index] else {
358 unreachable!("record step on another item");
359 };
360 let Some(bytes) = record.contiguous_bytes() else {
361 let rest = record.canonical_len() - batch.offset;
362 if rest == 0 {
363 batch.offset = 0;
364 batch.step = Step::RecordEnd(index);
365 } else if *read_used < read.len() {
366 let input = &read[*read_used..];
367 match encode_step(outgoing, chunk, filled, input, EncodeMode::Write)? {
368 Some(progress) => {
369 batch.offset += progress.consumed;
370 *read_used += progress.consumed;
371 }
372 None => break,
373 }
374 } else {
375 let mut buffer = std::mem::take(read);
376 buffer.clear();
377 buffer.resize(rest.min(*chunk_bytes), 0);
378 *read_used = 0;
379 *read_state = ReadState::Requested(RecordRead {
380 record: record.clone(),
381 offset: batch.offset,
382 buffer,
383 });
384 break;
385 }
386 continue;
387 };
388 let rest = &bytes[batch.offset..];
389 if rest.is_empty() {
390 batch.offset = 0;
391 batch.step = Step::RecordEnd(index);
392 } else if outgoing.is_identity() && rest.len() > chunk.len() - *filled {
393 if *filled > 0 {
394 break;
395 }
396 *taken = batch.offset;
397 *in_place = Some(record.clone());
398 batch.offset = 0;
399 batch.step = Step::RecordEnd(index);
400 return Ok(());
401 } else {
402 match encode_step(outgoing, chunk, filled, rest, EncodeMode::Write)? {
403 Some(progress) => batch.offset += progress.consumed,
404 None => break,
405 }
406 }
407 }
408 Step::RecordEnd(index) => {
409 match encode_step(outgoing, chunk, filled, b"\n", EncodeMode::Write)? {
410 Some(progress) if progress.consumed == 1 => {
411 batch.step = Step::Item(index + 1);
412 }
413 Some(_) => {}
414 None => break,
415 }
416 }
417 Step::Flush | Step::End => {
418 let mode = match batch.step {
419 Step::Flush => EncodeMode::Flush,
420 _ => EncodeMode::End,
421 };
422 match encode_step(outgoing, chunk, filled, &[], mode)? {
423 Some(progress) if progress.done => batch.step = Step::Done,
424 Some(_) => {}
425 None => break,
426 }
427 }
428 Step::Done => break,
429 }
430 }
431 self.finish_when_taken();
432 Ok(())
433 }
434}
435
436fn copy(input: &[u8], room: &mut [u8]) -> usize {
437 let count = input.len().min(room.len());
438 room[..count].copy_from_slice(&input[..count]);
439 count
440}
441
442fn encode_step(
445 outgoing: &mut Outgoing,
446 chunk: &mut [u8],
447 filled: &mut usize,
448 input: &[u8],
449 mode: EncodeMode,
450) -> Result<Option<EncodeProgress>, IltpError> {
451 let room = &mut chunk[*filled..];
452 if room.len() < ENCODE_MIN_OUTPUT {
453 return Ok(None);
454 }
455 let progress = outgoing.encode(input, mode, room)?;
456 *filled += progress.produced;
457 if !progress.done && progress.consumed == 0 && progress.produced == 0 {
458 return if *filled > 0 {
459 Ok(None)
460 } else {
461 Err(IltpError::Local("ILTP stream encoder made no progress"))
462 };
463 }
464 Ok(Some(progress))
465}