diff --git a/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs b/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs index 055c36d2f1..9da0acf308 100644 --- a/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs +++ b/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs @@ -847,4 +847,57 @@ mod tests { assert_eq!(span.span_links[0].span_id, 777); assert_eq!(span.span_events[0].time_unix_nano, 123); } + + /// A payload with one span whose attributes are `[key, type, value]` triplets written by + /// `write_value`. + fn payload_with_double(write_value: impl FnOnce(&mut ByteBuf)) -> Vec { + let mut buf = ByteBuf::new(); + encode::write_map_len(&mut buf, 1).unwrap(); + wkey(&mut buf, trace_key::CHUNKS); + encode::write_array_len(&mut buf, 1).unwrap(); + encode::write_map_len(&mut buf, 2).unwrap(); + wkey(&mut buf, chunk_key::TRACE_ID); + encode::write_bin(&mut buf, &[1u8; 16]).unwrap(); + wkey(&mut buf, chunk_key::SPANS); + encode::write_array_len(&mut buf, 1).unwrap(); + encode::write_map_len(&mut buf, 3).unwrap(); + wkey(&mut buf, span_key::SPAN_ID); + encode::write_uint(&mut buf, 1).unwrap(); + wkey(&mut buf, span_key::START); + encode::write_uint(&mut buf, 100).unwrap(); + wkey(&mut buf, span_key::ATTRIBUTES); + encode::write_array_len(&mut buf, 3).unwrap(); + encode::write_str(&mut buf, "ratio").unwrap(); + encode::write_uint(&mut buf, u64::from(ANY_VALUE_KEY_DOUBLE)).unwrap(); + write_value(&mut buf); + buf.into_vec() + } + + fn decoded_ratio(bytes: Vec) -> Result { + let (payload, _) = from_bytes(Bytes::from(bytes))?; + match payload.chunks[0].spans[0].attributes.get("ratio") { + Some(AttributeValue::Float(f)) => Ok(*f), + other => panic!("unexpected attribute {other:?}"), + } + } + + #[test] + fn double_attribute_accepts_float32_and_float64() { + // Tracers may write a double as float32 when that holds it exactly, as the agent reads. + let f32_bytes = payload_with_double(|b| encode::write_f32(b, 1.5).unwrap()); + assert_eq!(decoded_ratio(f32_bytes).unwrap(), 1.5); + let f64_bytes = payload_with_double(|b| encode::write_f64(b, 0.1).unwrap()); + assert_eq!(decoded_ratio(f64_bytes).unwrap(), 0.1); + } + + #[test] + fn double_attribute_rejects_integers() { + let int_bytes = payload_with_double(|b| { + encode::write_uint(b, 1).unwrap(); + }); + assert!(matches!( + decoded_ratio(int_bytes), + Err(DecodeError::InvalidFormat(m)) if m.contains("Double") + )); + } } diff --git a/libdd-trace-utils/src/msgpack_decoder/v1/span.rs b/libdd-trace-utils/src/msgpack_decoder/v1/span.rs index 3cd531ce2f..83cb4ff6b3 100644 --- a/libdd-trace-utils/src/msgpack_decoder/v1/span.rs +++ b/libdd-trace-utils/src/msgpack_decoder/v1/span.rs @@ -15,7 +15,8 @@ use crate::msgpack_decoder::decode::error::DecodeError; use crate::span::DeserializableTraceData; use crate::span::v1::{AttributeValue, Span, SpanEvent, SpanKind, SpanLink, ThinVec}; use crate::span::vec_map::VecMap; -use rmp::decode; +use rmp::Marker; +use rmp::decode::{self, RmpRead}; use std::borrow::Borrow; /// Decodes a V1 span (msgpack map with integer keys) into a [`Span`]. @@ -142,6 +143,17 @@ where Ok(map) } +/// Reads a double written as float64, or as float32 when that holds the value exactly (as the +/// agent's msgp decoder accepts, and some tracers emit to save space). Integers are rejected. +fn read_double(buf: &mut &[u8]) -> Result { + let fail = || DecodeError::InvalidFormat("V1 attribute Double read failure".to_owned()); + match decode::read_marker(buf).map_err(|_| fail())? { + Marker::F32 => buf.read_data_f32().map(f64::from).map_err(|_| fail()), + Marker::F64 => buf.read_data_f64().map_err(|_| fail()), + _ => Err(fail()), + } +} + /// Reads `[type_uint8, value]` and dispatches by type discriminant. Recurses into `Array` and /// `KeyValueList`. pub(super) fn read_typed_attribute_value( @@ -163,12 +175,7 @@ where })?; Ok(AttributeValue::Bool(b)) } - ANY_VALUE_KEY_DOUBLE => { - let f = decode::read_f64(buf.as_mut_slice()).map_err(|_| { - DecodeError::InvalidFormat("V1 attribute Double read failure".to_owned()) - })?; - Ok(AttributeValue::Float(f)) - } + ANY_VALUE_KEY_DOUBLE => Ok(AttributeValue::Float(read_double(buf.as_mut_slice())?)), ANY_VALUE_KEY_INT64 => { // Encoder writes signed via `write_sint`, which can emit any int marker. `read_int` // accepts any integer marker that fits in i64.