1use bytes::{Buf, Bytes, BytesMut};
2use memchr::memchr;
3use tokio_util::codec::Decoder;
4use tracing::{trace, warn};
5use vector_config::configurable_component;
6
7use super::BoxedFramingError;
8
9#[configurable_component]
11#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[serde(rename_all = "snake_case")]
13pub enum OversizedAction {
14 #[default]
16 Drop,
17
18 Truncate,
22}
23
24#[configurable_component]
26#[derive(Debug, Clone)]
27pub struct CharacterDelimitedDecoderConfig {
28 pub character_delimited: CharacterDelimitedDecoderOptions,
30}
31
32impl CharacterDelimitedDecoderConfig {
33 pub const fn new(delimiter: u8) -> Self {
35 Self {
36 character_delimited: CharacterDelimitedDecoderOptions::new(delimiter, None),
37 }
38 }
39 pub const fn build(&self) -> CharacterDelimitedDecoder {
41 let oversized_action = self.character_delimited.oversized_action;
42 if let Some(max_length) = self.character_delimited.max_length {
43 CharacterDelimitedDecoder::new_with_max_length(
44 self.character_delimited.delimiter,
45 max_length,
46 )
47 .with_oversized_action(oversized_action)
48 } else {
49 CharacterDelimitedDecoder::new(self.character_delimited.delimiter)
50 }
51 }
52}
53
54#[configurable_component]
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct CharacterDelimitedDecoderOptions {
58 #[configurable(metadata(docs::type_override = "ascii_char"))]
60 #[serde(with = "vector_core::serde::ascii_char")]
61 pub delimiter: u8,
62
63 #[serde(skip_serializing_if = "vector_core::serde::is_default")]
75 pub max_length: Option<usize>,
76
77 #[serde(default, skip_serializing_if = "vector_core::serde::is_default")]
85 pub oversized_action: OversizedAction,
86}
87
88impl CharacterDelimitedDecoderOptions {
89 pub const fn new(delimiter: u8, max_length: Option<usize>) -> Self {
91 Self {
92 delimiter,
93 max_length,
94 oversized_action: OversizedAction::Drop,
95 }
96 }
97}
98
99#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
101pub struct CharacterDelimitedDecoder {
102 pub delimiter: u8,
104 pub max_length: usize,
106 pub oversized_action: OversizedAction,
108}
109
110impl CharacterDelimitedDecoder {
111 pub const fn new(delimiter: u8) -> Self {
113 CharacterDelimitedDecoder {
114 delimiter,
115 max_length: usize::MAX,
116 oversized_action: OversizedAction::Drop,
117 }
118 }
119
120 pub const fn new_with_max_length(delimiter: u8, max_length: usize) -> Self {
124 CharacterDelimitedDecoder {
125 max_length,
126 ..CharacterDelimitedDecoder::new(delimiter)
127 }
128 }
129
130 pub const fn with_oversized_action(mut self, action: OversizedAction) -> Self {
132 self.oversized_action = action;
133 self
134 }
135
136 pub const fn max_length(&self) -> usize {
138 self.max_length
139 }
140}
141
142impl Decoder for CharacterDelimitedDecoder {
143 type Item = Bytes;
144 type Error = BoxedFramingError;
145
146 fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Bytes>, Self::Error> {
147 loop {
148 match memchr(self.delimiter, buf) {
156 None => return Ok(None),
157 Some(next_delimiter_idx) => {
158 if next_delimiter_idx > self.max_length {
159 match self.oversized_action {
160 OversizedAction::Drop => {
161 warn!(
162 message = "Discarding frame larger than max_length.",
163 buf_len = buf.len(),
164 max_length = self.max_length,
165 );
166 buf.advance(next_delimiter_idx + 1);
167 }
168 OversizedAction::Truncate => {
169 warn!(
170 message = "Truncating frame larger than max_length.",
171 original_len = next_delimiter_idx,
172 max_length = self.max_length,
173 );
174 let frame = buf.split_to(self.max_length).freeze();
175 buf.advance(next_delimiter_idx - self.max_length + 1);
177 return Ok(Some(frame));
178 }
179 }
180 } else {
181 let frame = buf.split_to(next_delimiter_idx).freeze();
182 trace!(
183 message = "Decoding the frame.",
184 bytes_processed = frame.len()
185 );
186 buf.advance(1); return Ok(Some(frame));
188 }
189 }
190 }
191 }
192 }
193
194 fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Bytes>, Self::Error> {
195 match self.decode(buf)? {
196 Some(frame) => Ok(Some(frame)),
197 None => {
198 if buf.is_empty() {
199 Ok(None)
200 } else if buf.len() > self.max_length {
201 match self.oversized_action {
202 OversizedAction::Drop => {
203 warn!(
204 message = "Discarding frame larger than max_length.",
205 buf_len = buf.len(),
206 max_length = self.max_length,
207 );
208 buf.clear();
209 Ok(None)
210 }
211 OversizedAction::Truncate => {
212 warn!(
213 message = "Truncating frame larger than max_length.",
214 original_len = buf.len(),
215 max_length = self.max_length,
216 );
217 let frame = buf.split_to(self.max_length).freeze();
218 buf.clear();
219 Ok(Some(frame))
220 }
221 }
222 } else {
223 let bytes: Bytes = buf.split_to(buf.len()).freeze();
224 Ok(Some(bytes))
225 }
226 }
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use std::collections::HashMap;
234
235 use bytes::BufMut;
236 use indoc::indoc;
237
238 use super::*;
239
240 #[test]
241 fn decode() {
242 let mut codec = CharacterDelimitedDecoder::new(b'\n');
243 let buf = &mut BytesMut::new();
244 buf.put_slice(b"abc\n");
245 assert_eq!(Some("abc".into()), codec.decode(buf).unwrap());
246 }
247
248 #[test]
249 fn decode_max_length() {
250 const MAX_LENGTH: usize = 6;
251
252 let mut codec = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH);
253 let buf = &mut BytesMut::new();
254
255 buf.put_slice(b"1234567\n123456\n123412314\n123");
257
258 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("123456")));
259 assert_eq!(codec.decode(buf).unwrap(), None);
260
261 let buf = &mut BytesMut::new();
262
263 buf.put_slice(b"1234567\n123456\n123412314\n123");
265
266 assert_eq!(codec.decode_eof(buf).unwrap(), Some(Bytes::from("123456")));
267 assert_eq!(codec.decode_eof(buf).unwrap(), Some(Bytes::from("123")));
268 assert_eq!(codec.decode_eof(buf).unwrap(), None);
269 }
270
271 #[test]
274 fn decode_discard_repeat() {
275 const MAX_LENGTH: usize = 1;
276
277 let mut codec = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH);
278 let buf = &mut BytesMut::new();
279
280 buf.reserve(200);
281 buf.put(&b"aa"[..]);
282 assert!(codec.decode(buf).unwrap().is_none());
283 buf.put(&b"a"[..]);
284 assert!(codec.decode(buf).unwrap().is_none());
285 }
286
287 #[test]
288 fn decode_json_escaped() {
289 let mut input = HashMap::new();
290 input.insert("key", "value");
291 input.insert("new", "li\nne");
292
293 let mut bytes = serde_json::to_vec(&input).unwrap();
294 bytes.push(b'\n');
295
296 let mut codec = CharacterDelimitedDecoder::new(b'\n');
297 let buf = &mut BytesMut::new();
298
299 buf.reserve(bytes.len());
300 buf.extend(bytes);
301
302 let result = codec.decode(buf).unwrap();
303
304 assert!(result.is_some());
305 assert!(buf.is_empty());
306 }
307
308 #[test]
309 fn decode_json_multiline() {
310 let events = indoc! {r#"
311 {"log":"\u0009at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)\n","stream":"stdout","time":"2019-01-18T07:49:27.374616758Z"}
312 {"log":"\u0009at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)\n","stream":"stdout","time":"2019-01-18T07:49:27.374640288Z"}
313 {"log":"\u0009at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)\n","stream":"stdout","time":"2019-01-18T07:49:27.374655505Z"}
314 {"log":"\u0009at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n","stream":"stdout","time":"2019-01-18T07:49:27.374671955Z"}
315 {"log":"\u0009at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)\n","stream":"stdout","time":"2019-01-18T07:49:27.374690312Z"}
316 {"log":"\u0009at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)\n","stream":"stdout","time":"2019-01-18T07:49:27.374704522Z"}
317 {"log":"\u0009at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)\n","stream":"stdout","time":"2019-01-18T07:49:27.374718459Z"}
318 {"log":"\u0009at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)\n","stream":"stdout","time":"2019-01-18T07:49:27.374732919Z"}
319 {"log":"\u0009at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)\n","stream":"stdout","time":"2019-01-18T07:49:27.374750799Z"}
320 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n","stream":"stdout","time":"2019-01-18T07:49:27.374764819Z"}
321 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n","stream":"stdout","time":"2019-01-18T07:49:27.374778682Z"}
322 {"log":"\u0009at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)\n","stream":"stdout","time":"2019-01-18T07:49:27.374792429Z"}
323 {"log":"\u0009at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n","stream":"stdout","time":"2019-01-18T07:49:27.374805985Z"}
324 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n","stream":"stdout","time":"2019-01-18T07:49:27.374819625Z"}
325 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n","stream":"stdout","time":"2019-01-18T07:49:27.374833335Z"}
326 {"log":"\u0009at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)\n","stream":"stdout","time":"2019-01-18T07:49:27.374847845Z"}
327 {"log":"\u0009at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n","stream":"stdout","time":"2019-01-18T07:49:27.374861925Z"}
328 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n","stream":"stdout","time":"2019-01-18T07:49:27.37487589Z"}
329 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n","stream":"stdout","time":"2019-01-18T07:49:27.374890043Z"}
330 {"log":"\u0009at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)\n","stream":"stdout","time":"2019-01-18T07:49:27.374903813Z"}
331 {"log":"\u0009at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n","stream":"stdout","time":"2019-01-18T07:49:27.374917793Z"}
332 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n","stream":"stdout","time":"2019-01-18T07:49:27.374931586Z"}
333 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n","stream":"stdout","time":"2019-01-18T07:49:27.374946006Z"}
334 {"log":"\u0009at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:117)\n","stream":"stdout","time":"2019-01-18T07:49:27.37496104Z"}
335 {"log":"\u0009at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:106)\n","stream":"stdout","time":"2019-01-18T07:49:27.37498773Z"}
336 {"log":"\u0009at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n","stream":"stdout","time":"2019-01-18T07:49:27.375003113Z"}
337 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n","stream":"stdout","time":"2019-01-18T07:49:27.375017063Z"}
338 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n","stream":"stdout","time":"2019-01-18T07:49:27.37503086Z"}
339 {"log":"\u0009at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)\n","stream":"stdout","time":"2019-01-18T07:49:27.3750454Z"}
340 {"log":"\u0009at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n","stream":"stdout","time":"2019-01-18T07:49:27.37505928Z"}
341 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n","stream":"stdout","time":"2019-01-18T07:49:27.37507306Z"}
342 {"log":"\u0009at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n","stream":"stdout","time":"2019-01-18T07:49:27.375086726Z"}
343 {"log":"\u0009at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)\n","stream":"stdout","time":"2019-01-18T07:49:27.375100817Z"}
344 {"log":"\u0009at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n","stream":"stdout","time":"2019-01-18T07:49:27.375115354Z"}
345 {"log":"\u0009at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)\n","stream":"stdout","time":"2019-01-18T07:49:27.375129454Z"}
346 {"log":"\u0009at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)\n","stream":"stdout","time":"2019-01-18T07:49:27.375144001Z"}
347 {"log":"\u0009at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)\n","stream":"stdout","time":"2019-01-18T07:49:27.375157464Z"}
348 {"log":"\u0009at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)\n","stream":"stdout","time":"2019-01-18T07:49:27.375170981Z"}
349 {"log":"\u0009at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)\n","stream":"stdout","time":"2019-01-18T07:49:27.375184417Z"}
350 {"log":"\u0009at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)\n","stream":"stdout","time":"2019-01-18T07:49:27.375198024Z"}
351 {"log":"\u0009at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)\n","stream":"stdout","time":"2019-01-18T07:49:27.375211594Z"}
352 {"log":"\u0009at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)\n","stream":"stdout","time":"2019-01-18T07:49:27.375225237Z"}
353 {"log":"\u0009at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)\n","stream":"stdout","time":"2019-01-18T07:49:27.375239487Z"}
354 {"log":"\u0009at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n","stream":"stdout","time":"2019-01-18T07:49:27.375253464Z"}
355 {"log":"\u0009at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n","stream":"stdout","time":"2019-01-18T07:49:27.375323255Z"}
356 {"log":"\u0009at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n","stream":"stdout","time":"2019-01-18T07:49:27.375345642Z"}
357 {"log":"\u0009at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n","stream":"stdout","time":"2019-01-18T07:49:27.375363208Z"}
358 {"log":"\u0009at java.lang.Thread.run(Thread.java:748)\n","stream":"stdout","time":"2019-01-18T07:49:27.375377695Z"}
359 {"log":"\n","stream":"stdout","time":"2019-01-18T07:49:27.375391335Z"}
360 {"log":"\n","stream":"stdout","time":"2019-01-18T07:49:27.375416915Z"}
361 {"log":"2019-01-18 07:53:06.419 [ ] INFO 1 --- [vent-bus.prod-1] c.t.listener.CommonListener : warehousing Dailywarehousing.daily\n","stream":"stdout","time":"2019-01-18T07:53:06.420527437Z"}
362 "#};
363
364 let mut codec = CharacterDelimitedDecoder::new(b'\n');
365 let buf = &mut BytesMut::new();
366
367 buf.extend(events.to_string().as_bytes());
368
369 let mut i = 0;
370 while codec.decode(buf).unwrap().is_some() {
371 i += 1;
372 }
373
374 assert_eq!(i, 51);
375 }
376
377 #[test]
378 fn decode_truncate_oversized() {
379 const MAX_LENGTH: usize = 6;
380
381 let mut codec = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH)
382 .with_oversized_action(OversizedAction::Truncate);
383 let buf = &mut BytesMut::new();
384
385 buf.put_slice(b"1234567890\n123456\n");
386
387 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("123456")));
389 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("123456")));
391 assert_eq!(codec.decode(buf).unwrap(), None);
392 }
393
394 #[test]
395 fn decode_truncate_exact_boundary() {
396 const MAX_LENGTH: usize = 6;
397
398 let mut codec = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH)
399 .with_oversized_action(OversizedAction::Truncate);
400 let buf = &mut BytesMut::new();
401
402 buf.put_slice(b"123456\n");
404 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("123456")));
405 assert_eq!(codec.decode(buf).unwrap(), None);
406 }
407
408 #[test]
409 fn decode_truncate_next_frame_intact() {
410 const MAX_LENGTH: usize = 3;
411
412 let mut codec = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH)
413 .with_oversized_action(OversizedAction::Truncate);
414 let buf = &mut BytesMut::new();
415
416 buf.put_slice(b"toolong\nok\nanother_too_long\nfin\n");
417
418 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("too")));
419 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("ok")));
420 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("ano")));
421 assert_eq!(codec.decode(buf).unwrap(), Some(Bytes::from("fin")));
422 assert_eq!(codec.decode(buf).unwrap(), None);
423 }
424
425 #[test]
426 fn decode_eof_truncate() {
427 const MAX_LENGTH: usize = 4;
428
429 let mut codec = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH)
430 .with_oversized_action(OversizedAction::Truncate);
431 let buf = &mut BytesMut::new();
432
433 buf.put_slice(b"abcdefgh");
435 assert_eq!(codec.decode_eof(buf).unwrap(), Some(Bytes::from("abcd")));
436 assert_eq!(codec.decode_eof(buf).unwrap(), None);
437 }
438
439 #[test]
440 fn decode_truncate_mixed_with_drop() {
441 const MAX_LENGTH: usize = 6;
442
443 let mut codec_drop = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH);
445 let buf = &mut BytesMut::new();
446 buf.put_slice(b"1234567\n123456\n123412314\n123");
447
448 assert_eq!(codec_drop.decode(buf).unwrap(), Some(Bytes::from("123456")));
449 assert_eq!(codec_drop.decode(buf).unwrap(), None);
450
451 let mut codec_trunc = CharacterDelimitedDecoder::new_with_max_length(b'\n', MAX_LENGTH)
453 .with_oversized_action(OversizedAction::Truncate);
454 let buf = &mut BytesMut::new();
455 buf.put_slice(b"1234567\n123456\n123412314\n123");
456
457 assert_eq!(
458 codec_trunc.decode(buf).unwrap(),
459 Some(Bytes::from("123456"))
460 );
461 assert_eq!(
462 codec_trunc.decode(buf).unwrap(),
463 Some(Bytes::from("123456"))
464 );
465 assert_eq!(
466 codec_trunc.decode(buf).unwrap(),
467 Some(Bytes::from("123412"))
468 );
469 assert_eq!(codec_trunc.decode(buf).unwrap(), None);
470 }
471}