1mod presence;
10
11use std::borrow::Cow;
12use std::collections::HashMap;
13
14use prost_reflect::{
15 DynamicMessage, FieldDescriptor, Kind, MessageDescriptor, ReflectMessage, Value,
16};
17use serde::de::value::StrDeserializer;
18use serde::Deserializer;
19use serde_json::Value as JsonValue;
20
21use presence::{has_special_json, OneEntry, Presence, Recording};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum BodyMapping {
27 None,
29 Root,
31 Field(String),
33}
34
35impl BodyMapping {
36 pub fn parse(raw: &str) -> Self {
41 match raw {
42 "" => BodyMapping::None,
43 "*" => BodyMapping::Root,
44 field => BodyMapping::Field(field.to_string()),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum Body<'a> {
52 Absent,
56 Json(&'a [u8]),
59 Form(&'a [u8]),
62}
63
64impl<'a> Body<'a> {
65 pub fn new(content_type: Option<&str>, bytes: &'a [u8]) -> Self {
78 match content_type {
79 Some(ct) if ct.starts_with("application/x-www-form-urlencoded") => Body::Form(bytes),
80 _ => Body::Json(bytes),
81 }
82 }
83}
84
85pub fn build_request_message(
154 input: &MessageDescriptor,
155 mapping: &BodyMapping,
156 body: Body<'_>,
157 path_params: &HashMap<String, String>,
158 raw_query: Option<&str>,
159) -> Result<DynamicMessage, String> {
160 let query: Vec<(Cow<'_, str>, Cow<'_, str>)> = match raw_query {
162 Some(q) => url::form_urlencoded::parse(q.as_bytes()).collect(),
163 None => Vec::new(),
164 };
165 build(input, mapping, body, path_params, &query)
166}
167
168#[derive(Clone, Copy, PartialEq, Eq)]
170enum Binding {
171 Set,
173 Fill,
175}
176
177fn build(
178 input: &MessageDescriptor,
179 mapping: &BodyMapping,
180 body: Body<'_>,
181 path_params: &HashMap<String, String>,
182 query: &[(Cow<'_, str>, Cow<'_, str>)],
183) -> Result<DynamicMessage, String> {
184 let body_len = match body {
186 Body::Json(bytes) | Body::Form(bytes) => bytes.len(),
187 Body::Absent => 0,
188 };
189 let mut presence = (!query.is_empty()).then(|| Presence::for_body(body_len));
190 let mut path = Vec::new();
192
193 let mut inside = false;
195
196 let mut message = match (mapping, body) {
197 (BodyMapping::None, _) => DynamicMessage::new(input.clone()),
198 (BodyMapping::Field(name), Body::Absent) => {
199 if let (Some(presence), Some(field)) = (&mut presence, input.get_field_by_name(name)) {
200 presence.record_path(&[field], false);
201 }
202 DynamicMessage::new(input.clone())
203 }
204 (BodyMapping::Root, Body::Absent) => DynamicMessage::new(input.clone()),
205 (_, Body::Json(bytes) | Body::Form(bytes)) if bytes.is_empty() => {
207 json_body(input, mapping, b"{}", path_params, presence.as_mut())?
208 }
209 (_, Body::Json(bytes)) => json_body(input, mapping, bytes, path_params, presence.as_mut())?,
210 (_, Body::Form(bytes)) => {
211 let pairs: Vec<(Cow<'_, str>, Cow<'_, str>)> =
212 url::form_urlencoded::parse(bytes).collect();
213 let form = Form {
214 pairs: &pairs,
215 path_params,
216 };
217 let (message, form_inside) =
218 form_body(input, mapping, form, presence.as_mut(), &mut path)?;
219 inside = form_inside;
220 message
221 }
222 };
223
224 let root_inside = has_special_json(input);
225 for (key, raw) in path_params {
227 let target = Target {
228 root: input,
229 inside: root_inside,
230 prefix: None,
231 key,
232 values: &[raw.as_str()],
233 };
234 inside |= bind(
235 &mut message,
236 target,
237 Binding::Set,
238 presence.as_mut(),
239 &mut path,
240 )?;
241 }
242
243 if let Some(presence) = &mut presence {
244 let start = Start {
245 root: input,
246 inside: root_inside,
247 prefix: None,
248 };
249 inside |= bind_pairs(
250 &mut message,
251 query,
252 start,
253 Binding::Fill,
254 Some(presence),
255 None,
256 &mut path,
257 )?;
258 }
259 if inside {
260 check_well_known(&message)?;
261 }
262 Ok(message)
263}
264
265fn check_well_known(message: &DynamicMessage) -> Result<(), String> {
270 let invalid = |e: &dyn std::fmt::Display| format!("invalid value for a well-known type: {e}");
271 check_time_ranges(message).map_err(|e| invalid(&e))?;
272 serde_json::to_writer(std::io::sink(), message).map_err(|e| invalid(&e))
276}
277
278fn check_time_ranges(message: &DynamicMessage) -> Result<(), &'static str> {
282 let field = |name| message.get_field_by_name(name);
283 match message.descriptor().full_name() {
284 "google.protobuf.Timestamp" => {
287 let seconds = field("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
288 let nanos = field("nanos").and_then(|v| v.as_i32()).unwrap_or(0);
289 if !(-62_135_596_800..=253_402_300_799).contains(&seconds)
290 || !(0..=999_999_999).contains(&nanos)
291 {
292 return Err("timestamp out of range");
293 }
294 }
295 "google.protobuf.Duration" => {
299 let seconds = field("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
300 let nanos = field("nanos").and_then(|v| v.as_i32()).unwrap_or(0);
301 if seconds.unsigned_abs() > 315_576_000_000
302 || nanos.unsigned_abs() > 999_999_999
303 || (seconds > 0 && nanos < 0)
304 || (seconds < 0 && nanos > 0)
305 {
306 return Err("duration out of range");
307 }
308 }
309 _ => {}
310 }
311 for (_, value) in message.fields() {
312 check_value_time_ranges(value)?;
313 }
314 Ok(())
315}
316
317fn check_value_time_ranges(value: &Value) -> Result<(), &'static str> {
318 match value {
319 Value::Message(inner) => check_time_ranges(inner),
320 Value::List(items) => items.iter().try_for_each(check_value_time_ranges),
321 Value::Map(entries) => entries.values().try_for_each(check_value_time_ranges),
322 _ => Ok(()),
323 }
324}
325
326#[derive(Clone, Copy)]
329struct Target<'a> {
330 root: &'a MessageDescriptor,
331 inside: bool,
333 prefix: Option<&'a FieldDescriptor>,
334 key: &'a str,
335 values: &'a [&'a str],
337}
338
339fn json_body(
346 input: &MessageDescriptor,
347 mapping: &BodyMapping,
348 bytes: &[u8],
349 path_params: &HashMap<String, String>,
350 mut presence: Option<&mut Presence>,
351) -> Result<DynamicMessage, String> {
352 if matches!(mapping, BodyMapping::Root) && trim_json_whitespace(bytes) == b"null" {
354 return Ok(DynamicMessage::new(input.clone()));
355 }
356 let mut de = serde_json::Deserializer::from_slice(bytes);
357 let first = deserialize(input, mapping, &mut de, presence.as_deref_mut())
358 .and_then(|message| de.end().map(|()| message));
359 let error = match first {
360 Ok(message) => return Ok(message),
361 Err(error) => error,
362 };
363 let fail = |e: serde_json::Error| format!("failed to decode request body: {e}");
364 if path_params.is_empty() {
365 return Err(fail(error));
366 }
367 let Ok(mut value) = serde_json::from_slice::<JsonValue>(bytes) else {
369 return Err(fail(error));
370 };
371 strip_path_fields(&mut value, input, mapping, path_params);
372 if let Some(presence) = presence.as_deref_mut() {
373 presence.clear();
374 }
375 deserialize(input, mapping, value, presence).map_err(fail)
376}
377
378fn strip_path_fields(
381 body: &mut JsonValue,
382 input: &MessageDescriptor,
383 mapping: &BodyMapping,
384 path_params: &HashMap<String, String>,
385) {
386 for key in path_params.keys() {
387 let mut segments = key.split('.');
388 let desc = match mapping {
389 BodyMapping::Root => input.clone(),
390 BodyMapping::Field(name) => {
393 if segments.next() != Some(name.as_str()) {
394 continue;
395 }
396 match input.get_field_by_name(name).map(|f| f.kind()) {
397 Some(Kind::Message(inner)) => inner,
398 _ => continue,
399 }
400 }
401 BodyMapping::None => return,
402 };
403 remove_path(body, desc, segments);
404 }
405}
406
407fn remove_path<'k>(
409 mut value: &mut JsonValue,
410 mut desc: MessageDescriptor,
411 segments: impl Iterator<Item = &'k str>,
412) {
413 let mut segments = segments.peekable();
414 while let Some(segment) = segments.next() {
415 let Some(field) = field_named(&desc, segment, &mut false) else {
416 return;
417 };
418 let Some(object) = value.as_object_mut() else {
419 return;
420 };
421 if segments.peek().is_none() {
422 object.remove(field.json_name());
423 object.remove(field.name());
424 return;
425 }
426 let Kind::Message(inner) = field.kind() else {
427 return;
428 };
429 let key = if object.contains_key(field.json_name()) {
430 field.json_name()
431 } else {
432 field.name()
433 };
434 let Some(next) = object.get_mut(key) else {
435 return;
436 };
437 value = next;
438 desc = inner;
439 }
440}
441
442fn trim_json_whitespace(bytes: &[u8]) -> &[u8] {
443 let ws = |b: &u8| matches!(b, b' ' | b'\t' | b'\n' | b'\r');
444 let start = bytes.iter().position(|b| !ws(b)).unwrap_or(bytes.len());
445 let end = bytes.iter().rposition(|b| !ws(b)).map_or(start, |i| i + 1);
446 &bytes[start..end]
447}
448
449fn deserialize<'de, D: Deserializer<'de>>(
452 input: &MessageDescriptor,
453 mapping: &BodyMapping,
454 body: D,
455 presence: Option<&mut Presence>,
456) -> Result<DynamicMessage, D::Error> {
457 match mapping {
458 BodyMapping::Field(name) => {
459 let field = input.get_field_by_name(name);
462 let key = field.as_ref().map_or(name.as_str(), |f| f.json_name());
463 deserialize_input(input, OneEntry { key, value: body }, presence)
464 }
465 _ => deserialize_input(input, body, presence),
466 }
467}
468
469fn deserialize_input<'de, D: Deserializer<'de>>(
470 input: &MessageDescriptor,
471 de: D,
472 presence: Option<&mut Presence>,
473) -> Result<DynamicMessage, D::Error> {
474 let Some(presence) = presence else {
475 return DynamicMessage::deserialize(input.clone(), de);
476 };
477 if has_special_json(input) {
478 presence.record_whole();
479 return DynamicMessage::deserialize(input.clone(), de);
480 }
481 DynamicMessage::deserialize(input.clone(), Recording::root(de, presence))
482}
483
484fn form_body(
487 input: &MessageDescriptor,
488 mapping: &BodyMapping,
489 form: Form<'_, '_>,
490 mut presence: Option<&mut Presence>,
491 path: &mut Vec<FieldDescriptor>,
492) -> Result<(DynamicMessage, bool), String> {
493 let Form { pairs, path_params } = form;
494 let mut message = DynamicMessage::new(input.clone());
495 let (prefix, start_inside) = match mapping {
496 BodyMapping::Field(name) => {
497 let (field, inner) = input
498 .get_field_by_name(name)
499 .filter(|f| !f.is_list() && !f.is_map())
500 .and_then(|f| match f.kind() {
501 Kind::Message(inner) => Some((f, inner)),
502 _ => None,
503 })
504 .ok_or_else(|| format!("a form body cannot fill field `{name}`"))?;
505 message.get_field_mut(&field);
507 if let Some(presence) = presence.as_deref_mut() {
508 presence.record_path(std::slice::from_ref(&field), true);
509 }
510 (Some(field), has_special_json(&inner))
511 }
512 _ => (None, has_special_json(input)),
513 };
514 let start = Start {
515 root: input,
516 inside: start_inside,
517 prefix: prefix.as_ref(),
518 };
519 let inside = bind_pairs(
520 &mut message,
521 pairs,
522 start,
523 Binding::Set,
524 presence,
525 Some(path_params),
526 path,
527 )?;
528 Ok((message, inside))
529}
530
531#[derive(Clone, Copy)]
533struct Form<'p, 'q> {
534 pairs: &'p [(Cow<'q, str>, Cow<'q, str>)],
535 path_params: &'p HashMap<String, String>,
536}
537
538fn covers(bound: &str, path: &[FieldDescriptor]) -> bool {
541 let mut fields = path.iter();
542 bound.split('.').all(|segment| {
543 fields
544 .next()
545 .is_some_and(|f| segment == f.name() || segment == f.json_name())
546 })
547}
548
549#[derive(Clone, Copy)]
552struct Start<'a> {
553 root: &'a MessageDescriptor,
554 inside: bool,
555 prefix: Option<&'a FieldDescriptor>,
556}
557
558fn bind_pairs(
566 message: &mut DynamicMessage,
567 pairs: &[(Cow<'_, str>, Cow<'_, str>)],
568 start: Start<'_>,
569 binding: Binding,
570 mut presence: Option<&mut Presence>,
571 path_params: Option<&HashMap<String, String>>,
572 path: &mut Vec<FieldDescriptor>,
573) -> Result<bool, String> {
574 let mut order: Vec<usize> = (0..pairs.len()).collect();
576 order.sort_by(|&a, &b| pairs[a].0.cmp(&pairs[b].0));
577 let mut values = Vec::new();
578 let mut inside = false;
579 let mut aliased: Vec<(String, &[usize])> = Vec::new();
582 let mut failed: Vec<(&str, String)> = Vec::new();
584 for run in order.chunk_by(|&a, &b| pairs[a].0 == pairs[b].0) {
585 values.clear();
586 values.extend(run.iter().map(|&i| pairs[i].1.as_ref()));
587 let target = Target {
588 root: start.root,
589 inside: start.inside,
590 prefix: start.prefix,
591 key: &pairs[run[0]].0,
592 values: &values,
593 };
594 let Some(resolved) = resolve(target, path) else {
595 continue;
596 };
597 if resolved.aliased {
598 let own = &path[usize::from(start.prefix.is_some())..];
599 let canonical = own.iter().map(|f| f.name()).collect::<Vec<_>>().join(".");
600 aliased.push((canonical, run));
601 continue;
602 }
603 let bound = bind_resolved(
604 message,
605 path,
606 &values,
607 binding,
608 presence.as_deref_mut(),
609 path_params,
610 );
611 match bound {
612 Ok(set) => inside |= set && resolved.inside,
613 Err(e) => failed.push((pairs[run[0]].0.as_ref(), e)),
618 }
619 }
620 aliased.sort_by(|a, b| a.0.cmp(&b.0));
621 let covered = |key: &str| {
623 aliased
624 .binary_search_by(|(c, _)| c.as_str().cmp(key))
625 .is_ok()
626 };
627 if let Some((_, e)) = failed.into_iter().find(|(key, _)| !covered(key)) {
628 return Err(e);
629 }
630 if aliased.is_empty() {
631 return Ok(inside);
632 }
633 let mut indices = Vec::new();
634 for group in aliased.chunk_by(|a, b| a.0 == b.0) {
635 let canonical = group[0].0.as_str();
636 indices.clear();
637 for (_, run) in group {
638 indices.extend_from_slice(run);
639 }
640 let from = order.partition_point(|&i| pairs[i].0.as_ref() < canonical);
642 let to = order.partition_point(|&i| pairs[i].0.as_ref() <= canonical);
643 indices.extend_from_slice(&order[from..to]);
644 indices.sort_unstable();
645 values.clear();
646 values.extend(indices.iter().map(|&i| pairs[i].1.as_ref()));
647 let target = Target {
648 root: start.root,
649 inside: start.inside,
650 prefix: start.prefix,
651 key: canonical,
652 values: &values,
653 };
654 let Some(resolved) = resolve(target, path) else {
655 continue;
656 };
657 inside |= bind_resolved(
658 message,
659 path,
660 &values,
661 binding,
662 presence.as_deref_mut(),
663 path_params,
664 )? && resolved.inside;
665 }
666 Ok(inside)
667}
668
669fn bind_resolved(
672 message: &mut DynamicMessage,
673 path: &[FieldDescriptor],
674 values: &[&str],
675 binding: Binding,
676 presence: Option<&mut Presence>,
677 path_params: Option<&HashMap<String, String>>,
678) -> Result<bool, String> {
679 if path_params.is_some_and(|bound| bound.keys().any(|key| covers(key, path))) {
682 return Ok(false);
683 }
684 assign(message, path, values, binding, presence)?;
685 Ok(true)
686}
687
688fn bind(
692 message: &mut DynamicMessage,
693 target: Target<'_>,
694 binding: Binding,
695 presence: Option<&mut Presence>,
696 path: &mut Vec<FieldDescriptor>,
697) -> Result<bool, String> {
698 let Some(resolved) = resolve(target, path) else {
699 return Ok(false);
700 };
701 assign(message, path, target.values, binding, presence)?;
702 Ok(resolved.inside)
703}
704
705#[derive(Clone, Copy)]
707struct Resolved {
708 inside: bool,
710 aliased: bool,
712}
713
714fn resolve(target: Target<'_>, path: &mut Vec<FieldDescriptor>) -> Option<Resolved> {
724 path.clear();
725 let mut inside = target.inside;
726 let mut aliased = false;
727 let mut owned;
728 let mut desc = match target.prefix {
729 Some(field) => {
730 let Kind::Message(inner) = field.kind() else {
731 return None;
732 };
733 path.push(field.clone());
734 owned = inner;
735 &owned
736 }
737 None => target.root,
738 };
739 let mut segments = target.key.split('.');
740 let mut leaf = segments.next().unwrap_or_default();
741 for next in segments {
742 let field = field_named(desc, leaf, &mut aliased)?;
743 let Kind::Message(inner) = field.kind() else {
744 return None;
745 };
746 if field.is_list() || field.is_map() {
747 return None;
748 }
749 inside |= has_special_json(&inner);
750 path.push(field);
751 owned = inner;
752 desc = &owned;
753 leaf = next;
754 }
755 path.push(field_named(desc, leaf, &mut aliased)?);
756 Some(Resolved { inside, aliased })
757}
758
759fn field_named(
762 desc: &MessageDescriptor,
763 name: &str,
764 aliased: &mut bool,
765) -> Option<FieldDescriptor> {
766 if let Some(field) = desc.get_field_by_name(name) {
767 return Some(field);
768 }
769 let field = desc.get_field_by_json_name(name)?;
770 *aliased = true;
771 Some(field)
772}
773
774fn assign(
778 message: &mut DynamicMessage,
779 path: &[FieldDescriptor],
780 values: &[&str],
781 binding: Binding,
782 mut presence: Option<&mut Presence>,
783) -> Result<(), String> {
784 if binding == Binding::Fill && presence.as_deref_mut().is_some_and(|p| p.blocks(path)) {
785 return Ok(());
786 }
787
788 let (field, parents) = path
789 .split_last()
790 .expect("a resolved path ends in its field");
791 let value = field_value(field, values)?;
792 let mut holder = message;
793 for parent in parents {
794 check_oneof(holder, parent)?;
795 holder = match holder.get_field_mut(parent) {
796 Value::Message(inner) => inner,
797 _ => unreachable!("a singular message field holds a message"),
798 };
799 }
800 check_oneof(holder, field)?;
801 holder.set_field(field, value);
802 if binding == Binding::Set {
803 if let Some(presence) = presence {
804 presence.record_path(path, false);
805 }
806 }
807 Ok(())
808}
809
810fn check_oneof(message: &DynamicMessage, field: &FieldDescriptor) -> Result<(), String> {
813 let Some(oneof) = field.containing_oneof() else {
814 return Ok(());
815 };
816 if oneof
817 .fields()
818 .any(|other| other.number() != field.number() && message.has_field(&other))
819 {
820 return Err(format!(
821 "multiple fields provided for oneof '{}'",
822 oneof.name()
823 ));
824 }
825 Ok(())
826}
827
828fn field_value(field: &FieldDescriptor, raw: &[&str]) -> Result<Value, String> {
830 let invalid = |e: String| format!("invalid value for field `{}`: {e}", field.name());
831 if field.is_map() {
832 return Err(invalid("a map field cannot be set from a string".into()));
833 }
834 let kind = field.kind();
835 if field.is_list() {
836 return raw
837 .iter()
838 .map(|raw| scalar(&kind, raw))
839 .collect::<Result<Vec<_>, _>>()
840 .map(Value::List)
841 .map_err(invalid);
842 }
843 let raw = raw.last().expect("a bound key has a value");
844 scalar(&kind, raw).map_err(invalid)
845}
846
847fn scalar(kind: &Kind, raw: &str) -> Result<Value, String> {
852 Ok(match kind {
853 Kind::Double => Value::F64(parse_f64(raw)?),
854 Kind::Float => Value::F32(parse_f32(raw)?),
855 Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => Value::I32(parse(raw)?),
856 Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => Value::I64(parse(raw)?),
857 Kind::Uint32 | Kind::Fixed32 => Value::U32(parse(raw)?),
858 Kind::Uint64 | Kind::Fixed64 => Value::U64(parse(raw)?),
859 Kind::Bool => Value::Bool(parse(raw)?),
860 Kind::String => Value::String(raw.to_owned()),
861 Kind::Bytes => Value::Bytes(decode_base64(raw)?.into()),
862 Kind::Enum(desc) => Value::EnumNumber(
863 desc.get_value_by_name(raw)
864 .ok_or_else(|| format!("unrecognized enum value '{raw}'"))?
865 .number(),
866 ),
867 Kind::Message(desc) => Value::Message(
868 DynamicMessage::deserialize(
869 desc.clone(),
870 StrDeserializer::<serde::de::value::Error>::new(raw),
871 )
872 .map_err(|e| e.to_string())?,
873 ),
874 })
875}
876
877fn parse<T: std::str::FromStr>(raw: &str) -> Result<T, String>
878where
879 T::Err: std::fmt::Display,
880{
881 raw.parse().map_err(|e: T::Err| e.to_string())
882}
883
884fn special_float(raw: &str) -> Option<f64> {
886 match raw {
887 "Infinity" => Some(f64::INFINITY),
888 "-Infinity" => Some(f64::NEG_INFINITY),
889 "NaN" => Some(f64::NAN),
890 _ => None,
891 }
892}
893
894fn parse_f64(raw: &str) -> Result<f64, String> {
899 if let Some(special) = special_float(raw) {
900 return Ok(special);
901 }
902 match raw.parse::<f64>() {
903 Ok(value) if value.is_finite() => Ok(value),
904 Ok(_) => Err("value out of range, or not a decimal number".to_string()),
905 Err(e) => Err(e.to_string()),
906 }
907}
908
909fn parse_f32(raw: &str) -> Result<f32, String> {
912 let wide = parse_f64(raw)?;
913 if wide.is_finite() && (wide < f64::from(f32::MIN) || wide > f64::from(f32::MAX)) {
914 return Err("float value out of range".to_string());
915 }
916 Ok(wide as f32)
919}
920
921fn decode_base64(raw: &str) -> Result<Vec<u8>, String> {
924 use base64::alphabet;
925 use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
926 use base64::{DecodeError, Engine};
927
928 const CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
929 .with_decode_allow_trailing_bits(true)
930 .with_decode_padding_mode(DecodePaddingMode::Indifferent);
931 const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, CONFIG);
932 const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, CONFIG);
933
934 let mut buf = Vec::new();
935 match STANDARD.decode_vec(raw, &mut buf) {
936 Ok(()) => Ok(buf),
937 Err(DecodeError::InvalidByte(_, b'-' | b'_')) => {
938 buf.clear();
939 URL_SAFE
940 .decode_vec(raw, &mut buf)
941 .map(|()| buf)
942 .map_err(|e| format!("invalid base64: {e}"))
943 }
944 Err(e) => Err(format!("invalid base64: {e}")),
945 }
946}
947
948#[cfg(test)]
949mod tests;