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// Sanity limit for the payload length declared by `MSG` and the total length
34// declared by `HMSG`. The nats-server bounds the messages it delivers by the
35// `max_payload` option (1 MiB by default, validated against `max_pending`,
36// 64 MiB by default; nats-server v2.15), so a larger declared length can only
37// come from a broken or malicious peer. Without a limit such a peer could
38// make the read buffer grow without bounds.
39const MAX_MSG_LEN: usize = 128 * 1024 * 1024;
40
41#[derive(Debug)]
42pub(super) enum DecoderStatus {
43    ControlLine {
44        last_bytes_read: usize,
45    },
46    Headers {
47        subscription_id: SubscriptionId,
48        subject: Subject,
49        reply_subject: Option<Subject>,
50        header_len: usize,
51        payload_len: usize,
52    },
53    Payload {
54        subscription_id: SubscriptionId,
55        subject: Subject,
56        reply_subject: Option<Subject>,
57        status_code: Option<StatusCode>,
58        status_description: Option<ByteString>,
59        headers: HeaderMap,
60        payload_len: usize,
61    },
62    Poisoned,
63}
64
65pub(super) trait BytesLike: Buf + Deref<Target = [u8]> {
66    fn len(&self) -> usize {
67        Buf::remaining(self)
68    }
69
70    fn split_to(&mut self, at: usize) -> Bytes {
71        self.copy_to_bytes(at)
72    }
73}
74
75impl BytesLike for Bytes {}
76impl BytesLike for BytesMut {}
77
78pub(super) fn decode(
79    crlf: &CrlfFinder,
80    status: &mut DecoderStatus,
81    read_buf: &mut impl BytesLike,
82) -> Result<Option<ServerOp>, DecoderError> {
83    loop {
84        match status {
85            DecoderStatus::ControlLine { last_bytes_read } => {
86                if read_buf.starts_with(b"+OK\r\n") {
87                    // Fast path for handling `+OK`
88                    debug_assert_eq!(
89                        *last_bytes_read, 0,
90                        "we shouldn't have handled any bytes before"
91                    );
92                    read_buf.advance("+OK\r\n".len());
93                    return Ok(Some(ServerOp::Success));
94                }
95
96                if *last_bytes_read == read_buf.len() {
97                    // No progress has been made
98                    return Ok(None);
99                }
100
101                let Some(control_line_len) = crlf.find(read_buf) else {
102                    return if read_buf.len() < MAX_HEAD_LEN {
103                        *last_bytes_read = read_buf.len();
104                        Ok(None)
105                    } else {
106                        Err(DecoderError::HeadTooLong {
107                            len: read_buf.len(),
108                        })
109                    };
110                };
111
112                let mut control_line = read_buf.split_to(control_line_len + "\r\n".len());
113                control_line.truncate(control_line.len() - 2);
114
115                return if control_line.starts_with(b"MSG ") {
116                    *status = decode_msg(control_line)?;
117                    continue;
118                } else if control_line.starts_with(b"HMSG ") {
119                    *status = decode_hmsg(control_line)?;
120                    continue;
121                } else if control_line.starts_with(b"PING") {
122                    Ok(Some(ServerOp::Ping))
123                } else if control_line.starts_with(b"PONG") {
124                    Ok(Some(ServerOp::Pong))
125                } else if control_line.starts_with(b"+OK") {
126                    // Slow path for handling `+OK`
127                    Ok(Some(ServerOp::Success))
128                } else if control_line.starts_with(b"-ERR ") {
129                    control_line.advance("-ERR ".len());
130                    // A single `'` satisfies both `starts_with` and `ends_with`
131                    // while only being one byte long: requiring at least two
132                    // bytes keeps the quote stripping below from underflowing.
133                    if control_line.len() < 2
134                        || !control_line.starts_with(b"'")
135                        || !control_line.ends_with(b"'")
136                    {
137                        return Err(DecoderError::InvalidErrorMessage);
138                    }
139
140                    control_line.advance(1);
141                    control_line.truncate(control_line.len() - 1);
142                    let raw_message = ByteString::try_from(control_line)
143                        .map_err(|_| DecoderError::InvalidErrorMessage)?;
144                    let error = ServerError::parse(raw_message);
145                    Ok(Some(ServerOp::Error { error }))
146                } else if let Some(info) = control_line.strip_prefix(b"INFO ") {
147                    let info = serde_json::from_slice(info).map_err(DecoderError::InvalidInfo)?;
148                    Ok(Some(ServerOp::Info { info }))
149                } else {
150                    Err(DecoderError::InvalidCommand)
151                };
152            }
153            DecoderStatus::Headers { header_len, .. } => {
154                if read_buf.len() < *header_len {
155                    return Ok(None);
156                }
157
158                decode_headers(crlf, read_buf, status)?;
159            }
160            DecoderStatus::Payload { payload_len, .. } => {
161                // `payload_len` is declared by the server and may be close to
162                // `usize::MAX`, in which case adding the CRLF length overflows
163                // and (in release builds) wraps around, letting a partial
164                // payload through to the panicking `split_to` call below.
165                if read_buf.len() < payload_len.saturating_add("\r\n".len()) {
166                    return Ok(None);
167                }
168
169                let DecoderStatus::Payload {
170                    subscription_id,
171                    subject,
172                    reply_subject,
173                    status_code,
174                    status_description,
175                    headers,
176                    payload_len,
177                } = mem::replace(status, DecoderStatus::ControlLine { last_bytes_read: 0 })
178                else {
179                    unreachable!()
180                };
181
182                let payload = read_buf.split_to(payload_len);
183                read_buf.advance("\r\n".len());
184                let message = ServerMessage {
185                    status_code,
186                    status_description,
187                    subscription_id,
188                    base: MessageBase {
189                        subject,
190                        reply_subject,
191                        headers,
192                        payload,
193                    },
194                };
195                return Ok(Some(ServerOp::Message { message }));
196            }
197            DecoderStatus::Poisoned => return Err(DecoderError::Poisoned),
198        }
199    }
200}
201
202fn decode_msg(mut control_line: Bytes) -> Result<DecoderStatus, DecoderError> {
203    control_line.advance("MSG ".len());
204
205    let mut chunks = util::split_spaces(control_line);
206    let (subject, subscription_id, reply_subject, payload_len) = match (
207        chunks.next(),
208        chunks.next(),
209        chunks.next(),
210        chunks.next(),
211        chunks.next(),
212    ) {
213        (Some(subject), Some(subscription_id), Some(reply_subject), Some(payload_len), None) => {
214            (subject, subscription_id, Some(reply_subject), payload_len)
215        }
216        (Some(subject), Some(subscription_id), Some(payload_len), None, None) => {
217            (subject, subscription_id, None, payload_len)
218        }
219        _ => return Err(DecoderError::InvalidMsgArgsCount),
220    };
221    let subject = Subject::from_dangerous_value(
222        subject
223            .try_into()
224            .map_err(|_| DecoderError::SubjectInvalidUtf8)?,
225    );
226    let subscription_id =
227        SubscriptionId::from_ascii_bytes(&subscription_id).map_err(DecoderError::SubscriptionId)?;
228    let reply_subject = reply_subject
229        .map(|reply_subject| {
230            ByteString::try_from(reply_subject).map_err(|_| DecoderError::ReplySubjectInvalidUtf8)
231        })
232        .transpose()?
233        .map(Subject::from_dangerous_value);
234    let payload_len =
235        util::parse_usize(&payload_len).map_err(DecoderError::InvalidPayloadLength)?;
236    if payload_len > MAX_MSG_LEN {
237        // The declared length doesn't overflow `usize`, but it overflows what
238        // we're willing to buffer: report it as an overflow.
239        return Err(DecoderError::InvalidPayloadLength(ParseUintError::Overflow));
240    }
241    Ok(DecoderStatus::Payload {
242        subscription_id,
243        subject,
244        reply_subject,
245        status_code: None,
246        status_description: None,
247        headers: HeaderMap::new(),
248        payload_len,
249    })
250}
251
252fn decode_hmsg(mut control_line: Bytes) -> Result<DecoderStatus, DecoderError> {
253    control_line.advance("HMSG ".len());
254    let mut chunks = util::split_spaces(control_line);
255
256    let (subject, subscription_id, reply_subject, header_len, total_len) = match (
257        chunks.next(),
258        chunks.next(),
259        chunks.next(),
260        chunks.next(),
261        chunks.next(),
262        chunks.next(),
263    ) {
264        (
265            Some(subject),
266            Some(subscription_id),
267            Some(reply_to),
268            Some(header_len),
269            Some(total_len),
270            None,
271        ) => (
272            subject,
273            subscription_id,
274            Some(reply_to),
275            header_len,
276            total_len,
277        ),
278        (Some(subject), Some(subscription_id), Some(header_len), Some(total_len), None, None) => {
279            (subject, subscription_id, None, header_len, total_len)
280        }
281        _ => return Err(DecoderError::InvalidHmsgArgsCount),
282    };
283
284    let subject = Subject::from_dangerous_value(
285        subject
286            .try_into()
287            .map_err(|_| DecoderError::SubjectInvalidUtf8)?,
288    );
289    let subscription_id =
290        SubscriptionId::from_ascii_bytes(&subscription_id).map_err(DecoderError::SubscriptionId)?;
291    let reply_subject = reply_subject
292        .map(|reply_subject| {
293            ByteString::try_from(reply_subject).map_err(|_| DecoderError::ReplySubjectInvalidUtf8)
294        })
295        .transpose()?
296        .map(Subject::from_dangerous_value);
297    let header_len = util::parse_usize(&header_len).map_err(DecoderError::InvalidHeaderLength)?;
298    let total_len = util::parse_usize(&total_len).map_err(DecoderError::InvalidPayloadLength)?;
299    if total_len > MAX_MSG_LEN {
300        // The declared length doesn't overflow `usize`, but it overflows what
301        // we're willing to buffer: report it as an overflow.
302        return Err(DecoderError::InvalidPayloadLength(ParseUintError::Overflow));
303    }
304
305    let payload_len = total_len
306        .checked_sub(header_len)
307        .ok_or(DecoderError::InvalidTotalLength)?;
308
309    Ok(DecoderStatus::Headers {
310        subscription_id,
311        subject,
312        reply_subject,
313        header_len,
314        payload_len,
315    })
316}
317
318fn decode_headers(
319    crlf: &CrlfFinder,
320    read_buf: &mut impl BytesLike,
321    status: &mut DecoderStatus,
322) -> Result<(), DecoderError> {
323    let DecoderStatus::Headers {
324        subscription_id,
325        subject,
326        reply_subject,
327        header_len,
328        payload_len,
329    } = mem::replace(status, DecoderStatus::Poisoned)
330    else {
331        unreachable!()
332    };
333
334    let header = read_buf.split_to(header_len);
335    let mut lines = util::lines_iter(crlf, header);
336    let head = lines.next().ok_or(DecoderError::MissingHead)?;
337    let status_line = head
338        .strip_prefix(b"NATS/1.0")
339        .ok_or(DecoderError::InvalidHead)?;
340    let status_code = if let Some(status_code) = status_line.get(1..4) {
341        Some(StatusCode::from_ascii_bytes(status_code).map_err(DecoderError::StatusCode)?)
342    } else {
343        None
344    };
345    let status_description = match status_line.get(4..) {
346        Some([separator, description @ ..]) => {
347            if !separator.is_ascii_whitespace() {
348                return Err(DecoderError::InvalidHead);
349            }
350
351            match description.trim_ascii() {
352                [] => None,
353                description => Some(
354                    // `description` is a subslice of `head`, making this conversion zero-copy
355                    ByteString::try_from(head.slice_ref(description))
356                        .map_err(|_| DecoderError::StatusDescriptionInvalidUtf8)?,
357                ),
358            }
359        }
360        _ => None,
361    };
362
363    let headers = lines
364        .filter(|line| !line.is_empty())
365        .map(|mut line| {
366            let i = memchr::memchr(b':', &line).ok_or(DecoderError::InvalidHeaderLine)?;
367
368            let name = line.split_to(i);
369            line.advance(":".len());
370            if line.first().is_some_and(u8::is_ascii_whitespace) {
371                // The fact that this is allowed sounds like BS to me
372                line.advance(1);
373            }
374            let value = line;
375
376            let name = HeaderName::try_from(
377                ByteString::try_from(name).map_err(|_| DecoderError::HeaderNameInvalidUtf8)?,
378            )
379            .map_err(DecoderError::HeaderName)?;
380            let value = HeaderValue::from_bytes(&value).map_err(DecoderError::HeaderValue)?;
381            Ok((name, value))
382        })
383        .collect::<Result<_, _>>()?;
384
385    *status = DecoderStatus::Payload {
386        subscription_id,
387        subject,
388        reply_subject,
389        status_code,
390        status_description,
391        headers,
392        payload_len,
393    };
394    Ok(())
395}
396
397#[derive(Debug, thiserror::Error)]
398pub enum DecoderError {
399    #[error("The head exceeded the maximum head length (len {len} maximum {MAX_HEAD_LEN}")]
400    HeadTooLong { len: usize },
401    #[error("Invalid command")]
402    InvalidCommand,
403    #[error("MSG command has an unexpected number of arguments")]
404    InvalidMsgArgsCount,
405    #[error("HMSG command has an unexpected number of arguments")]
406    InvalidHmsgArgsCount,
407    #[error("The subject isn't valid utf-8")]
408    SubjectInvalidUtf8,
409    #[error("The reply subject isn't valid utf-8")]
410    ReplySubjectInvalidUtf8,
411    #[error("Couldn't parse the Subscription ID")]
412    SubscriptionId(#[source] ParseUintError),
413    #[error("Couldn't parse the length of the header")]
414    InvalidHeaderLength(#[source] ParseUintError),
415    #[error("Couldn't parse the length of the payload")]
416    InvalidPayloadLength(#[source] ParseUintError),
417    #[error("The total length is greater than the header length")]
418    InvalidTotalLength,
419    #[error("HMSG is missing head")]
420    MissingHead,
421    #[error("HMSG has an invalid head")]
422    InvalidHead,
423    #[error("HMSG header line is missing ': '")]
424    InvalidHeaderLine,
425    #[error("Couldn't parse the status code")]
426    StatusCode(#[source] StatusCodeError),
427    #[error("The status description isn't valid utf-8")]
428    StatusDescriptionInvalidUtf8,
429    #[error("The header name isn't valid utf-8")]
430    HeaderNameInvalidUtf8,
431    #[error("The header name couldn't be parsed")]
432    HeaderName(#[source] HeaderNameValidateError),
433    #[error("The header value couldn't be parsed")]
434    HeaderValue(#[source] HeaderValueValidateError),
435    #[error("INFO command JSON payload couldn't be deserialized")]
436    InvalidInfo(#[source] serde_json::Error),
437    #[error("-ERR command message couldn't be deserialized")]
438    InvalidErrorMessage,
439    #[error("The decoder was poisoned")]
440    Poisoned,
441}