1pub(super) mod span;
5
6use crate::msgpack_decoder::decode::buffer::Buffer;
7use crate::msgpack_decoder::decode::error::DecodeError;
8use crate::span::v1::{TraceChunk, TracerPayload, TracerPayloadBytes, TracerPayloadSlice};
9use crate::span::DeserializableTraceData;
10use rmp::decode;
11use std::borrow::Borrow;
12
13pub(super) mod trace_key {
17 pub const CONTAINER_ID: u8 = 2;
18 pub const LANGUAGE_NAME: u8 = 3;
19 pub const LANGUAGE_VERSION: u8 = 4;
20 pub const TRACER_VERSION: u8 = 5;
21 pub const RUNTIME_ID: u8 = 6;
22 pub const ENV_REF: u8 = 7;
23 pub const HOSTNAME_REF: u8 = 8;
24 pub const APP_VERSION_REF: u8 = 9;
25 pub const ATTRIBUTES: u8 = 10;
26 pub const CHUNKS: u8 = 11;
27}
28
29pub(super) mod chunk_key {
30 pub const PRIORITY: u8 = 1;
31 pub const ORIGIN: u8 = 2;
32 pub const ATTRIBUTES: u8 = 3;
33 pub const SPANS: u8 = 4;
34 pub const DROPPED_TRACE: u8 = 5;
35 pub const TRACE_ID: u8 = 6;
36 pub const SAMPLING_MECHANISM: u8 = 7;
37}
38
39pub(super) mod span_key {
40 pub const SERVICE: u8 = 1;
41 pub const NAME: u8 = 2;
42 pub const RESOURCE: u8 = 3;
43 pub const SPAN_ID: u8 = 4;
44 pub const PARENT_ID: u8 = 5;
45 pub const START: u8 = 6;
46 pub const DURATION: u8 = 7;
47 pub const ERROR: u8 = 8;
48 pub const ATTRIBUTES: u8 = 9;
49 pub const TYPE: u8 = 10;
50 pub const SPAN_LINKS: u8 = 11;
51 pub const SPAN_EVENTS: u8 = 12;
52 pub const ENV: u8 = 13;
53 pub const VERSION: u8 = 14;
54 pub const COMPONENT: u8 = 15;
55 pub const KIND: u8 = 16;
56}
57
58pub(super) mod span_link_key {
59 pub const TRACE_ID: u8 = 1;
60 pub const SPAN_ID: u8 = 2;
61 pub const ATTRIBUTES: u8 = 3;
62 pub const TRACE_STATE: u8 = 4;
63 pub const FLAGS: u8 = 5;
64}
65
66pub(super) mod span_event_key {
67 pub const TIME: u8 = 1;
68 pub const NAME: u8 = 2;
69 pub const ATTRIBUTES: u8 = 3;
70}
71
72pub(super) const ANY_VALUE_KEY_STRING: u8 = 1;
73pub(super) const ANY_VALUE_KEY_BOOL: u8 = 2;
74pub(super) const ANY_VALUE_KEY_DOUBLE: u8 = 3;
75pub(super) const ANY_VALUE_KEY_INT64: u8 = 4;
76pub(super) const ANY_VALUE_KEY_BYTES: u8 = 5;
77pub(super) const ANY_VALUE_KEY_ARRAY: u8 = 6;
78pub(super) const ANY_VALUE_KEY_KEY_VALUE_LIST: u8 = 7;
79
80pub(super) const TYPED_VALUE_STRIDE: u32 = 2;
82
83pub(super) const FLAT_ATTR_STRIDE: u32 = 3;
85
86pub(super) const TRACE_ID_LEN: u32 = 16;
88
89pub(super) struct StringTable<T: DeserializableTraceData>
95where
96 T::Text: Clone,
97{
98 seen: Vec<T::Text>,
99}
100
101impl<T: DeserializableTraceData> StringTable<T>
102where
103 T::Text: Clone,
104{
105 pub(super) fn new() -> Self {
106 Self {
107 seen: vec![T::Text::default()],
108 }
109 }
110
111 fn resolve(&self, id: u64) -> Result<T::Text, DecodeError> {
113 usize::try_from(id)
114 .ok()
115 .and_then(|i| self.seen.get(i).cloned())
116 .ok_or_else(|| {
117 DecodeError::InvalidFormat(format!(
118 "V1 string table reference out of range: id={id}, table_len={}",
119 self.seen.len()
120 ))
121 })
122 }
123
124 fn record(&mut self, s: &T::Text) {
126 self.seen.push(s.clone());
127 }
128}
129
130pub(super) fn read_interned_string<T: DeserializableTraceData>(
136 buf: &mut Buffer<T>,
137 table: &mut StringTable<T>,
138) -> Result<T::Text, DecodeError>
139where
140 T::Text: Clone,
141{
142 let slice: &[u8] = buf.as_slice();
143 let marker_byte = *slice.first().ok_or_else(|| {
144 DecodeError::InvalidFormat(
145 "Unexpected end of V1 buffer when reading interned string".to_owned(),
146 )
147 })?;
148
149 match marker_byte {
155 0xa0..=0xbf | 0xd9 | 0xda | 0xdb => {
156 let s = buf.read_string()?;
157 table.record(&s);
158 Ok(s)
159 }
160 0x00..=0x7f | 0xcc | 0xcd | 0xce | 0xcf => {
161 let id: u64 = decode::read_int(buf.as_mut_slice()).map_err(|_| {
162 DecodeError::InvalidFormat(
163 "V1 interned string reference uint read failure".to_owned(),
164 )
165 })?;
166 table.resolve(id)
167 }
168 _ => Err(DecodeError::InvalidFormat(format!(
169 "Unexpected msgpack marker 0x{marker_byte:02x} for V1 interned string"
170 ))),
171 }
172}
173
174pub fn from_bytes(
187 data: libdd_tinybytes::Bytes,
188) -> Result<(TracerPayloadBytes, usize), DecodeError> {
189 from_buffer(&mut Buffer::new(data))
190}
191
192pub fn from_slice(data: &[u8]) -> Result<(TracerPayloadSlice<'_>, usize), DecodeError> {
195 from_buffer(&mut Buffer::new(data))
196}
197
198pub fn from_buffer<T: DeserializableTraceData>(
200 data: &mut Buffer<T>,
201) -> Result<(TracerPayload<T>, usize), DecodeError>
202where
203 T::Text: Clone,
204{
205 let start_len = data.len();
206 let mut table = StringTable::<T>::new();
207 let payload = decode_payload(data, &mut table)?;
208 let consumed = start_len - data.len();
209 Ok((payload, consumed))
210}
211
212pub(super) fn skip_unknown_value<T: DeserializableTraceData>(
220 buf: &mut Buffer<T>,
221 table: &mut StringTable<T>,
222) -> Result<(), DecodeError>
223where
224 T::Text: Clone,
225{
226 let owner = buf.bytes().clone();
231 let value = rmpv::decode::read_value_ref(buf.as_mut_slice())
232 .map_err(|_| DecodeError::InvalidFormat("Failed to skip unknown V1 value".to_owned()))?;
233 record_strings_in_value_ref::<T>(&value, &owner, table);
234 Ok(())
235}
236
237fn record_strings_in_value_ref<T: DeserializableTraceData>(
246 value: &rmpv::ValueRef<'static>,
247 owner: &T::Bytes,
248 table: &mut StringTable<T>,
249) where
250 T::Text: Clone,
251{
252 match value {
253 rmpv::ValueRef::String(s) => {
254 if let Some(s) = (*s).into_str() {
255 table.record(&T::intern_skipped_str(owner, s));
256 }
257 }
258 rmpv::ValueRef::Array(items) => {
259 for item in items {
260 record_strings_in_value_ref::<T>(item, owner, table);
261 }
262 }
263 rmpv::ValueRef::Map(entries) => {
264 for (key, val) in entries {
265 record_strings_in_value_ref::<T>(key, owner, table);
266 record_strings_in_value_ref::<T>(val, owner, table);
267 }
268 }
269 _ => {}
270 }
271}
272
273fn decode_payload<T: DeserializableTraceData>(
275 buf: &mut Buffer<T>,
276 table: &mut StringTable<T>,
277) -> Result<TracerPayload<T>, DecodeError>
278where
279 T::Text: Clone,
280{
281 let map_len = decode::read_map_len(buf.as_mut_slice())
282 .map_err(|_| DecodeError::InvalidFormat("Unable to read V1 payload map len".to_owned()))?;
283
284 let mut payload = TracerPayload::<T>::default();
285 let mut saw_chunks = false;
286
287 for _ in 0..map_len {
288 let key = decode::read_int::<u8, _>(buf.as_mut_slice()).map_err(|_| {
289 DecodeError::InvalidFormat("V1 payload key (u8) read failure".to_owned())
290 })?;
291 match key {
292 trace_key::CHUNKS => {
293 payload.chunks = decode_chunks(buf, table)?;
294 saw_chunks = true;
295 }
296 trace_key::CONTAINER_ID => payload.container_id = read_interned_string(buf, table)?,
297 trace_key::LANGUAGE_NAME => payload.language_name = read_interned_string(buf, table)?,
298 trace_key::LANGUAGE_VERSION => {
299 payload.language_version = read_interned_string(buf, table)?
300 }
301 trace_key::TRACER_VERSION => payload.tracer_version = read_interned_string(buf, table)?,
302 trace_key::RUNTIME_ID => payload.runtime_id = read_interned_string(buf, table)?,
303 trace_key::ENV_REF => payload.env = read_interned_string(buf, table)?,
304 trace_key::HOSTNAME_REF => payload.hostname = read_interned_string(buf, table)?,
305 trace_key::APP_VERSION_REF => payload.app_version = read_interned_string(buf, table)?,
306 trace_key::ATTRIBUTES => {
307 payload.attributes = span::read_attributes_map(buf, table)?;
308 }
309 _unknown => skip_unknown_value(buf, table)?,
310 }
311 }
312
313 if !saw_chunks {
314 return Err(DecodeError::InvalidFormat(
315 "V1 payload is missing the chunks field".to_owned(),
316 ));
317 }
318
319 Ok(payload)
320}
321
322fn decode_chunks<T: DeserializableTraceData>(
323 buf: &mut Buffer<T>,
324 table: &mut StringTable<T>,
325) -> Result<Vec<TraceChunk<T>>, DecodeError>
326where
327 T::Text: Clone,
328{
329 let count = decode::read_array_len(buf.as_mut_slice())
330 .map_err(|_| DecodeError::InvalidFormat("V1 chunks array len read failure".to_owned()))?;
331 let mut chunks = Vec::with_capacity(buf.capped_capacity(count as usize));
332 for _ in 0..count {
333 chunks.push(decode_chunk(buf, table)?);
334 }
335 Ok(chunks)
336}
337
338fn decode_chunk<T: DeserializableTraceData>(
339 buf: &mut Buffer<T>,
340 table: &mut StringTable<T>,
341) -> Result<TraceChunk<T>, DecodeError>
342where
343 T::Text: Clone,
344{
345 let map_len = decode::read_map_len(buf.as_mut_slice())
346 .map_err(|_| DecodeError::InvalidFormat("V1 chunk map len read failure".to_owned()))?;
347 let mut chunk = TraceChunk::<T>::default();
348 let mut saw_trace_id = false;
349 let mut saw_spans = false;
350
351 for _ in 0..map_len {
352 let key = decode::read_int::<u8, _>(buf.as_mut_slice())
353 .map_err(|_| DecodeError::InvalidFormat("V1 chunk key (u8) read failure".to_owned()))?;
354 match key {
355 chunk_key::TRACE_ID => {
356 let len = decode::read_bin_len(buf.as_mut_slice()).map_err(|_| {
357 DecodeError::InvalidFormat("V1 chunk trace_id bin len read failure".to_owned())
358 })?;
359 if len != TRACE_ID_LEN {
360 return Err(DecodeError::InvalidFormat(format!(
361 "V1 chunk trace_id must be {TRACE_ID_LEN} bytes, got {len}"
362 )));
363 }
364 let bytes = buf
365 .try_slice_and_advance(TRACE_ID_LEN as usize)
366 .ok_or_else(|| {
367 DecodeError::InvalidFormat("V1 chunk trace_id payload truncated".to_owned())
368 })?;
369 let slice: &[u8] = bytes.borrow();
370 chunk.trace_id.copy_from_slice(slice);
371 saw_trace_id = true;
372 }
373 chunk_key::SPANS => {
374 let count = decode::read_array_len(buf.as_mut_slice()).map_err(|_| {
375 DecodeError::InvalidFormat("V1 chunk spans array len read failure".to_owned())
376 })?;
377 let mut spans = Vec::with_capacity(buf.capped_capacity(count as usize));
378 for _ in 0..count {
379 spans.push(span::decode_span(buf, table)?);
380 }
381 chunk.spans = spans;
382 saw_spans = true;
383 }
384 chunk_key::ORIGIN => chunk.origin = read_interned_string(buf, table)?,
385 chunk_key::PRIORITY => {
386 let v: i64 = decode::read_int(buf.as_mut_slice()).map_err(|_| {
387 DecodeError::InvalidFormat("V1 chunk priority read failure".to_owned())
388 })?;
389 chunk.priority = Some(i32::try_from(v).map_err(|_| {
390 DecodeError::InvalidFormat(format!("V1 chunk priority {v} exceeds i32 range"))
391 })?);
392 }
393 chunk_key::SAMPLING_MECHANISM => {
394 let v: u64 = decode::read_int(buf.as_mut_slice()).map_err(|_| {
395 DecodeError::InvalidFormat(
396 "V1 chunk sampling_mechanism read failure".to_owned(),
397 )
398 })?;
399 chunk.sampling_mechanism = Some(u32::try_from(v).map_err(|_| {
400 DecodeError::InvalidFormat(format!(
401 "V1 chunk sampling_mechanism {v} exceeds u32::MAX"
402 ))
403 })?);
404 }
405 chunk_key::ATTRIBUTES => {
406 chunk.attributes = span::read_attributes_map(buf, table)?;
407 }
408 chunk_key::DROPPED_TRACE => {
409 chunk.dropped_trace = decode::read_bool(buf.as_mut_slice()).map_err(|_| {
410 DecodeError::InvalidFormat(
411 "V1 chunk dropped_trace bool read failure".to_owned(),
412 )
413 })?;
414 }
415 _unknown => skip_unknown_value(buf, table)?,
416 }
417 }
418
419 if !saw_trace_id {
420 return Err(DecodeError::InvalidFormat(
421 "V1 chunk is missing trace_id".to_owned(),
422 ));
423 }
424 if !saw_spans {
425 return Err(DecodeError::InvalidFormat(
426 "V1 chunk is missing spans array".to_owned(),
427 ));
428 }
429
430 Ok(chunk)
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use crate::msgpack_encoder::v1::to_vec_from_v1;
437 use crate::span::v1::{
438 AttributeValue, Span as V1Span, SpanBytes as V1SpanBytes, SpanKind, TraceChunkBytes,
439 TracerPayloadBytes,
440 };
441 use crate::span::vec_map::VecMap;
442 use bolero::check;
443 use libdd_tinybytes::{Bytes, BytesString};
444
445 fn bs(s: &str) -> BytesString {
446 BytesString::from_slice(s.as_bytes()).expect("test string must fit in BytesString")
447 }
448
449 fn sample_payload() -> TracerPayloadBytes {
450 let mut attrs = VecMap::<BytesString, AttributeValue<_>>::new();
451 attrs.insert(bs("http.method"), AttributeValue::String(bs("GET")));
452 attrs.insert(bs("http.status"), AttributeValue::Int(200));
453 attrs.insert(bs("is_root"), AttributeValue::Bool(true));
454 attrs.insert(bs("ratio"), AttributeValue::Float(0.75));
455 attrs.insert(
456 bs("ids"),
457 AttributeValue::List(vec![AttributeValue::Int(1), AttributeValue::Int(2)]),
458 );
459
460 let span = V1Span {
461 service: bs("svc"),
462 name: bs("GET /users"),
463 resource: bs("/users"),
464 r#type: bs("web"),
465 span_id: 42,
466 parent_id: 7,
467 start: 1_700_000_000_000,
468 duration: 1_500,
469 error: true,
470 span_kind: SpanKind::Server,
471 env: bs("prod"),
472 version: bs("1.2.3"),
473 component: bs("net/http"),
474 attributes: attrs,
475 ..Default::default()
476 };
477
478 let mut chunk_attrs = VecMap::<BytesString, AttributeValue<_>>::new();
479 chunk_attrs.insert(bs("_dd.p.dm"), AttributeValue::String(bs("-1")));
480
481 let chunk = TraceChunkBytes {
482 trace_id: [1u8; 16],
483 priority: Some(1),
484 origin: bs("synthetic"),
485 sampling_mechanism: Some(2),
486 dropped_trace: false,
487 attributes: chunk_attrs,
488 spans: vec![span],
489 };
490
491 TracerPayloadBytes {
492 language_name: bs("rust"),
493 language_version: bs("1.87"),
494 tracer_version: bs("9.9.9"),
495 runtime_id: bs("abcd-1234"),
496 env: bs("prod"),
497 hostname: bs("host-1"),
498 app_version: bs("1.2.3"),
499 chunks: vec![chunk],
500 ..Default::default()
501 }
502 }
503
504 #[test]
505 fn roundtrip_full_payload() {
506 let original = sample_payload();
507 let bytes = to_vec_from_v1(&original);
508 let payload_len = bytes.len();
509 let (decoded, consumed) =
510 from_bytes(Bytes::from(bytes)).expect("decoder should succeed on encoder output");
511
512 assert_eq!(consumed, payload_len, "decoder should consume all bytes");
513
514 assert_eq!(decoded.language_name.as_str(), "rust");
516 assert_eq!(decoded.language_version.as_str(), "1.87");
517 assert_eq!(decoded.tracer_version.as_str(), "9.9.9");
518 assert_eq!(decoded.runtime_id.as_str(), "abcd-1234");
519 assert_eq!(decoded.env.as_str(), "prod");
520 assert_eq!(decoded.hostname.as_str(), "host-1");
521 assert_eq!(decoded.app_version.as_str(), "1.2.3");
522
523 assert_eq!(decoded.chunks.len(), 1);
525 let chunk = &decoded.chunks[0];
526 assert_eq!(chunk.trace_id, [1u8; 16]);
527 assert_eq!(chunk.priority, Some(1));
528 assert_eq!(chunk.sampling_mechanism, Some(2));
529 assert_eq!(chunk.origin.as_str(), "synthetic");
530 assert_eq!(chunk.attributes.len(), 1);
531
532 assert_eq!(chunk.spans.len(), 1);
534 let span = &chunk.spans[0];
535 assert_eq!(span.service.as_str(), "svc");
536 assert_eq!(span.name.as_str(), "GET /users");
537 assert_eq!(span.resource.as_str(), "/users");
538 assert_eq!(span.r#type.as_str(), "web");
539 assert_eq!(span.span_id, 42);
540 assert_eq!(span.parent_id, 7);
541 assert_eq!(span.start, 1_700_000_000_000);
542 assert_eq!(span.duration, 1_500);
543 assert!(span.error);
544 assert_eq!(span.span_kind, SpanKind::Server);
545 assert_eq!(span.env.as_str(), "prod");
546 assert_eq!(span.version.as_str(), "1.2.3");
547 assert_eq!(span.component.as_str(), "net/http");
548 assert_eq!(span.attributes.len(), 5);
549 }
550
551 #[test]
552 fn empty_payload_roundtrip() {
553 let original = TracerPayloadBytes::default();
554 let bytes = to_vec_from_v1(&original);
555 let (decoded, _) =
556 from_bytes(Bytes::from(bytes)).expect("decoder should succeed on empty payload");
557 assert!(decoded.chunks.is_empty());
558 assert!(decoded.language_name.as_str().is_empty());
559 }
560
561 #[test]
562 fn missing_chunks_field_is_rejected() {
563 let bytes = vec![0x81, 0x07, 0xa1, 0x78];
566 let err = from_bytes(Bytes::from(bytes)).expect_err("missing chunks must error");
567 assert!(matches!(err, DecodeError::InvalidFormat(_)));
568 }
569
570 #[test]
571 fn truncated_trace_id_is_rejected_not_panicking() {
572 let bytes = vec![
576 0x81,
577 trace_key::CHUNKS,
578 0x91, 0x81, chunk_key::TRACE_ID,
581 0xc4, 0x10, 0x01,
584 0x02,
585 0x03,
586 0x04, ];
588 let err = from_bytes(Bytes::from(bytes)).expect_err("truncated trace_id must error");
589 assert!(matches!(err, DecodeError::InvalidFormat(_)));
590 }
591
592 #[test]
593 fn string_interning_resolves_across_chunks() {
594 let span_a = V1Span {
597 service: bs("shared"),
598 name: bs("a"),
599 span_id: 1,
600 start: 1,
601 ..Default::default()
602 };
603 let span_b = V1Span {
604 service: bs("shared"),
605 name: bs("b"),
606 span_id: 2,
607 start: 1,
608 ..Default::default()
609 };
610 let payload = TracerPayloadBytes {
611 chunks: vec![
612 TraceChunkBytes {
613 trace_id: [1u8; 16],
614 spans: vec![span_a],
615 ..Default::default()
616 },
617 TraceChunkBytes {
618 trace_id: [2u8; 16],
619 spans: vec![span_b],
620 ..Default::default()
621 },
622 ],
623 ..Default::default()
624 };
625 let bytes = to_vec_from_v1(&payload);
626 let (decoded, _) =
627 from_bytes(Bytes::from(bytes)).expect("decoder should resolve interned strings");
628 assert_eq!(decoded.chunks[0].spans[0].service.as_str(), "shared");
629 assert_eq!(decoded.chunks[1].spans[0].service.as_str(), "shared");
630 }
631
632 #[test]
633 fn nested_keyvalue_attribute_roundtrip() {
634 let mut inner = VecMap::<BytesString, AttributeValue<_>>::new();
635 inner.insert(bs("k"), AttributeValue::String(bs("v")));
636 let mut attrs = VecMap::<BytesString, AttributeValue<_>>::new();
637 attrs.insert(bs("nested"), AttributeValue::KeyValue(inner));
638
639 let span = V1Span {
640 service: bs("svc"),
641 name: bs("op"),
642 span_id: 1,
643 start: 1,
644 attributes: attrs,
645 ..Default::default()
646 };
647 let payload = TracerPayloadBytes {
648 chunks: vec![TraceChunkBytes {
649 trace_id: [0u8; 16],
650 spans: vec![span],
651 ..Default::default()
652 }],
653 ..Default::default()
654 };
655 let bytes = to_vec_from_v1(&payload);
656 let (decoded, _) = from_bytes(Bytes::from(bytes)).expect("nested KeyValue roundtrip");
657
658 let decoded_attrs = &decoded.chunks[0].spans[0].attributes;
659 match decoded_attrs.get(&bs("nested")) {
660 Some(AttributeValue::KeyValue(map)) => {
661 assert_eq!(map.len(), 1);
662 match map.get(&bs("k")) {
663 Some(AttributeValue::String(v)) => assert_eq!(v.as_str(), "v"),
664 _ => panic!("inner value should be String"),
665 }
666 }
667 _ => panic!("attribute should decode as KeyValue"),
668 }
669 }
670
671 #[test]
676 #[cfg_attr(miri, ignore)]
677 fn fuzz_from_bytes() {
678 check!()
679 .with_type::<(
680 String, String, String, String, String, String, String, String, u64, u64, u64, bool, )>()
693 .cloned()
694 .for_each(
695 |(
696 lang,
697 payload_env,
698 service,
699 name,
700 resource,
701 span_env,
702 attr_key,
703 attr_value,
704 span_id,
705 parent_id,
706 start,
707 error,
708 )| {
709 let bs = |s: &str| BytesString::from_slice(s.as_ref()).unwrap();
710 let mut attrs = VecMap::<BytesString, AttributeValue<_>>::new();
711 attrs.insert(bs(&attr_key), AttributeValue::String(bs(&attr_value)));
712
713 let span = V1SpanBytes {
714 service: bs(&service),
715 name: bs(&name),
716 resource: bs(&resource),
717 span_id,
718 parent_id,
719 start: start as i64,
720 error,
721 env: bs(&span_env),
722 attributes: attrs,
723 ..Default::default()
724 };
725
726 let payload = TracerPayloadBytes {
727 language_name: bs(&lang),
728 env: bs(&payload_env),
729 chunks: vec![TraceChunkBytes {
730 trace_id: [0xab; 16],
731 spans: vec![span],
732 ..Default::default()
733 }],
734 ..Default::default()
735 };
736
737 let encoded = to_vec_from_v1(&payload);
738 let result = from_bytes(Bytes::from(encoded));
739 assert!(
740 result.is_ok(),
741 "decoder rejected its own encoded output: {result:?}"
742 );
743 },
744 );
745 }
746
747 use rmp::encode::{self, ByteBuf};
755
756 fn wkey(buf: &mut ByteBuf, k: u8) {
758 encode::write_uint(buf, k as u64).unwrap();
759 }
760
761 #[test]
770 fn unknown_keys_are_skipped_at_every_level() {
771 let mut span_link = ByteBuf::new();
772 encode::write_map_len(&mut span_link, 2).unwrap();
773 wkey(&mut span_link, 55); encode::write_bool(&mut span_link, false).unwrap();
775 wkey(&mut span_link, span_link_key::SPAN_ID);
776 encode::write_uint(&mut span_link, 777).unwrap();
777
778 let mut span_event = ByteBuf::new();
779 encode::write_map_len(&mut span_event, 2).unwrap();
780 wkey(&mut span_event, 66); encode::write_uint(&mut span_event, 999).unwrap();
782 wkey(&mut span_event, span_event_key::TIME);
783 encode::write_uint(&mut span_event, 123).unwrap();
784
785 let mut span = ByteBuf::new();
786 encode::write_map_len(&mut span, 6).unwrap();
787 wkey(&mut span, span_key::SPAN_ID);
788 encode::write_uint(&mut span, 42).unwrap();
789 wkey(&mut span, span_key::START);
790 encode::write_uint(&mut span, 100).unwrap();
791 wkey(&mut span, 88); encode::write_f64(&mut span, 2.5).unwrap();
793 wkey(&mut span, span_key::SERVICE);
794 encode::write_uint(&mut span, 2).unwrap(); wkey(&mut span, span_key::SPAN_LINKS);
796 encode::write_array_len(&mut span, 1).unwrap();
797 span.as_mut_vec().extend_from_slice(&span_link.into_vec());
798 wkey(&mut span, span_key::SPAN_EVENTS);
799 encode::write_array_len(&mut span, 1).unwrap();
800 span.as_mut_vec().extend_from_slice(&span_event.into_vec());
801
802 let mut chunk = ByteBuf::new();
803 encode::write_map_len(&mut chunk, 3).unwrap();
804 wkey(&mut chunk, 77); encode::write_array_len(&mut chunk, 3).unwrap();
806 encode::write_uint(&mut chunk, 1).unwrap();
807 encode::write_str(&mut chunk, "buried").unwrap(); encode::write_map_len(&mut chunk, 1).unwrap();
809 encode::write_uint(&mut chunk, 5).unwrap();
810 encode::write_bool(&mut chunk, true).unwrap();
811 wkey(&mut chunk, chunk_key::TRACE_ID);
812 encode::write_bin(&mut chunk, &[9u8; 16]).unwrap();
813 wkey(&mut chunk, chunk_key::SPANS);
814 encode::write_array_len(&mut chunk, 1).unwrap();
815 chunk.as_mut_vec().extend_from_slice(&span.into_vec());
816
817 let mut buf = ByteBuf::new();
818 encode::write_map_len(&mut buf, 3).unwrap();
819 wkey(&mut buf, 99); encode::write_str(&mut buf, "ghost").unwrap();
821 wkey(&mut buf, trace_key::ENV_REF);
822 encode::write_str(&mut buf, "prod").unwrap(); wkey(&mut buf, trace_key::CHUNKS);
824 encode::write_array_len(&mut buf, 1).unwrap();
825 buf.as_mut_vec().extend_from_slice(&chunk.into_vec());
826 let buf = buf.into_vec();
827
828 let (decoded, consumed) =
829 from_bytes(Bytes::from(buf.clone())).expect("unknown keys must be skipped");
830 assert_eq!(
831 consumed,
832 buf.len(),
833 "decoder must consume every skipped value"
834 );
835 assert_eq!(decoded.env.as_str(), "prod");
836 let chunk = &decoded.chunks[0];
837 assert_eq!(chunk.trace_id, [9u8; 16]);
838 let span = &chunk.spans[0];
839 assert_eq!(span.span_id, 42);
840 assert_eq!(span.start, 100);
841 assert_eq!(
842 span.service.as_str(),
843 "prod",
844 "back-reference must still resolve correctly: harvesting \"ghost\" and \"buried\" \
845 while skipping unknown fields must not desync the string table"
846 );
847 assert_eq!(span.span_links[0].span_id, 777);
848 assert_eq!(span.span_events[0].time_unix_nano, 123);
849 }
850}