1#[derive(Debug, thiserror::Error)]
14pub enum NdrError {
15 #[error("NDR buffer underrun: need {need} bytes at offset {pos}")]
16 Underrun { need: usize, pos: usize },
17}
18
19pub type Result<T> = std::result::Result<T, NdrError>;
20
21#[derive(Default)]
23pub struct NdrEncoder {
24 buf: Vec<u8>,
25 next_referent: u32,
26}
27
28impl NdrEncoder {
29 pub fn new() -> Self {
30 NdrEncoder {
31 buf: Vec::new(),
32 next_referent: 0x0002_0000,
33 }
34 }
35
36 pub fn align(&mut self, a: usize) {
39 if a <= 1 {
40 return;
41 }
42 while self.buf.len() % a != 0 {
43 self.buf.push(0);
44 }
45 }
46
47 pub fn u8(&mut self, v: u8) {
48 self.buf.push(v);
49 }
50 pub fn u16(&mut self, v: u16) {
51 self.align(2);
52 self.buf.extend_from_slice(&v.to_le_bytes());
53 }
54 pub fn u32(&mut self, v: u32) {
55 self.align(4);
56 self.buf.extend_from_slice(&v.to_le_bytes());
57 }
58 pub fn u64(&mut self, v: u64) {
59 self.align(8);
60 self.buf.extend_from_slice(&v.to_le_bytes());
61 }
62 pub fn bytes(&mut self, b: &[u8]) {
63 self.buf.extend_from_slice(b);
64 }
65 pub fn uuid(&mut self, b: &[u8; 16]) {
67 self.align(4);
68 self.buf.extend_from_slice(b);
69 }
70
71 pub fn referent(&mut self) -> u32 {
73 let id = self.next_referent;
74 self.next_referent += 4;
75 self.u32(id);
76 id
77 }
78 pub fn null_ptr(&mut self) {
79 self.u32(0);
80 }
81
82 pub fn conformant_varying_wstr(&mut self, s: &str) {
85 let mut units: Vec<u16> = s.encode_utf16().collect();
86 units.push(0);
87 let n = units.len() as u32;
88 self.u32(n); self.u32(0); self.u32(n); for u in units {
92 self.u16(u);
93 }
94 }
95
96 pub fn into_bytes(self) -> Vec<u8> {
97 self.buf
98 }
99 pub fn len(&self) -> usize {
100 self.buf.len()
101 }
102 pub fn is_empty(&self) -> bool {
103 self.buf.is_empty()
104 }
105}
106
107pub struct NdrDecoder<'a> {
109 buf: &'a [u8],
110 pos: usize,
111}
112
113impl<'a> NdrDecoder<'a> {
114 pub fn new(buf: &'a [u8]) -> Self {
115 NdrDecoder { buf, pos: 0 }
116 }
117
118 pub fn align(&mut self, a: usize) {
119 if a <= 1 {
120 return;
121 }
122 let remainder = self.pos % a;
123 if remainder != 0 {
124 self.pos = self.pos.saturating_add(a - remainder);
125 }
126 }
127 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
128 let end = self.pos.checked_add(n).ok_or(NdrError::Underrun {
129 need: n,
130 pos: self.pos,
131 })?;
132 if end > self.buf.len() {
133 return Err(NdrError::Underrun {
134 need: n,
135 pos: self.pos,
136 });
137 }
138 let s = &self.buf[self.pos..end];
139 self.pos = end;
140 Ok(s)
141 }
142
143 pub fn u8(&mut self) -> Result<u8> {
144 Ok(self.take(1)?[0])
145 }
146 pub fn u16(&mut self) -> Result<u16> {
147 self.align(2);
148 Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
149 }
150 pub fn u32(&mut self) -> Result<u32> {
151 self.align(4);
152 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
153 }
154 pub fn u64(&mut self) -> Result<u64> {
155 self.align(8);
156 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
157 }
158 pub fn uuid(&mut self) -> Result<[u8; 16]> {
159 self.align(4);
160 Ok(self.take(16)?.try_into().unwrap())
161 }
162
163 pub fn conformant_varying_wstr(&mut self) -> Result<String> {
174 let max = self.u32()?;
175 let offset = self.u32()?;
176 let actual = self.u32()? as usize;
177 if offset > max || actual > (max - offset) as usize {
178 return Err(NdrError::Underrun {
179 need: actual,
180 pos: self.pos,
181 });
182 }
183 let need = actual.checked_mul(2).ok_or(NdrError::Underrun {
186 need: usize::MAX,
187 pos: self.pos,
188 })?;
189 if need > self.remaining() {
190 return Err(NdrError::Underrun {
191 need,
192 pos: self.pos,
193 });
194 }
195 let mut units = Vec::with_capacity(actual);
196 for _ in 0..actual {
197 units.push(self.u16()?);
198 }
199 while units.last() == Some(&0) {
200 units.pop();
201 }
202 Ok(String::from_utf16_lossy(&units))
203 }
204
205 pub fn read_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
207 self.take(n)
208 }
209
210 pub fn remaining(&self) -> usize {
211 self.buf.len().saturating_sub(self.pos)
212 }
213 pub fn position(&self) -> usize {
214 self.pos
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn primitive_alignment() {
224 let mut e = NdrEncoder::new();
225 e.u8(0x01);
226 e.u32(0xAABB_CCDD); assert_eq!(e.into_bytes(), vec![0x01, 0, 0, 0, 0xDD, 0xCC, 0xBB, 0xAA]);
228 }
229
230 #[test]
231 fn wstr_roundtrip() {
232 let mut e = NdrEncoder::new();
233 e.conformant_varying_wstr("ADHAMMER");
234 let bytes = e.into_bytes();
235 assert_eq!(bytes.len(), 30);
237 let mut d = NdrDecoder::new(&bytes);
238 assert_eq!(d.conformant_varying_wstr().unwrap(), "ADHAMMER");
239 assert_eq!(d.remaining(), 0);
240 }
241
242 #[test]
243 fn referent_ids_are_nonzero_and_advance() {
244 let mut e = NdrEncoder::new();
245 let a = e.referent();
246 let b = e.referent();
247 assert_ne!(a, 0);
248 assert_eq!(b, a + 4);
249 }
250
251 #[test]
252 fn underrun_reports_offset() {
253 let bytes = [0x01, 0x02]; let mut d = NdrDecoder::new(&bytes);
255 assert!(matches!(
256 d.u32(),
257 Err(NdrError::Underrun { need: 4, pos: 0 })
258 ));
259 }
260
261 #[test]
267 fn wstr_rejects_hostile_actual_count() {
268 let mut bytes = Vec::new();
269 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0x7FFF_FFFFu32.to_le_bytes()); let mut d = NdrDecoder::new(&bytes);
274 let start = std::time::Instant::now();
275 let r = d.conformant_varying_wstr();
276 let elapsed_ms = start.elapsed().as_millis();
277 assert!(
278 matches!(r, Err(NdrError::Underrun { .. })),
279 "expected Underrun, got {r:?}"
280 );
281 assert!(
282 elapsed_ms < 50,
283 "preflight must reject in <50ms, took {elapsed_ms}ms — probably allocated"
284 );
285 }
286
287 #[test]
291 fn wstr_rejects_actual_count_overflow() {
292 let mut bytes = Vec::new();
293 bytes.extend_from_slice(&0u32.to_le_bytes());
294 bytes.extend_from_slice(&0u32.to_le_bytes());
295 bytes.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); let mut d = NdrDecoder::new(&bytes);
297 assert!(matches!(
298 d.conformant_varying_wstr(),
299 Err(NdrError::Underrun { .. })
300 ));
301 }
302
303 #[test]
304 fn zero_alignment_is_a_noop() {
305 let mut e = NdrEncoder::new();
306 e.u8(1);
307 e.align(0);
308 assert_eq!(e.into_bytes(), vec![1]);
309
310 let bytes = [1u8];
311 let mut d = NdrDecoder::new(&bytes);
312 d.align(0);
313 assert_eq!(d.u8().unwrap(), 1);
314 }
315
316 #[test]
317 fn alignment_past_end_reports_underrun() {
318 let bytes = [1u8];
319 let mut d = NdrDecoder::new(&bytes);
320 assert_eq!(d.u8().unwrap(), 1);
321 d.align(4);
322 assert_eq!(d.remaining(), 0);
323 assert!(matches!(
324 d.u32(),
325 Err(NdrError::Underrun { need: 4, pos: 4 })
326 ));
327 }
328
329 #[test]
330 fn wstr_rejects_inconsistent_varying_bounds() {
331 let mut bytes = Vec::new();
332 bytes.extend_from_slice(&2u32.to_le_bytes()); bytes.extend_from_slice(&2u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes());
336 let mut d = NdrDecoder::new(&bytes);
337 assert!(d.conformant_varying_wstr().is_err());
338 }
339}