netget/server/netconf/
wire.rs1use anyhow::{bail, ensure, Result};
3use std::collections::VecDeque;
4
5pub const MAX_MESSAGE_BYTES: usize = 1024 * 1024;
6pub const MAX_CHUNKS: usize = 1024;
7pub const MAX_BUFFER_BYTES: usize = MAX_MESSAGE_BYTES + 256 * 1024 + 16 * MAX_CHUNKS;
8pub const DELIMITER: &[u8] = b"]]>]]>";
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Framing {
12 Delimiter,
13 Chunked,
14}
15
16pub struct Decoder {
17 framing: Framing,
18 input: VecDeque<u8>,
19 body: Vec<u8>,
20 header: Vec<u8>,
21 remaining: usize,
22 chunks: usize,
23}
24impl Decoder {
25 pub fn new(framing: Framing) -> Self {
26 Self {
27 framing,
28 input: VecDeque::new(),
29 body: Vec::new(),
30 header: Vec::new(),
31 remaining: 0,
32 chunks: 0,
33 }
34 }
35 pub fn set_framing(&mut self, framing: Framing) -> Result<()> {
36 ensure!(
37 self.body.is_empty()
38 && self.header.is_empty()
39 && self.remaining == 0
40 && self.chunks == 0,
41 "NETCONF framing transition during a message"
42 );
43 self.framing = framing;
44 Ok(())
45 }
46 pub fn feed(&mut self, bytes: &[u8]) -> Result<()> {
47 ensure!(
48 bytes.len() <= MAX_BUFFER_BYTES.saturating_sub(self.input.len()),
49 "NETCONF input queue bound"
50 );
51 self.input.extend(bytes);
52 Ok(())
53 }
54 pub fn is_partial(&self) -> bool {
55 !self.input.is_empty()
56 || !self.body.is_empty()
57 || !self.header.is_empty()
58 || self.remaining > 0
59 || self.chunks > 0
60 }
61 pub fn next_message(&mut self) -> Result<Option<Vec<u8>>> {
62 if self.framing == Framing::Delimiter {
63 while let Some(byte) = self.input.pop_front() {
64 self.body.push(byte);
65 if self.body.ends_with(DELIMITER) {
66 self.body.truncate(self.body.len() - DELIMITER.len());
67 ensure!(
68 self.body.len() <= MAX_MESSAGE_BYTES,
69 "NETCONF message byte bound"
70 );
71 return Ok(Some(std::mem::take(&mut self.body)));
72 }
73 ensure!(
74 self.body.len() < MAX_MESSAGE_BYTES + DELIMITER.len(),
75 "NETCONF message byte bound"
76 );
77 }
78 return Ok(None);
79 }
80 loop {
81 if self.remaining > 0 {
82 let available = self.remaining.min(self.input.len());
83 for _ in 0..available {
84 self.body
85 .push(self.input.pop_front().expect("available byte"));
86 }
87 self.remaining -= available;
88 if self.remaining > 0 {
89 return Ok(None);
90 }
91 }
92 let Some(byte) = self.input.pop_front() else {
93 return Ok(None);
94 };
95 ensure!(self.header.len() < 13, "NETCONF chunk header bound");
96 self.header.push(byte);
97 match self.header.len() {
98 1 => ensure!(byte == b'\n', "NETCONF chunk must start with LF"),
99 2 => ensure!(byte == b'#', "NETCONF chunk must start with LF HASH"),
100 3 => ensure!(
101 byte == b'#' || (b'1'..=b'9').contains(&byte),
102 "NETCONF chunk size must be positive without leading zero"
103 ),
104 _ => {
105 if self.header[2] == b'#' {
106 ensure!(
107 self.header.len() == 4 && byte == b'\n',
108 "NETCONF invalid end-of-chunks"
109 );
110 ensure!(self.chunks > 0, "NETCONF empty chunked message");
111 self.header.clear();
112 self.chunks = 0;
113 return Ok(Some(std::mem::take(&mut self.body)));
114 }
115 if byte != b'\n' {
116 ensure!(byte.is_ascii_digit(), "NETCONF invalid chunk size digit");
117 continue;
118 }
119 let mut length = 0u64;
120 for digit in &self.header[2..self.header.len() - 1] {
121 length = length * 10 + u64::from(digit - b'0');
122 }
123 ensure!(
124 length <= u64::from(u32::MAX),
125 "NETCONF native chunk size maximum"
126 );
127 ensure!(
128 length <= (MAX_MESSAGE_BYTES - self.body.len()) as u64,
129 "NETCONF aggregate message byte bound"
130 );
131 ensure!(self.chunks < MAX_CHUNKS, "NETCONF chunk count bound");
132 self.chunks += 1;
133 self.remaining = length as usize;
134 self.header.clear();
135 }
136 }
137 }
138 }
139}
140
141pub fn frame(message: &[u8], framing: Framing) -> Result<Vec<u8>> {
142 ensure!(
143 !message.is_empty() && message.len() <= MAX_MESSAGE_BYTES,
144 "NETCONF outgoing message byte bound"
145 );
146 match framing {
147 Framing::Delimiter => {
148 if message.windows(DELIMITER.len()).any(|w| w == DELIMITER) {
149 bail!("NETCONF delimiter inside outgoing XML");
150 }
151 let mut framed = Vec::with_capacity(message.len() + DELIMITER.len());
152 framed.extend_from_slice(message);
153 framed.extend_from_slice(DELIMITER);
154 Ok(framed)
155 }
156 Framing::Chunked => {
157 let mut framed = format!("\n#{}\n", message.len()).into_bytes();
158 framed.extend_from_slice(message);
159 framed.extend_from_slice(b"\n##\n");
160 Ok(framed)
161 }
162 }
163}