netget/client/
response_reader.rs1use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader};
4
5use crate::utils::line_reader::{read_bounded_line, BoundedLine};
6
7pub const MAX_RESPONSE_LINE_BYTES: usize = 64 * 1024;
9pub const MAX_MULTILINE_BYTES: usize = 8 * 1024 * 1024;
11pub const RESPONSE_DEADLINE: std::time::Duration = std::time::Duration::from_secs(30);
13
14pub async fn read_response_line<R: AsyncRead + Unpin>(
18 reader: &mut BufReader<R>,
19 line: &mut String,
20) -> std::io::Result<usize> {
21 read_response_line_with_timeout(reader, line, RESPONSE_DEADLINE).await
22}
23
24pub async fn read_response_line_with_timeout<R: AsyncRead + Unpin>(
26 reader: &mut BufReader<R>,
27 line: &mut String,
28 deadline: std::time::Duration,
29) -> std::io::Result<usize> {
30 line.clear();
31 if reader.fill_buf().await?.is_empty() {
33 return Ok(0);
34 }
35 let result = tokio::time::timeout(deadline, read_bounded_line(reader, MAX_RESPONSE_LINE_BYTES))
36 .await
37 .map_err(|_| {
38 std::io::Error::new(
39 std::io::ErrorKind::TimedOut,
40 "response line deadline exceeded",
41 )
42 })??;
43 match result {
44 (BoundedLine::Line(value), consumed) => {
45 *line = value;
46 Ok(consumed)
47 }
48 (BoundedLine::Eof, 0) => Ok(0),
49 (BoundedLine::Eof, _) => Err(std::io::Error::new(
50 std::io::ErrorKind::UnexpectedEof,
51 "server closed midway through a response line",
52 )),
53 (BoundedLine::TooLong, _) => Err(std::io::Error::new(
54 std::io::ErrorKind::InvalidData,
55 format!("response line exceeds the {MAX_RESPONSE_LINE_BYTES}-byte cap"),
56 )),
57 }
58}
59
60pub async fn read_expected_response_line<R: AsyncRead + Unpin>(
62 reader: &mut BufReader<R>,
63 line: &mut String,
64) -> std::io::Result<usize> {
65 tokio::time::timeout(RESPONSE_DEADLINE, read_response_line(reader, line))
66 .await
67 .map_err(|_| {
68 std::io::Error::new(
69 std::io::ErrorKind::TimedOut,
70 "expected response deadline exceeded",
71 )
72 })?
73}
74
75pub async fn read_dot_response<R: AsyncRead + Unpin>(
78 reader: &mut BufReader<R>,
79 response: String,
80) -> std::io::Result<String> {
81 tokio::time::timeout(RESPONSE_DEADLINE, read_dot_response_inner(reader, response))
82 .await
83 .map_err(|_| {
84 std::io::Error::new(
85 std::io::ErrorKind::TimedOut,
86 "multiline response deadline exceeded",
87 )
88 })?
89}
90
91async fn read_dot_response_inner<R: AsyncRead + Unpin>(
92 reader: &mut BufReader<R>,
93 mut response: String,
94) -> std::io::Result<String> {
95 if response.len() > MAX_MULTILINE_BYTES {
96 return Err(std::io::Error::new(
97 std::io::ErrorKind::InvalidData,
98 "multiline response exceeds the byte cap",
99 ));
100 }
101 let mut line = String::new();
102 loop {
103 if read_response_line(reader, &mut line).await? == 0 {
104 return Err(std::io::Error::new(
105 std::io::ErrorKind::UnexpectedEof,
106 "server closed before the multiline response terminator",
107 ));
108 }
109 let line = line.strip_suffix('\n').unwrap_or(&line);
110 let line = line.strip_suffix('\r').unwrap_or(line);
111 if line == "." {
112 return Ok(response);
113 }
114 let line = if line.starts_with("..") {
115 &line[1..]
116 } else {
117 line
118 };
119 if line.len() + 1 > MAX_MULTILINE_BYTES.saturating_sub(response.len()) {
120 return Err(std::io::Error::new(
121 std::io::ErrorKind::InvalidData,
122 format!("multiline response exceeds the {MAX_MULTILINE_BYTES}-byte cap"),
123 ));
124 }
125 response.push('\n');
126 response.push_str(line);
127 }
128}
129
130pub struct FrameReader<R> {
133 inner: R,
134 deadline: Option<std::pin::Pin<Box<tokio::time::Sleep>>>,
135}
136impl<R> FrameReader<R> {
137 pub fn new(inner: R) -> Self {
138 Self {
139 inner,
140 deadline: None,
141 }
142 }
143 pub fn start_frame(&mut self, duration: std::time::Duration) {
144 self.deadline = Some(Box::pin(tokio::time::sleep(duration)));
145 }
146 pub fn end_frame(&mut self) {
147 self.deadline = None;
148 }
149}
150impl<R: AsyncRead + Unpin> AsyncRead for FrameReader<R> {
151 fn poll_read(
152 self: std::pin::Pin<&mut Self>,
153 cx: &mut std::task::Context<'_>,
154 buf: &mut tokio::io::ReadBuf<'_>,
155 ) -> std::task::Poll<std::io::Result<()>> {
156 use std::future::Future;
157 let this = self.get_mut();
158 if this
159 .deadline
160 .as_mut()
161 .is_some_and(|deadline| deadline.as_mut().poll(cx).is_ready())
162 {
163 return std::task::Poll::Ready(Err(std::io::Error::new(
164 std::io::ErrorKind::TimedOut,
165 "partial response frame deadline exceeded",
166 )));
167 }
168 std::pin::Pin::new(&mut this.inner).poll_read(cx, buf)
169 }
170}