use obs_proto::obs::v1::{Cardinality, Classification, FieldKind, MetricKind, Severity, Tier};
const EVENT_TAG_BYTES: [u8; 3] = [0x8A, 0x88, 0x27];
const FIELD_TAG_BYTES: [u8; 3] = [0x92, 0x88, 0x27];
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct EventOptions {
pub tier: Option<Tier>,
pub default_sev: Option<Severity>,
pub paired_with: Option<String>,
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct FieldOptions {
pub kind: Option<FieldKind>,
pub cardinality: Option<Cardinality>,
pub classification: Option<Classification>,
pub metric: Option<MetricSpec>,
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct MetricSpec {
pub kind: Option<MetricKind>,
pub unit: Option<String>,
pub bounds: Vec<f64>,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CodegenError {
#[error("protoc failed: {0}")]
Protoc(String),
#[error("descriptor set IO: {0}")]
DescriptorIo(#[source] std::io::Error),
#[error("descriptor decode failed: {0}")]
DescriptorDecode(String),
#[error("buffa-build failed: {0}")]
Buffa(String),
#[error("option decode failed for `{path}`: {detail}")]
OptionDecode {
path: String,
detail: String,
},
#[error("output IO: {0}")]
OutputIo(#[source] std::io::Error),
}
#[doc(hidden)]
pub fn read_event_options(bytes: &[u8], path: &str) -> Result<Option<EventOptions>, CodegenError> {
let Some(payload) = find_tag_payload(bytes, &EVENT_TAG_BYTES) else {
return Ok(None);
};
let mut out = EventOptions::default();
walk_message(payload, |field, kind, value| {
match (field, kind) {
(1, WireKind::Varint) => {
if let Some(v) = value.varint() {
out.tier = decode_tier(v as i32);
}
}
(2, WireKind::Varint) => {
if let Some(v) = value.varint() {
out.default_sev = decode_severity(v as i32);
}
}
(3, WireKind::Length) => {
if let Some(s) = value.length()
&& let Ok(s) = std::str::from_utf8(s)
{
out.paired_with = Some(s.to_string());
}
}
_ => {}
}
})
.map_err(|detail| CodegenError::OptionDecode {
path: path.to_string(),
detail: detail.to_string(),
})?;
Ok(Some(out))
}
#[doc(hidden)]
pub fn read_field_options(bytes: &[u8], path: &str) -> Result<Option<FieldOptions>, CodegenError> {
let Some(payload) = find_tag_payload(bytes, &FIELD_TAG_BYTES) else {
return Ok(None);
};
let mut out = FieldOptions::default();
walk_message(payload, |field, kind, value| match (field, kind) {
(1, WireKind::Varint) => {
out.kind = value.varint().and_then(|v| decode_field_kind(v as i32))
}
(2, WireKind::Varint) => {
out.cardinality = value.varint().and_then(|v| decode_cardinality(v as i32))
}
(3, WireKind::Varint) => {
out.classification = value.varint().and_then(|v| decode_classification(v as i32))
}
(4, WireKind::Length) => {
if let Some(submsg) = value.length() {
let mut spec = MetricSpec::default();
let _ = walk_message(submsg, |sf, sk, sv| match (sf, sk) {
(1, WireKind::Varint) => {
spec.kind = sv.varint().and_then(|v| decode_metric_kind(v as i32))
}
(2, WireKind::Length) => {
if let Some(s) = sv.length()
&& let Ok(s) = std::str::from_utf8(s)
{
spec.unit = Some(s.to_string());
}
}
(3, WireKind::Length) => {
if let Some(s) = sv.length() {
for chunk in s.chunks_exact(8) {
if let Ok(arr) = <[u8; 8]>::try_from(chunk) {
spec.bounds.push(f64::from_le_bytes(arr));
}
}
}
}
(3, WireKind::Fixed64) => {
if let Some(b) = sv.fixed64() {
spec.bounds.push(f64::from_le_bytes(b));
}
}
_ => {}
});
out.metric = Some(spec);
}
}
_ => {}
})
.map_err(|detail| CodegenError::OptionDecode {
path: path.to_string(),
detail: detail.to_string(),
})?;
Ok(Some(out))
}
fn decode_tier(i: i32) -> Option<Tier> {
Some(match i {
1 => Tier::Log,
2 => Tier::Metric,
3 => Tier::Trace,
4 => Tier::Audit,
_ => Tier::Unspecified,
})
}
fn decode_severity(i: i32) -> Option<Severity> {
Some(match i {
1 => Severity::Trace,
2 => Severity::Debug,
3 => Severity::Info,
4 => Severity::Warn,
5 => Severity::Error,
6 => Severity::Fatal,
_ => Severity::Unspecified,
})
}
fn decode_field_kind(i: i32) -> Option<FieldKind> {
Some(match i {
1 => FieldKind::Label,
2 => FieldKind::Attribute,
3 => FieldKind::Measurement,
4 => FieldKind::TraceId,
5 => FieldKind::SpanId,
6 => FieldKind::ParentSpanId,
7 => FieldKind::TimestampNs,
8 => FieldKind::DurationNs,
9 => FieldKind::Forensic,
_ => FieldKind::Unspecified,
})
}
fn decode_cardinality(i: i32) -> Option<Cardinality> {
Some(match i {
1 => Cardinality::Low,
2 => Cardinality::Medium,
3 => Cardinality::High,
4 => Cardinality::Unbounded,
_ => Cardinality::Unspecified,
})
}
fn decode_classification(i: i32) -> Option<Classification> {
Some(match i {
1 => Classification::Internal,
2 => Classification::Pii,
3 => Classification::Secret,
_ => Classification::Unspecified,
})
}
fn decode_metric_kind(i: i32) -> Option<MetricKind> {
Some(match i {
1 => MetricKind::Counter,
2 => MetricKind::Gauge,
3 => MetricKind::Histogram,
_ => MetricKind::Unspecified,
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum WireKind {
Varint,
Fixed64,
Length,
Fixed32,
}
enum WireValue<'a> {
Varint(u64),
Fixed64([u8; 8]),
Length(&'a [u8]),
#[allow(dead_code)] Fixed32([u8; 4]),
}
impl<'a> WireValue<'a> {
fn varint(&self) -> Option<u64> {
match self {
Self::Varint(v) => Some(*v),
_ => None,
}
}
fn fixed64(&self) -> Option<[u8; 8]> {
match self {
Self::Fixed64(v) => Some(*v),
_ => None,
}
}
fn length(&self) -> Option<&'a [u8]> {
match self {
Self::Length(s) => Some(*s),
_ => None,
}
}
}
#[derive(Debug)]
struct WireScanError(&'static str);
impl std::fmt::Display for WireScanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
fn find_tag_payload<'a>(bytes: &'a [u8], tag: &[u8]) -> Option<&'a [u8]> {
let mut i = 0;
while i + tag.len() <= bytes.len() {
if &bytes[i..i + tag.len()] == tag {
let mut j = i + tag.len();
let (len, consumed) = read_varint(&bytes[j..]).ok()?;
j += consumed;
let start = j;
let end = start.checked_add(len as usize)?;
if end > bytes.len() {
return None;
}
return Some(&bytes[start..end]);
}
i += 1;
}
None
}
fn walk_message<F>(payload: &[u8], mut visit: F) -> Result<(), WireScanError>
where
F: FnMut(u32, WireKind, WireValue<'_>),
{
let mut i = 0;
while i < payload.len() {
let (tag, consumed) =
read_varint(&payload[i..]).map_err(|_| WireScanError("invalid tag varint"))?;
i += consumed;
let field = (tag >> 3) as u32;
let wire = tag & 0b111;
match wire {
0 => {
let (v, c) = read_varint(&payload[i..])
.map_err(|_| WireScanError("invalid value varint"))?;
i += c;
visit(field, WireKind::Varint, WireValue::Varint(v));
}
1 => {
if i + 8 > payload.len() {
return Err(WireScanError("truncated fixed64"));
}
let mut arr = [0u8; 8];
arr.copy_from_slice(&payload[i..i + 8]);
i += 8;
visit(field, WireKind::Fixed64, WireValue::Fixed64(arr));
}
2 => {
let (len, c) =
read_varint(&payload[i..]).map_err(|_| WireScanError("invalid LEN varint"))?;
i += c;
let end = i
.checked_add(len as usize)
.ok_or(WireScanError("LEN overflow"))?;
if end > payload.len() {
return Err(WireScanError("truncated LEN payload"));
}
visit(field, WireKind::Length, WireValue::Length(&payload[i..end]));
i = end;
}
5 => {
if i + 4 > payload.len() {
return Err(WireScanError("truncated fixed32"));
}
let mut arr = [0u8; 4];
arr.copy_from_slice(&payload[i..i + 4]);
i += 4;
visit(field, WireKind::Fixed32, WireValue::Fixed32(arr));
}
_ => return Err(WireScanError("unknown wire type")),
}
}
Ok(())
}
fn read_varint(bytes: &[u8]) -> Result<(u64, usize), &'static str> {
let mut v: u64 = 0;
let mut shift = 0u32;
for (idx, b) in bytes.iter().enumerate().take(10) {
v |= ((*b & 0x7f) as u64) << shift;
if (*b & 0x80) == 0 {
return Ok((v, idx + 1));
}
shift += 7;
}
Err("varint too long")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_decode_event_options_from_spike_payload() {
let bytes = [0x8a, 0x88, 0x27, 0x04, 0x08, 0x01, 0x10, 0x03];
let opts = read_event_options(&bytes, "test").unwrap().unwrap();
assert_eq!(opts.tier, Some(Tier::Log));
assert_eq!(opts.default_sev, Some(Severity::Info));
}
#[test]
fn test_should_decode_field_options_from_spike_payload() {
let bytes = [0x92, 0x88, 0x27, 0x06, 0x08, 0x02, 0x10, 0x03, 0x18, 0x02];
let opts = read_field_options(&bytes, "test").unwrap().unwrap();
assert_eq!(opts.kind, Some(FieldKind::Attribute));
assert_eq!(opts.cardinality, Some(Cardinality::High));
assert_eq!(opts.classification, Some(Classification::Pii));
}
#[test]
fn test_should_return_none_when_tag_absent() {
let bytes = [0x00, 0x01, 0x02];
assert!(read_event_options(&bytes, "test").unwrap().is_none());
}
}