cadmpeg_core/decode/
input.rs1use std::io::Read;
5
6use super::{u64_from_index, DecodeContext, ResourceDimension, ResourceFailure};
7use crate::CodecError;
8
9const INPUT_CHUNK: usize = 8192;
10
11impl DecodeContext<'_> {
12 pub fn read_input_prefix(
15 &self,
16 reader: &mut dyn Read,
17 length: usize,
18 ) -> Result<Vec<u8>, CodecError> {
19 let mut bytes = Vec::new();
20 self.extend_input_prefix(reader, &mut bytes, length)?;
21 Ok(bytes)
22 }
23
24 pub fn extend_input_prefix(
26 &self,
27 reader: &mut dyn Read,
28 bytes: &mut Vec<u8>,
29 length: usize,
30 ) -> Result<(), CodecError> {
31 let mut chunk = [0_u8; INPUT_CHUNK];
32 while bytes.len() < length {
33 let count = (length - bytes.len()).min(chunk.len());
34 self.charge_work(u64_from_index(count), "read input prefix")?;
35 let read = reader.read(&mut chunk[..count]).map_err(CodecError::Io)?;
36 if read == 0 {
37 break;
38 }
39 self.budget
40 .charge_input(u64_from_index(read), "read input prefix")?;
41 self.charge_work(u64_from_index(read), "copy input prefix")?;
42 bytes.try_reserve_exact(read).map_err(|_| {
43 self.budget.refuse(
44 ResourceDimension::InputBytes,
45 ResourceFailure::AllocationFailed,
46 self.policy().limits.max_input_bytes,
47 self.budget.input_bytes(),
48 u64_from_index(read),
49 "input prefix storage",
50 )
51 })?;
52 bytes.extend_from_slice(&chunk[..read]);
53 }
54 Ok(())
55 }
56
57 pub fn complete_input(
59 &self,
60 reader: &mut dyn Read,
61 bytes: &mut Vec<u8>,
62 ) -> Result<(), CodecError> {
63 let max = self.policy().limits.max_input_bytes;
64 loop {
65 let remaining = max
66 .checked_sub(u64_from_index(bytes.len()))
67 .ok_or_else(|| self.refuse_input_limit(0, "complete input prefix"))?;
68 let count = if remaining >= u64_from_index(INPUT_CHUNK) {
69 INPUT_CHUNK
70 } else {
71 usize::try_from(remaining).map_err(|_| {
72 self.refuse_codec_limit(
73 "address input chunk",
74 u64_from_index(INPUT_CHUNK),
75 remaining,
76 )
77 })?
78 };
79 let length = bytes.len().checked_add(count).ok_or_else(|| {
80 self.refuse_codec_limit("address complete input", u64_from_index(usize::MAX), max)
81 })?;
82 self.extend_input_prefix(reader, bytes, length)?;
83 if count == 0 || bytes.len() < length {
84 break;
85 }
86 }
87 self.charge_work(1, "input end probe")?;
88 if reader.read(&mut [0_u8; 1])? != 0 {
89 return Err(self.refuse_input_limit(1, "complete input"));
90 }
91 Ok(())
92 }
93
94 pub fn refuse_input_limit(&self, additional: u64, operation: &'static str) -> CodecError {
96 self.budget.refuse(
97 ResourceDimension::InputBytes,
98 ResourceFailure::BudgetExceeded,
99 self.policy().limits.max_input_bytes,
100 self.budget.input_bytes(),
101 additional,
102 operation,
103 )
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use crate::decode::{DecodeArena, DecodeContext, DecodePolicy, ResourceDimension};
110 use crate::CodecError;
111 use std::io::Cursor;
112
113 #[test]
114 fn raw_input_storage_does_not_consume_decoded_collection_slots() {
115 let arena = DecodeArena::new();
116 let mut policy = DecodePolicy::service();
117 policy.limits.max_collection_items = 1;
119 let source = vec![b'x'; 9000];
120 let (ctx, root) =
121 DecodeContext::read_root(&mut Cursor::new(&source), &arena, &policy, false)
122 .expect("physical input is governed by input bytes");
123 assert_eq!(root.window(), source);
124 ctx.finish_session().expect("no collection refusal");
125 }
126
127 #[test]
128 fn input_prefix_refusal_keeps_input_dimension() {
129 let arena = DecodeArena::new();
130 let mut policy = DecodePolicy::service();
131 policy.limits.max_input_bytes = 2;
132 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root");
133 assert!(matches!(ctx.read_input_prefix(&mut Cursor::new(b"abc"), 3),
134 Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::InputBytes
135 && limit.used == 0 && limit.additional == 3));
136 }
137
138 #[test]
139 fn input_prefix_reads_share_one_input_budget() {
140 let arena = DecodeArena::new();
141 let mut policy = DecodePolicy::service();
142 policy.limits.max_input_bytes = 2;
143 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root");
144 let mut reader = Cursor::new(b"abc");
145 assert_eq!(
146 ctx.read_input_prefix(&mut reader, 2).expect("prefix"),
147 b"ab"
148 );
149 assert!(matches!(ctx.read_input_prefix(&mut reader, 1),
150 Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::InputBytes
151 && limit.used == 2 && limit.additional == 1));
152 }
153
154 #[test]
155 fn complete_input_preserves_large_policy_and_prefix() {
156 let arena = DecodeArena::new();
157 let mut policy = DecodePolicy::service();
158 policy.limits.max_input_bytes = u64::MAX;
159 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root");
160 let source = vec![b'x'; 9000];
161 let mut reader = Cursor::new(&source);
162 let mut bytes = ctx.read_input_prefix(&mut reader, 7).expect("prefix");
163 ctx.complete_input(&mut reader, &mut bytes)
164 .expect("complete input");
165 assert_eq!(bytes, source);
166 assert_eq!(ctx.budget.input_bytes(), 9000);
167 }
168
169 #[test]
170 fn complete_input_refusal_keeps_input_dimension() {
171 let arena = DecodeArena::new();
172 let mut policy = DecodePolicy::service();
173 policy.limits.max_input_bytes = 2;
174 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root");
175 let mut bytes = Vec::new();
176 assert!(
177 matches!(ctx.complete_input(&mut Cursor::new(b"abc"), &mut bytes),
178 Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::InputBytes
179 && limit.used == 2 && limit.additional == 1)
180 );
181 assert_eq!(bytes, b"ab");
182 }
183}