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}