use crate::ingress::IngressContextJson;
pub const LIMIT: usize = 4096;
pub const KEYS: [&str; 4] = ["trace-info-bin", "ldc-info-bin", "request-info-bin", "profuse-context-bin"];
#[derive(Debug)]
pub enum EncodeError { TooLarge, InvalidRange }
impl std::fmt::Display for EncodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self { Self::TooLarge => "context JSON exceeds 4096 bytes", Self::InvalidRange => "invalid validated context range" })
}
}
impl std::error::Error for EncodeError {}
pub struct Encoded {
bytes: [u8; LIMIT],
lengths: [usize; 4],
used: usize,
profuse: bool,
}
impl Encoded {
fn append(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
let end = self.used.checked_add(bytes.len()).filter(|end| *end <= LIMIT).ok_or(EncodeError::TooLarge)?;
self.bytes[self.used..end].copy_from_slice(bytes);
self.used = end;
Ok(())
}
fn string(&mut self, value: &str) -> Result<(), EncodeError> {
self.append(b"\"")?;
for byte in value.bytes() {
match byte {
b'"' => self.append(br#"\""#)?,
b'\\' => self.append(br#"\\"#)?,
0..=31 => {
const HEX: &[u8] = b"0123456789abcdef";
self.append(&[b'\\', b'u', b'0', b'0', HEX[(byte >> 4) as usize], HEX[(byte & 15) as usize]])?;
}
_ => self.append(&[byte])?,
}
}
self.append(b"\"")
}
pub fn values(&self) -> impl Iterator<Item = (&'static str, &[u8])> {
let mut at = 0;
self.lengths.iter().enumerate().filter_map(move |(index, length)| {
let start = at; at += length;
(index != 3 || self.profuse).then_some((KEYS[index], &self.bytes[start..at]))
})
}
}
pub fn encode(raw: Option<&IngressContextJson>, trace: &str, rpc: &str, zone: &str, idc: &str, env: &str, request: &str, call: &str, deadline: i64) -> Result<Encoded, EncodeError> {
let mut out = Encoded { bytes: [0; LIMIT], lengths: [0; 4], used: 0, profuse: raw.is_some_and(|raw| raw.profuse_context.is_some()) };
if let Some(raw) = raw {
let source = raw.trace_info.as_bytes();
let rpc_range = &raw.rpc_value;
let trace_range = raw.trace_value.as_ref();
let mut patches = [(rpc_range.clone(), rpc), (trace_range.cloned().unwrap_or(0..0), trace)];
if trace_range.is_some() && patches[1].0.start < patches[0].0.start { patches.swap(0, 1); }
let count = if trace_range.is_some() { 2 } else { 1 };
let mut at = 0;
for (range, value) in &patches[..count] {
out.append(source.get(at..range.start).ok_or(EncodeError::InvalidRange)?)?;
out.string(value)?;
at = range.end;
}
if trace_range.is_none() {
out.append(source.get(at..source.len().checked_sub(1).ok_or(EncodeError::InvalidRange)?).ok_or(EncodeError::InvalidRange)?)?;
out.append(b",\"traceId\":")?; out.string(trace)?; out.append(b"}")?;
} else { out.append(source.get(at..).ok_or(EncodeError::InvalidRange)?)?; }
} else {
out.append(b"{\"traceId\":")?; out.string(trace)?; out.append(b",\"rpcId\":")?; out.string(rpc)?; out.append(b"}")?;
}
out.lengths[0] = out.used;
if let Some(raw) = raw { out.append(raw.ldc_info.as_bytes())?; }
else {
out.append(b"{\"zone\":")?; out.string(zone)?; out.append(b",\"idc\":")?; out.string(idc)?; out.append(b",\"env\":")?; out.string(env)?; out.append(b"}")?;
}
out.lengths[1] = out.used - out.lengths[0];
out.append(b"{\"requestId\":")?; out.string(request)?; out.append(b",\"callId\":")?; out.string(call)?; out.append(b",\"deadlineUnixMs\":")?;
let mut digits = [0u8; 20];
let mut n = deadline.unsigned_abs(); let mut at = digits.len();
loop { at -= 1; digits[at] = b'0' + (n % 10) as u8; n /= 10; if n == 0 { break; } }
if deadline < 0 { out.append(b"-")?; }
out.append(&digits[at..])?; out.append(b"}")?;
out.lengths[2] = out.used - out.lengths[0] - out.lengths[1];
if let Some(value) = raw.and_then(|raw| raw.profuse_context.as_deref()) { out.append(value.as_bytes())?; out.lengths[3] = value.len(); }
Ok(out)
}
impl Encoded {
pub fn metadata(&self, memory: &saddle_admission::RequestMemory) -> Result<saddle_admission::ReadOnlyInput<Vec<tonic::metadata::MetadataValue<tonic::metadata::Binary>>>, saddle_admission::AdmissionError> {
let count = if self.profuse { 4 } else { 3 };
memory.framework_output(|builder| builder.list(count, |index, builder| {
let (_, value) = self.values().nth(index).unwrap();
let encoded_len = (value.len() * 4 + 2) / 3;
let layout = std::alloc::Layout::array::<u8>(encoded_len).map_err(|_| saddle_admission::AdmissionError::SizeOverflow)?;
let metadata = builder.exact_value(layout, || tonic::metadata::MetadataValue::from_bytes(value))?;
builder.exact_value(std::alloc::Layout::new::<[usize; 3]>(), || metadata.clone())
}))
}
}
pub struct ChildIds {
rpc: [u8; 280],
call: [u8; 280],
rpc_len: usize,
call_len: usize,
}
impl ChildIds {
pub fn new(rpc: &str, call: &str, number: u64) -> Result<Self, EncodeError> {
let mut digits = [0u8; 20]; let mut at = digits.len(); let mut n = number;
loop { at -= 1; digits[at] = b'0' + (n % 10) as u8; n /= 10; if n == 0 { break; } }
let digits = &digits[at..];
let mut result = Self { rpc: [0; 280], call: [0; 280], rpc_len: 0, call_len: 0 };
for (parent, separator, target, length) in [(rpc, b'.', &mut result.rpc, &mut result.rpc_len), (call, b'-', &mut result.call, &mut result.call_len)] {
*length = parent.len() + 1 + digits.len();
if *length > target.len() { return Err(EncodeError::TooLarge); }
target[..parent.len()].copy_from_slice(parent.as_bytes()); target[parent.len()] = separator;
target[parent.len()+1..*length].copy_from_slice(digits);
}
Ok(result)
}
pub fn rpc(&self) -> &str { std::str::from_utf8(&self.rpc[..self.rpc_len]).unwrap() }
pub fn call(&self) -> &str { std::str::from_utf8(&self.call[..self.call_len]).unwrap() }
}
#[cfg(test)]
mod tests {
use super::*;
fn raw(trace: &str, profuse: Option<&str>) -> IngressContextJson {
let rpc = trace.find("\"parent\"").unwrap();
let id = trace.find("\"root\"").unwrap();
IngressContextJson { trace_info: trace.into(), rpc_value: rpc..rpc+8, trace_value: Some(id..id+6), ldc_info: r#"{"zone":"z","idc":"i","env":"e","extra":[null,"中文",9007199254740993123456789]}"#.into(), profuse_context: profuse.map(str::to_owned) }
}
#[test]
fn replaces_only_framework_identity_and_keeps_large_integer_spelling() {
for source in [r#"{"rpcId":"parent","extension":{"n":9007199254740993123456789},"traceId":"root"}"#, r#"{"traceId":"root","extension":[null,"中文"],"rpcId":"parent"}"#] {
let input = raw(source, Some("{}"));
let value = encode(Some(&input), "root", "parent.2", "z", "i", "e", "request", "call-2", 123).unwrap();
let values: Vec<_> = value.values().collect();
assert_eq!(values.len(), 4);
assert_eq!(std::str::from_utf8(values[0].1).unwrap(), source.replace("\"parent\"", "\"parent.2\""));
assert_eq!(values[1].1, input.ldc_info.as_bytes());
assert_eq!(values[3].1, b"{}");
}
}
#[test]
fn absence_and_empty_are_distinct_and_actual_limit_is_enforced() {
let base = encode(None, "t", "r.1", "z", "i", "e", "q", "c-1", 123).unwrap();
assert_eq!(base.values().count(), 3);
let source = r#"{"rpcId":"parent","traceId":"root"}"#;
let mut input = raw(source, Some("{}"));
let initial = encode(Some(&input), "root", "parent.1", "z", "i", "e", "q", "c-1", 123).unwrap().used;
input.profuse_context = Some(format!("{{\"x\":\"{}\"}}", "x".repeat(LIMIT - initial - 6)));
assert_eq!(encode(Some(&input), "root", "parent.1", "z", "i", "e", "q", "c-1", 123).unwrap().used, LIMIT);
input.profuse_context.as_mut().unwrap().push(' ');
assert!(matches!(encode(Some(&input), "root", "parent.1", "z", "i", "e", "q", "c-1", 123), Err(EncodeError::TooLarge)));
}
}