Skip to main content

mcproto_codec/
io.rs

1//! Counted I/O helpers for protocol codecs.
2//!
3//! These functions provide the completion guarantees of [`Read::read_exact`]
4//! and [`Write::write_all`] while attaching codec metadata and byte progress to
5//! any [`CodecError`] they return.
6
7use std::io::{self, Read, Write};
8
9use crate::error::{CodecError, CodecKind};
10
11/// Fills `buffer` from `reader` and tracks progress for codec errors.
12///
13/// `codec` identifies the codec performing the read. `bytes_processed` is the
14/// number of bytes that codec processed before this buffer; bytes read into
15/// `buffer` are added to that base when an error is reported.
16///
17/// Operations interrupted with [`io::ErrorKind::Interrupted`] are retried. If
18/// the reader reaches the end of its input before filling `buffer`, the result
19/// is a [`CodecErrorKind::UnexpectedEof`] error.
20///
21/// # Example
22///
23/// ```
24/// use mcproto_codec::{
25///     error::CodecKind,
26///     io::read_exact_counted,
27/// };
28///
29/// let mut input = [0x12, 0x34].as_slice();
30/// let mut bytes = [0; 2];
31/// read_exact_counted(&mut input, &mut bytes, CodecKind::Short, 0)?;
32/// assert_eq!(bytes, [0x12, 0x34]);
33///
34/// # Ok::<(), mcproto_codec::error::CodecError>(())
35/// ```
36///
37/// # Errors
38///
39/// Returns a [`CodecError`] if the reader reaches an unexpected end of input
40/// or reports another I/O error.
41///
42/// [`CodecErrorKind::UnexpectedEof`]: crate::error::CodecErrorKind::UnexpectedEof
43#[inline]
44pub fn read_exact_counted<R: Read + ?Sized>(
45    reader: &mut R,
46    buffer: &mut [u8],
47    codec: CodecKind,
48    bytes_processed: usize,
49) -> Result<(), CodecError> {
50    let mut current = 0;
51
52    while current < buffer.len() {
53        match reader.read(&mut buffer[current..]) {
54            Ok(0) => {
55                let error = io::Error::new(
56                    io::ErrorKind::UnexpectedEof,
57                    "failed to fill the whole buffer",
58                );
59                return Err(CodecError::from_read_error(
60                    codec,
61                    bytes_processed + current,
62                    error,
63                ));
64            }
65            Ok(read) => current += read,
66            Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
67            Err(error) => {
68                return Err(CodecError::from_read_error(
69                    codec,
70                    bytes_processed + current,
71                    error,
72                ));
73            }
74        }
75    }
76
77    Ok(())
78}
79
80/// Writes all of `buffer` to `writer` and tracks progress for codec errors.
81///
82/// `codec` identifies the codec performing the write. `bytes_processed` is the
83/// number of bytes that codec processed before this buffer; bytes written from
84/// `buffer` are added to that base when an error is reported.
85///
86/// Operations interrupted with [`io::ErrorKind::Interrupted`] are retried. A
87/// successful write of zero bytes while data remains is reported as an
88/// [`io::ErrorKind::WriteZero`] source error.
89///
90/// # Example
91///
92/// ```
93/// use mcproto_codec::{
94///     error::CodecKind,
95///     io::write_all_counted,
96/// };
97///
98/// let mut output = Vec::new();
99/// write_all_counted(&mut output, &[0x12, 0x34], CodecKind::Short, 0)?;
100/// assert_eq!(output, [0x12, 0x34]);
101///
102/// # Ok::<(), mcproto_codec::error::CodecError>(())
103/// ```
104///
105/// # Errors
106///
107/// Returns a [`CodecError`] if the writer cannot accept the complete buffer.
108#[inline]
109pub fn write_all_counted<W: Write + ?Sized>(
110    writer: &mut W,
111    buffer: &[u8],
112    codec: CodecKind,
113    bytes_processed: usize,
114) -> Result<(), CodecError> {
115    let mut current = 0;
116
117    while current < buffer.len() {
118        match writer.write(&buffer[current..]) {
119            Ok(0) => {
120                let error =
121                    io::Error::new(io::ErrorKind::WriteZero, "failed to write the whole buffer");
122                return Err(CodecError::from_write_error(
123                    codec,
124                    bytes_processed + current,
125                    error,
126                ));
127            }
128            Ok(written) => current += written,
129            Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
130            Err(error) => {
131                return Err(CodecError::from_write_error(
132                    codec,
133                    bytes_processed + current,
134                    error,
135                ));
136            }
137        }
138    }
139
140    Ok(())
141}