1use anyhow::{bail, Context, Result};
8use base64::{engine::general_purpose::STANDARD, Engine as _};
9use prost_reflect::{DynamicMessage, FieldDescriptor, Kind, MapKey, MessageDescriptor, Value};
10use serde_json::Value as Json;
11
12pub const MAX_VALUE_DEPTH: usize = 32;
14pub const MAX_VALUE_NODES: usize = 100_000;
15pub const MAX_VALUE_BYTES: usize = 8 * 1024 * 1024;
17pub const VALUE_NODE_OVERHEAD_BYTES: usize = 64;
18const MAP_KEY_OVERHEAD_BYTES: usize = 32;
19
20#[derive(Debug)]
22pub struct ValueLimitExceeded {
23 pub dimension: &'static str,
24 pub limit: usize,
25}
26impl std::fmt::Display for ValueLimitExceeded {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(
29 f,
30 "gRPC value exceeds {} limit {}",
31 self.dimension, self.limit
32 )
33 }
34}
35impl std::error::Error for ValueLimitExceeded {}
36
37#[derive(Default)]
38struct Budget {
39 nodes: usize,
40 bytes: usize,
41}
42impl Budget {
43 fn node(&mut self, depth: usize) -> Result<()> {
44 if depth > MAX_VALUE_DEPTH {
45 return Err(ValueLimitExceeded {
46 dimension: "depth",
47 limit: MAX_VALUE_DEPTH,
48 }
49 .into());
50 }
51 self.children(1)?;
52 self.nodes += 1;
53 self.bytes(VALUE_NODE_OVERHEAD_BYTES)
54 }
55
56 fn children(&self, count: usize) -> Result<()> {
57 if count > MAX_VALUE_NODES.saturating_sub(self.nodes) {
58 return Err(ValueLimitExceeded {
59 dimension: "nodes",
60 limit: MAX_VALUE_NODES,
61 }
62 .into());
63 }
64 Ok(())
65 }
66
67 fn bytes(&mut self, count: usize) -> Result<()> {
68 if count > MAX_VALUE_BYTES.saturating_sub(self.bytes) {
69 return Err(ValueLimitExceeded {
70 dimension: "bytes",
71 limit: MAX_VALUE_BYTES,
72 }
73 .into());
74 }
75 self.bytes += count;
76 Ok(())
77 }
78
79 fn key(&mut self, key: &str) -> Result<()> {
80 self.bytes(MAP_KEY_OVERHEAD_BYTES)?;
81 self.bytes(key.len())
82 }
83}
84
85pub fn json_to_dynamic_message(
87 json: &Json,
88 descriptor: &MessageDescriptor,
89) -> Result<DynamicMessage> {
90 json_to_message(json, descriptor, 0, &mut Budget::default())
91}
92
93pub fn dynamic_message_to_json(message: &DynamicMessage) -> Result<Json> {
95 message_to_json(message, 0, &mut Budget::default())
96}
97
98pub fn proto_value_to_json(value: &Value) -> Result<Json> {
100 value_to_json(value, 0, &mut Budget::default())
101}
102
103fn json_to_message(
104 json: &Json,
105 descriptor: &MessageDescriptor,
106 depth: usize,
107 budget: &mut Budget,
108) -> Result<DynamicMessage> {
109 budget.node(depth)?;
110 let object = json
111 .as_object()
112 .with_context(|| format!("message {} requires a JSON object", descriptor.full_name()))?;
113 budget.children(object.len())?;
114 let mut message = DynamicMessage::new(descriptor.clone());
115 let mut oneofs = std::collections::HashSet::new();
116 for (name, value) in object {
117 budget.key(name)?;
118 let field = descriptor.get_field_by_name(name).with_context(|| {
119 format!(
120 "unknown field {name:?} in message {}",
121 descriptor.full_name()
122 )
123 })?;
124 if let Some(oneof) = field.containing_oneof() {
125 if !oneofs.insert(oneof.full_name().to_owned()) {
126 bail!("multiple values supplied for oneof {}", oneof.full_name());
127 }
128 }
129 let value = json_to_field_value(value, &field, depth + 1, budget)?;
130 message
131 .try_set_field(&field, value)
132 .with_context(|| format!("invalid value for field {}", field.full_name()))?;
133 }
134 Ok(message)
135}
136
137fn json_to_field_value(
138 json: &Json,
139 field: &FieldDescriptor,
140 depth: usize,
141 budget: &mut Budget,
142) -> Result<Value> {
143 if field.is_map() {
144 budget.node(depth)?;
145 let Kind::Message(entry) = field.kind() else {
146 bail!("map field has no entry message")
147 };
148 let key_field = entry.get_field(1).context("map entry has no key field")?;
149 let value_field = entry.get_field(2).context("map entry has no value field")?;
150 let object = json
151 .as_object()
152 .with_context(|| format!("field {} requires a map object", field.full_name()))?;
153 budget.children(object.len())?;
154 let mut map = std::collections::HashMap::new();
155 for (key, value) in object {
156 budget.key(key)?;
157 let key = parse_map_key(key, key_field.kind())?;
158 if map.contains_key(&key) {
159 bail!("duplicate protobuf map key in field {}", field.full_name());
160 }
161 map.insert(
162 key,
163 json_to_proto_value(value, &value_field, depth + 1, budget)?,
164 );
165 }
166 return Ok(Value::Map(map));
167 }
168 if field.is_list() {
169 budget.node(depth)?;
170 let array = json
171 .as_array()
172 .with_context(|| format!("field {} requires an array", field.full_name()))?;
173 budget.children(array.len())?;
174 let mut list = Vec::new();
175 for item in array {
176 list.push(json_to_proto_value(item, field, depth + 1, budget)?);
177 }
178 return Ok(Value::List(list));
179 }
180 json_to_proto_value(json, field, depth, budget)
181}
182
183fn json_to_proto_value(
184 json: &Json,
185 field: &FieldDescriptor,
186 depth: usize,
187 budget: &mut Budget,
188) -> Result<Value> {
189 let kind = field.kind();
190 if let Kind::Message(descriptor) = kind {
191 return Ok(Value::Message(json_to_message(
192 json,
193 &descriptor,
194 depth,
195 budget,
196 )?));
197 }
198 budget.node(depth)?;
199 let invalid = || {
200 format!(
201 "value does not match field {} ({:?})",
202 field.full_name(),
203 field.kind()
204 )
205 };
206 Ok(match kind {
207 Kind::Bool => Value::Bool(json.as_bool().with_context(invalid)?),
208 Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => Value::I32(
209 i32::try_from(json.as_i64().with_context(invalid)?)
210 .context("integer does not fit int32")?,
211 ),
212 Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => {
213 Value::I64(json.as_i64().with_context(invalid)?)
214 }
215 Kind::Uint32 | Kind::Fixed32 => Value::U32(
216 u32::try_from(json.as_u64().with_context(invalid)?)
217 .context("integer does not fit uint32")?,
218 ),
219 Kind::Uint64 | Kind::Fixed64 => Value::U64(json.as_u64().with_context(invalid)?),
220 Kind::Float => {
221 let value = json.as_f64().with_context(invalid)?;
222 if !value.is_finite() || value.abs() > f32::MAX as f64 {
223 bail!(
224 "field {} is outside the finite float range",
225 field.full_name()
226 );
227 }
228 let narrowed = value as f32;
229 if value != 0.0 && narrowed == 0.0 {
230 bail!(
231 "field {} underflows the nonzero float range",
232 field.full_name()
233 );
234 }
235 Value::F32(narrowed)
236 }
237 Kind::Double => {
238 let value = json.as_f64().with_context(invalid)?;
239 if !value.is_finite() {
240 bail!("field {} requires a finite number", field.full_name());
241 }
242 Value::F64(value)
243 }
244 Kind::String => {
245 let value = json.as_str().with_context(invalid)?;
246 budget.bytes(value.len())?;
247 Value::String(value.to_owned())
248 }
249 Kind::Bytes => {
250 let value = json.as_str().with_context(invalid)?;
251 budget.bytes(value.len())?;
253 Value::Bytes(
254 STANDARD
255 .decode(value)
256 .context("invalid protobuf bytes base64")?
257 .into(),
258 )
259 }
260 Kind::Enum(descriptor) => {
261 let number = if let Some(number) = json.as_i64() {
262 i32::try_from(number).context("enum number does not fit int32")?
263 } else if let Some(name) = json.as_str() {
264 budget.bytes(name.len())?;
265 descriptor
266 .get_value_by_name(name)
267 .with_context(|| {
268 format!("unknown value {name:?} for enum {}", descriptor.full_name())
269 })?
270 .number()
271 } else {
272 bail!("enum {} requires a name or integer", descriptor.full_name())
273 };
274 if descriptor.get_value(number).is_none() {
275 bail!("unknown value {number} for enum {}", descriptor.full_name());
276 }
277 Value::EnumNumber(number)
278 }
279 Kind::Message(_) => unreachable!("messages handled before charging scalar nodes"),
280 })
281}
282
283fn parse_map_key(key: &str, kind: Kind) -> Result<MapKey> {
284 Ok(match kind {
285 Kind::String => MapKey::String(key.to_owned()),
286 Kind::Bool => MapKey::Bool(key.parse().context("map key is not a boolean")?),
287 Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => {
288 MapKey::I32(key.parse().context("map key is not int32")?)
289 }
290 Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => {
291 MapKey::I64(key.parse().context("map key is not int64")?)
292 }
293 Kind::Uint32 | Kind::Fixed32 => MapKey::U32(key.parse().context("map key is not uint32")?),
294 Kind::Uint64 | Kind::Fixed64 => MapKey::U64(key.parse().context("map key is not uint64")?),
295 _ => bail!("unsupported protobuf map key kind"),
296 })
297}
298
299fn message_to_json(message: &DynamicMessage, depth: usize, budget: &mut Budget) -> Result<Json> {
300 budget.node(depth)?;
301 let mut object = serde_json::Map::new();
302 for (field, value) in message.fields() {
304 budget.key(field.name())?;
305 let json = value_to_json(value, depth + 1, budget)?;
306 object.insert(field.name().to_owned(), json);
307 }
308 Ok(Json::Object(object))
309}
310
311fn value_to_json(value: &Value, depth: usize, budget: &mut Budget) -> Result<Json> {
312 if let Value::Message(message) = value {
313 return message_to_json(message, depth, budget);
314 }
315 budget.node(depth)?;
316 Ok(match value {
317 Value::Bool(value) => Json::Bool(*value),
318 Value::I32(value) => Json::Number((*value).into()),
319 Value::I64(value) => Json::Number((*value).into()),
320 Value::U32(value) => Json::Number((*value).into()),
321 Value::U64(value) => Json::Number((*value).into()),
322 Value::F32(value) => Json::Number(
323 serde_json::Number::from_f64(*value as f64)
324 .context("non-finite protobuf float cannot be represented in field-name JSON")?,
325 ),
326 Value::F64(value) => Json::Number(
327 serde_json::Number::from_f64(*value)
328 .context("non-finite protobuf double cannot be represented in field-name JSON")?,
329 ),
330 Value::String(value) => {
331 budget.bytes(value.len())?;
332 Json::String(value.clone())
333 }
334 Value::Bytes(value) => {
335 let length = value
336 .len()
337 .checked_add(2)
338 .and_then(|n| (n / 3).checked_mul(4))
339 .ok_or(ValueLimitExceeded {
340 dimension: "bytes",
341 limit: MAX_VALUE_BYTES,
342 })?;
343 budget.bytes(length)?;
344 Json::String(STANDARD.encode(value))
345 }
346 Value::EnumNumber(value) => Json::Number((*value).into()),
347 Value::List(values) => {
348 budget.children(values.len())?;
349 let mut array = Vec::new();
350 for value in values {
351 array.push(value_to_json(value, depth + 1, budget)?);
352 }
353 Json::Array(array)
354 }
355 Value::Map(values) => {
356 budget.children(values.len())?;
357 let mut object = serde_json::Map::new();
358 for (key, value) in values {
359 if let MapKey::String(key) = key {
361 budget.key(key)?;
362 }
363 let key_text = map_key_to_string(key);
364 if !matches!(key, MapKey::String(_)) {
365 budget.key(&key_text)?;
366 }
367 if object.contains_key(&key_text) {
368 bail!("distinct protobuf map keys have the same JSON representation");
369 }
370 let json = value_to_json(value, depth + 1, budget)?;
371 object.insert(key_text, json);
372 }
373 Json::Object(object)
374 }
375 Value::Message(_) => unreachable!("messages handled before charging scalar nodes"),
376 })
377}
378
379fn map_key_to_string(key: &MapKey) -> String {
380 match key {
381 MapKey::Bool(value) => value.to_string(),
382 MapKey::I32(value) => value.to_string(),
383 MapKey::I64(value) => value.to_string(),
384 MapKey::U32(value) => value.to_string(),
385 MapKey::U64(value) => value.to_string(),
386 MapKey::String(value) => value.clone(),
387 }
388}