use super::*;
use serde::de::{Deserializer, Visitor};
use serde_json::value::RawValue;
use std::{fmt, ops::Range};
#[derive(Debug)]
struct Text {
bytes: [u8; MAX_ID_BYTES],
len: usize,
oversized: bool,
}
impl Default for Text {
fn default() -> Self {
Self {
bytes: [0; MAX_ID_BYTES],
len: 0,
oversized: false,
}
}
}
impl Text {
fn get(&self) -> &str {
std::str::from_utf8(&self.bytes[..self.len]).expect("copied whole UTF-8")
}
fn identity(&self) -> Result<(), CodecError> {
validate_id(self.get())
}
fn trace(&self) -> Result<(), CodecError> {
if self.oversized {
return Err(error(400, "INVALID_TRACE_ID"));
}
if self.len == 0 {
Ok(())
} else {
validate_trace_id(self.get())
}
}
}
impl<'de> Deserialize<'de> for Text {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct FixedText;
impl<'de> Visitor<'de> for FixedText {
type Value = Text;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a string")
}
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Text, E> {
let mut text = Text::default();
if value.len() > MAX_ID_BYTES {
text.oversized = true;
} else {
text.bytes[..value.len()].copy_from_slice(value.as_bytes());
text.len = value.len();
}
Ok(text)
}
}
deserializer.deserialize_str(FixedText)
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Target {
app: Text,
#[serde(rename = "interfaceId")]
interface_id: Text,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct User {
#[serde(rename = "userId")]
user_id: Text,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Trace {
#[serde(default)]
trace_id: Text,
rpc_id: Text,
}
#[derive(Debug, Deserialize)]
struct Ldc {
zone: Text,
idc: Text,
env: Text,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Context {
#[serde(rename = "userInfo")]
user: User,
#[serde(rename = "traceInfo")]
trace: Trace,
#[serde(rename = "ldcInfo")]
ldc: Ldc,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawContext<'a> {
#[serde(rename = "userInfo")]
user: User,
#[serde(borrow, rename = "traceInfo")]
trace: &'a RawValue,
#[serde(borrow, rename = "ldcInfo")]
ldc: &'a RawValue,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Envelope<'a> {
target: Target,
#[serde(borrow, rename = "profuseGwContext")]
context: RawContext<'a>,
#[serde(borrow, rename = "requestData")]
request_data: &'a RawValue,
#[serde(default, borrow, rename = "profuseContext", deserialize_with = "super::json::present_raw")]
profuse: Option<&'a RawValue>,
}
#[derive(Debug)]
pub struct IngressFacts {
target: Target,
context: Context,
request_data: Range<usize>,
trace_json: Range<usize>,
ldc_json: Range<usize>,
profuse_json: Option<Range<usize>>,
rpc_value: Range<usize>,
trace_value: Option<Range<usize>>,
}
impl IngressFacts {
pub fn interface_id(&self) -> &str {
self.target.interface_id.get()
}
pub fn user_id(&self) -> &str {
self.context.user.user_id.get()
}
pub fn trace_id(&self) -> &str {
self.context.trace.trace_id.get()
}
pub fn rpc_id(&self) -> &str {
self.context.trace.rpc_id.get()
}
pub fn zone(&self) -> &str {
self.context.ldc.zone.get()
}
pub fn idc(&self) -> &str {
self.context.ldc.idc.get()
}
pub fn env(&self) -> &str {
self.context.ldc.env.get()
}
pub fn request_data_range(&self) -> Range<usize> {
self.request_data.clone()
}
}
impl IngressFacts {
pub(super) fn owned_context_json(&self, body: &[u8]) -> IngressContextJson {
let text = |range: Range<usize>| std::str::from_utf8(&body[range]).expect("validated source").to_owned();
IngressContextJson { trace_info: text(self.trace_json.clone()), rpc_value: self.rpc_value.clone(), trace_value: self.trace_value.clone(), ldc_info: text(self.ldc_json.clone()), profuse_context: self.profuse_json.clone().map(text) }
}
}
pub(super) fn recognize_body(body: &[u8], parser_error: impl FnOnce(&serde_json::Error)) -> Result<IngressFacts, CodecError> {
let mut parser_error = Some(parser_error);
let mut parse_error = |original: serde_json::Error| {
if let Some(observer) = parser_error.take() { observer(&original); }
error(400, "INVALID_JSON_ENVELOPE")
};
let envelope: Envelope<'_> = serde_json::from_slice(body).map_err(&mut parse_error)?;
let Envelope { target, context, request_data, profuse } = envelope;
if context.trace.get().len() + context.ldc.get().len() + profuse.map_or(0, |raw| raw.get().len()) > crate::grouped_context::LIMIT {
return Err(error(413, "CONTEXT_TOO_LARGE"));
}
let trace_fields = super::json::object(context.trace.get()).map_err(&mut parse_error)?;
super::json::object(context.ldc.get()).map_err(&mut parse_error)?;
if let Some(raw) = profuse { super::json::object(raw.get()).map_err(&mut parse_error)?; }
let trace: Trace = serde_json::from_str(context.trace.get()).map_err(&mut parse_error)?;
let ldc: Ldc = serde_json::from_str(context.ldc.get()).map_err(&mut parse_error)?;
let range = |raw: &RawValue| {
let text = raw.get(); let start = text.as_ptr() as usize - body.as_ptr() as usize;
start..start+text.len()
};
let facts = IngressFacts {
target, trace_json: range(context.trace), ldc_json: range(context.ldc), profuse_json: profuse.map(range),
rpc_value: trace_fields.rpc.ok_or(error(400, "INVALID_JSON_ENVELOPE"))?, trace_value: trace_fields.trace,
context: Context { user: context.user, trace, ldc }, request_data: range(request_data),
};
facts.target.app.identity()?; facts.target.interface_id.identity()?;
facts.context.user.user_id.identity()?; facts.context.trace.trace_id.trace()?; facts.context.trace.rpc_id.identity()?;
facts.context.ldc.zone.identity()?; facts.context.ldc.idc.identity()?; facts.context.ldc.env.identity()?;
if !request_data.get().starts_with('{') { return Err(error(400, "INVALID_REQUEST_DATA")); }
Ok(facts)
}
impl ProfuseGwListenerAdapter {
pub fn recognize_observed(&self, method: &str, path: &str, content_type: &str, body: &[u8], parser_error: impl FnOnce(&serde_json::Error)) -> Result<IngressFacts, CodecError> {
validate_transport(method, path, content_type, body)?;
let facts = recognize_body(body, parser_error)?;
if facts.target.app.get() != self.application.as_str() { return Err(error(404, "APPLICATION_NOT_FOUND")); }
Ok(facts)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn body(payload: &str, target: &str) -> String {
format!(
r#"{{"requestData":{payload},"profuseGwContext":{{"userInfo":{{"userId":"用户"}},"traceInfo":{{"rpcId":"0"}},"ldcInfo":{{"zone":"z","idc":"i","env":"test"}}}},"target":{target}}}"#
)
}
#[test]
fn extensible_objects_preserve_raw_values_and_reject_ambiguous_input() {
let adapter = ProfuseGwListenerAdapter::new("app").unwrap();
let target = r#"{"app":"app","interfaceId":"route"}"#;
let huge = "9".repeat(400);
let source = body("{}", target).replace(r#""rpcId":"0""#, &format!(r#""rpcId":"0","extra":{{"n":{huge},"a":[null,"中文",true]}}"#))
.replacen(r#""requestData""#, r#""profuseContext":{},"requestData""#, 1);
let facts = adapter.recognize_observed(METHOD, PATH, MEDIA_TYPE, source.as_bytes(), |_| panic!("valid extensible object")).unwrap();
let raw = facts.owned_context_json(source.as_bytes());
assert!(raw.trace_info.contains(&huge));
assert_eq!(raw.profuse_context.as_deref(), Some("{}"));
let accepted = adapter.accept(METHOD, PATH, MEDIA_TYPE, IngressIdentity::new("r", "c", 1).unwrap(), source.as_bytes()).unwrap();
let held = accepted.shared_context_json().unwrap();
drop(accepted);
assert_eq!(held.get().trace_info, raw.trace_info);
for replacement in ["null", "[]", "false", r#"{"x":1,"x":2}"#, r#"{"a":[{"x":1,"\u0078":2}]}"#] {
let bad = source.replacen(r#""profuseContext":{}"#, &format!(r#""profuseContext":{replacement}"#), 1);
assert_eq!(adapter.recognize_observed(METHOD, PATH, MEDIA_TYPE, bad.as_bytes(), |_| {}).unwrap_err().http_status, 400);
assert_eq!(adapter.accept(METHOD, PATH, MEDIA_TYPE, IngressIdentity::new("r", "c", 1).unwrap(), bad.as_bytes()).unwrap_err().http_status, 400);
}
let duplicate = source.replace(r#""n":"#, r#""n":1,"n":"#);
assert_eq!(adapter.recognize_observed(METHOD, PATH, MEDIA_TYPE, duplicate.as_bytes(), |_| {}).unwrap_err().http_status, 400);
let too_big = source.replace(&huge, &"9".repeat(4096));
assert_eq!(adapter.recognize_observed(METHOD, PATH, MEDIA_TYPE, too_big.as_bytes(), |_| {}).unwrap_err().http_status, 413);
assert!(adapter.accept(METHOD, PATH, MEDIA_TYPE, IngressIdentity::new("r", "c", 1).unwrap(), source.as_bytes()).is_ok());
}
#[test]
fn route_at_tail_and_escaped_metadata_borrow_exact_raw_payload() {
let source = body(
r#" { "nested": [1,{"s":"a\\b"}] } "#,
r#"{"app":"app","interfaceId":"路\u7531"}"#,
);
let adapter = ProfuseGwListenerAdapter::new("app").unwrap();
let facts = adapter
.recognize_observed(METHOD, PATH, MEDIA_TYPE, source.as_bytes(), |_| {
panic!("valid JSON")
})
.unwrap();
assert_eq!(facts.interface_id(), "路由");
assert_eq!(facts.user_id(), "用户");
let raw = &source.as_bytes()[facts.request_data_range()];
assert_eq!(raw, br#"{ "nested": [1,{"s":"a\\b"}] }"#);
assert!(std::mem::size_of::<IngressFacts>() < 4096);
}
#[test]
fn bounded_recognition_preserves_codec_failures_and_original_parse_error() {
let adapter = ProfuseGwListenerAdapter::new("app").unwrap();
let target = r#"{"app":"app","interfaceId":"route"}"#;
for source in [
body("[]", target),
body("{}", r#"{"app":"other","interfaceId":"route"}"#),
body(
"{}",
&format!(r#"{{"app":"app","interfaceId":"{}"}}"#, "x".repeat(257)),
),
body("{}", r#"{"app":"app","interfaceId":"r","extra":1}"#),
body("{}", target).replace(
"\"requestData\":{}",
"\"requestData\":{},\"requestData\":{}",
),
body("{", target),
] {
let mut captured = false;
let new = adapter
.recognize_observed(METHOD, PATH, MEDIA_TYPE, source.as_bytes(), |_| {
captured = true
})
.unwrap_err();
let old = adapter
.accept(
METHOD,
PATH,
MEDIA_TYPE,
IngressIdentity::new("r", "c", 1).unwrap(),
source.as_bytes(),
)
.unwrap_err();
assert_eq!(new, old);
assert_eq!(captured, new.code == "INVALID_JSON_ENVELOPE");
}
}
}