Skip to main content

watermelon_proto/proto/decoder/
mod.rs

1use core::{mem, ops::Deref};
2
3use bytes::{Buf, Bytes, BytesMut};
4use bytestring::ByteString;
5
6use crate::{
7    MessageBase, ServerMessage, StatusCode, Subject, SubscriptionId,
8    error::ServerError,
9    headers::{
10        HeaderMap, HeaderName, HeaderValue,
11        error::{HeaderNameValidateError, HeaderValueValidateError},
12    },
13    status_code::StatusCodeError,
14    util::{self, CrlfFinder, ParseUintError},
15};
16
17pub use self::framed::{FrameDecoderError, decode_frame};
18pub use self::stream::StreamDecoder;
19
20use super::ServerOp;
21
22mod framed;
23mod stream;
24
25// Sanity limit for the length of `MSG`/`HMSG`/`INFO`/`-ERR` lines coming
26// from the server. The server's `max_control_line` (4096 by default) only
27// applies to lines sent by clients: routed/leafnode/gateway connections are
28// allowed arg lines of up to 16x that (64 KiB) and the server may grow
29// subjects further by rewriting them (e.g. `$JS.ACK` reply subjects and
30// gateway reply prefixes), so be generous here.
31const MAX_HEAD_LEN: usize = 128 * 1024;
32
33#[derive(Debug)]
34pub(super) enum DecoderStatus {
35    ControlLine {
36        last_bytes_read: usize,
37    },
38    Headers {
39        subscription_id: SubscriptionId,
40        subject: Subject,
41        reply_subject: Option<Subject>,
42        header_len: usize,
43        payload_len: usize,
44    },
45    Payload {
46        subscription_id: SubscriptionId,
47        subject: Subject,
48        reply_subject: Option<Subject>,
49        status_code: Option<StatusCode>,
50        status_description: Option<ByteString>,
51        headers: HeaderMap,
52        payload_len: usize,
53    },
54    Poisoned,
55}
56
57pub(super) trait BytesLike: Buf + Deref<Target = [u8]> {
58    fn len(&self) -> usize {
59        Buf::remaining(self)
60    }
61
62    fn split_to(&mut self, at: usize) -> Bytes {
63        self.copy_to_bytes(at)
64    }
65}
66
67impl BytesLike for Bytes {}
68impl BytesLike for BytesMut {}
69
70pub(super) fn decode(
71    crlf: &CrlfFinder,
72    status: &mut DecoderStatus,
73    read_buf: &mut impl BytesLike,
74) -> Result<Option<ServerOp>, DecoderError> {
75    loop {
76        match status {
77            DecoderStatus::ControlLine { last_bytes_read } => {
78                if read_buf.starts_with(b"+OK\r\n") {
79                    // Fast path for handling `+OK`
80                    debug_assert_eq!(
81                        *last_bytes_read, 0,
82                        "we shouldn't have handled any bytes before"
83                    );
84                    read_buf.advance("+OK\r\n".len());
85                    return Ok(Some(ServerOp::Success));
86                }
87
88                if *last_bytes_read == read_buf.len() {
89                    // No progress has been made
90                    return Ok(None);
91                }
92
93                let Some(control_line_len) = crlf.find(read_buf) else {
94                    return if read_buf.len() < MAX_HEAD_LEN {
95                        *last_bytes_read = read_buf.len();
96                        Ok(None)
97                    } else {
98                        Err(DecoderError::HeadTooLong {
99                            len: read_buf.len(),
100                        })
101                    };
102                };
103
104                let mut control_line = read_buf.split_to(control_line_len + "\r\n".len());
105                control_line.truncate(control_line.len() - 2);
106
107                return if control_line.starts_with(b"MSG ") {
108                    *status = decode_msg(control_line)?;
109                    continue;
110                } else if control_line.starts_with(b"HMSG ") {
111                    *status = decode_hmsg(control_line)?;
112                    continue;
113                } else if control_line.starts_with(b"PING") {
114                    Ok(Some(ServerOp::Ping))
115                } else if control_line.starts_with(b"PONG") {
116                    Ok(Some(ServerOp::Pong))
117                } else if control_line.starts_with(b"+OK") {
118                    // Slow path for handling `+OK`
119                    Ok(Some(ServerOp::Success))
120                } else if control_line.starts_with(b"-ERR ") {
121                    control_line.advance("-ERR ".len());
122                    if !control_line.starts_with(b"'") || !control_line.ends_with(b"'") {
123                        return Err(DecoderError::InvalidErrorMessage);
124                    }
125
126                    control_line.advance(1);
127                    control_line.truncate(control_line.len() - 1);
128                    let raw_message = ByteString::try_from(control_line)
129                        .map_err(|_| DecoderError::InvalidErrorMessage)?;
130                    let error = ServerError::parse(raw_message);
131                    Ok(Some(ServerOp::Error { error }))
132                } else if let Some(info) = control_line.strip_prefix(b"INFO ") {
133                    let info = serde_json::from_slice(info).map_err(DecoderError::InvalidInfo)?;
134                    Ok(Some(ServerOp::Info { info }))
135                } else {
136                    Err(DecoderError::InvalidCommand)
137                };
138            }
139            DecoderStatus::Headers { header_len, .. } => {
140                if read_buf.len() < *header_len {
141                    return Ok(None);
142                }
143
144                decode_headers(crlf, read_buf, status)?;
145            }
146            DecoderStatus::Payload { payload_len, .. } => {
147                if read_buf.len() < *payload_len + "\r\n".len() {
148                    return Ok(None);
149                }
150
151                let DecoderStatus::Payload {
152                    subscription_id,
153                    subject,
154                    reply_subject,
155                    status_code,
156                    status_description,
157                    headers,
158                    payload_len,
159                } = mem::replace(status, DecoderStatus::ControlLine { last_bytes_read: 0 })
160                else {
161                    unreachable!()
162                };
163
164                let payload = read_buf.split_to(payload_len);
165                read_buf.advance("\r\n".len());
166                let message = ServerMessage {
167                    status_code,
168                    status_description,
169                    subscription_id,
170                    base: MessageBase {
171                        subject,
172                        reply_subject,
173                        headers,
174                        payload,
175                    },
176                };
177                return Ok(Some(ServerOp::Message { message }));
178            }
179            DecoderStatus::Poisoned => return Err(DecoderError::Poisoned),
180        }
181    }
182}
183
184fn decode_msg(mut control_line: Bytes) -> Result<DecoderStatus, DecoderError> {
185    control_line.advance("MSG ".len());
186
187    let mut chunks = util::split_spaces(control_line);
188    let (subject, subscription_id, reply_subject, payload_len) = match (
189        chunks.next(),
190        chunks.next(),
191        chunks.next(),
192        chunks.next(),
193        chunks.next(),
194    ) {
195        (Some(subject), Some(subscription_id), Some(reply_subject), Some(payload_len), None) => {
196            (subject, subscription_id, Some(reply_subject), payload_len)
197        }
198        (Some(subject), Some(subscription_id), Some(payload_len), None, None) => {
199            (subject, subscription_id, None, payload_len)
200        }
201        _ => return Err(DecoderError::InvalidMsgArgsCount),
202    };
203    let subject = Subject::from_dangerous_value(
204        subject
205            .try_into()
206            .map_err(|_| DecoderError::SubjectInvalidUtf8)?,
207    );
208    let subscription_id =
209        SubscriptionId::from_ascii_bytes(&subscription_id).map_err(DecoderError::SubscriptionId)?;
210    let reply_subject = reply_subject
211        .map(|reply_subject| {
212            ByteString::try_from(reply_subject).map_err(|_| DecoderError::ReplySubjectInvalidUtf8)
213        })
214        .transpose()?
215        .map(Subject::from_dangerous_value);
216    let payload_len =
217        util::parse_usize(&payload_len).map_err(DecoderError::InvalidPayloadLength)?;
218    Ok(DecoderStatus::Payload {
219        subscription_id,
220        subject,
221        reply_subject,
222        status_code: None,
223        status_description: None,
224        headers: HeaderMap::new(),
225        payload_len,
226    })
227}
228
229fn decode_hmsg(mut control_line: Bytes) -> Result<DecoderStatus, DecoderError> {
230    control_line.advance("HMSG ".len());
231    let mut chunks = util::split_spaces(control_line);
232
233    let (subject, subscription_id, reply_subject, header_len, total_len) = match (
234        chunks.next(),
235        chunks.next(),
236        chunks.next(),
237        chunks.next(),
238        chunks.next(),
239        chunks.next(),
240    ) {
241        (
242            Some(subject),
243            Some(subscription_id),
244            Some(reply_to),
245            Some(header_len),
246            Some(total_len),
247            None,
248        ) => (
249            subject,
250            subscription_id,
251            Some(reply_to),
252            header_len,
253            total_len,
254        ),
255        (Some(subject), Some(subscription_id), Some(header_len), Some(total_len), None, None) => {
256            (subject, subscription_id, None, header_len, total_len)
257        }
258        _ => return Err(DecoderError::InvalidHmsgArgsCount),
259    };
260
261    let subject = Subject::from_dangerous_value(
262        subject
263            .try_into()
264            .map_err(|_| DecoderError::SubjectInvalidUtf8)?,
265    );
266    let subscription_id =
267        SubscriptionId::from_ascii_bytes(&subscription_id).map_err(DecoderError::SubscriptionId)?;
268    let reply_subject = reply_subject
269        .map(|reply_subject| {
270            ByteString::try_from(reply_subject).map_err(|_| DecoderError::ReplySubjectInvalidUtf8)
271        })
272        .transpose()?
273        .map(Subject::from_dangerous_value);
274    let header_len = util::parse_usize(&header_len).map_err(DecoderError::InvalidHeaderLength)?;
275    let total_len = util::parse_usize(&total_len).map_err(DecoderError::InvalidPayloadLength)?;
276
277    let payload_len = total_len
278        .checked_sub(header_len)
279        .ok_or(DecoderError::InvalidTotalLength)?;
280
281    Ok(DecoderStatus::Headers {
282        subscription_id,
283        subject,
284        reply_subject,
285        header_len,
286        payload_len,
287    })
288}
289
290fn decode_headers(
291    crlf: &CrlfFinder,
292    read_buf: &mut impl BytesLike,
293    status: &mut DecoderStatus,
294) -> Result<(), DecoderError> {
295    let DecoderStatus::Headers {
296        subscription_id,
297        subject,
298        reply_subject,
299        header_len,
300        payload_len,
301    } = mem::replace(status, DecoderStatus::Poisoned)
302    else {
303        unreachable!()
304    };
305
306    let header = read_buf.split_to(header_len);
307    let mut lines = util::lines_iter(crlf, header);
308    let head = lines.next().ok_or(DecoderError::MissingHead)?;
309    let status_line = head
310        .strip_prefix(b"NATS/1.0")
311        .ok_or(DecoderError::InvalidHead)?;
312    let status_code = if let Some(status_code) = status_line.get(1..4) {
313        Some(StatusCode::from_ascii_bytes(status_code).map_err(DecoderError::StatusCode)?)
314    } else {
315        None
316    };
317    let status_description = match status_line.get(4..) {
318        Some([separator, description @ ..]) => {
319            if !separator.is_ascii_whitespace() {
320                return Err(DecoderError::InvalidHead);
321            }
322
323            match description.trim_ascii() {
324                [] => None,
325                description => Some(
326                    // `description` is a subslice of `head`, making this conversion zero-copy
327                    ByteString::try_from(head.slice_ref(description))
328                        .map_err(|_| DecoderError::StatusDescriptionInvalidUtf8)?,
329                ),
330            }
331        }
332        _ => None,
333    };
334
335    let headers = lines
336        .filter(|line| !line.is_empty())
337        .map(|mut line| {
338            let i = memchr::memchr(b':', &line).ok_or(DecoderError::InvalidHeaderLine)?;
339
340            let name = line.split_to(i);
341            line.advance(":".len());
342            if line.first().is_some_and(u8::is_ascii_whitespace) {
343                // The fact that this is allowed sounds like BS to me
344                line.advance(1);
345            }
346            let value = line;
347
348            let name = HeaderName::try_from(
349                ByteString::try_from(name).map_err(|_| DecoderError::HeaderNameInvalidUtf8)?,
350            )
351            .map_err(DecoderError::HeaderName)?;
352            let value = HeaderValue::from_bytes(&value).map_err(DecoderError::HeaderValue)?;
353            Ok((name, value))
354        })
355        .collect::<Result<_, _>>()?;
356
357    *status = DecoderStatus::Payload {
358        subscription_id,
359        subject,
360        reply_subject,
361        status_code,
362        status_description,
363        headers,
364        payload_len,
365    };
366    Ok(())
367}
368
369#[derive(Debug, thiserror::Error)]
370pub enum DecoderError {
371    #[error("The head exceeded the maximum head length (len {len} maximum {MAX_HEAD_LEN}")]
372    HeadTooLong { len: usize },
373    #[error("Invalid command")]
374    InvalidCommand,
375    #[error("MSG command has an unexpected number of arguments")]
376    InvalidMsgArgsCount,
377    #[error("HMSG command has an unexpected number of arguments")]
378    InvalidHmsgArgsCount,
379    #[error("The subject isn't valid utf-8")]
380    SubjectInvalidUtf8,
381    #[error("The reply subject isn't valid utf-8")]
382    ReplySubjectInvalidUtf8,
383    #[error("Couldn't parse the Subscription ID")]
384    SubscriptionId(#[source] ParseUintError),
385    #[error("Couldn't parse the length of the header")]
386    InvalidHeaderLength(#[source] ParseUintError),
387    #[error("Couldn't parse the length of the payload")]
388    InvalidPayloadLength(#[source] ParseUintError),
389    #[error("The total length is greater than the header length")]
390    InvalidTotalLength,
391    #[error("HMSG is missing head")]
392    MissingHead,
393    #[error("HMSG has an invalid head")]
394    InvalidHead,
395    #[error("HMSG header line is missing ': '")]
396    InvalidHeaderLine,
397    #[error("Couldn't parse the status code")]
398    StatusCode(#[source] StatusCodeError),
399    #[error("The status description isn't valid utf-8")]
400    StatusDescriptionInvalidUtf8,
401    #[error("The header name isn't valid utf-8")]
402    HeaderNameInvalidUtf8,
403    #[error("The header name couldn't be parsed")]
404    HeaderName(#[source] HeaderNameValidateError),
405    #[error("The header value couldn't be parsed")]
406    HeaderValue(#[source] HeaderValueValidateError),
407    #[error("INFO command JSON payload couldn't be deserialized")]
408    InvalidInfo(#[source] serde_json::Error),
409    #[error("-ERR command message couldn't be deserialized")]
410    InvalidErrorMessage,
411    #[error("The decoder was poisoned")]
412    Poisoned,
413}