1use std::fmt;
2use std::str::FromStr;
3
4use crate::SshParserError;
5
6const ID_APPEND: char = '+';
7const ID_HEAD: char = '^';
8const ID_EXCLUDE: char = '-';
9
10#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Algorithms {
20 algos: Vec<String>,
22 overridden: bool,
24 rule: Option<AlgorithmsRule>,
26}
27
28impl Algorithms {
29 pub fn new<I, S>(default: I) -> Self
39 where
40 I: IntoIterator<Item = S>,
41 S: AsRef<str>,
42 {
43 Self {
44 algos: default
45 .into_iter()
46 .map(|s| s.as_ref().to_string())
47 .collect(),
48 overridden: false,
49 rule: None,
50 }
51 }
52}
53
54#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum AlgorithmsRule {
74 Append(Vec<String>),
76 Head(Vec<String>),
78 Exclude(Vec<String>),
80 Set(Vec<String>),
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86enum AlgorithmsOp {
87 Append,
88 Head,
89 Exclude,
90 Set,
91}
92
93impl Algorithms {
94 pub fn is_default(&self) -> bool {
96 !self.overridden
97 }
98
99 pub fn algorithms(&self) -> &[String] {
101 &self.algos
102 }
103
104 pub fn apply(&mut self, rule: AlgorithmsRule) {
109 if self.overridden {
110 return;
112 }
113
114 let mut current_algos = self.algos.clone();
115
116 match rule.clone() {
117 AlgorithmsRule::Append(algos) => {
118 for algo in algos {
120 if !current_algos.iter().any(|s| s == &algo) {
121 current_algos.push(algo);
122 }
123 }
124 }
125 AlgorithmsRule::Head(algos) => {
126 current_algos = algos;
127 current_algos.extend(self.algorithms().iter().map(|s| s.to_string()));
128 }
129 AlgorithmsRule::Exclude(exclude) => {
130 current_algos = current_algos
131 .iter()
132 .filter(|algo| !exclude.contains(algo))
133 .map(|s| s.to_string())
134 .collect();
135 }
136 AlgorithmsRule::Set(algos) => {
137 current_algos = algos;
139 }
140 }
141
142 self.rule = Some(rule);
144 self.algos = current_algos;
145 self.overridden = true;
146 }
147}
148
149impl AlgorithmsRule {
150 fn op(&self) -> AlgorithmsOp {
151 match self {
152 Self::Append(_) => AlgorithmsOp::Append,
153 Self::Head(_) => AlgorithmsOp::Head,
154 Self::Exclude(_) => AlgorithmsOp::Exclude,
155 Self::Set(_) => AlgorithmsOp::Set,
156 }
157 }
158}
159
160impl FromStr for AlgorithmsRule {
161 type Err = SshParserError;
162
163 fn from_str(s: &str) -> Result<Self, Self::Err> {
164 if s.is_empty() {
165 return Err(SshParserError::ExpectedAlgorithms);
166 }
167
168 let (op, start) = match s.chars().next().expect("can't be empty") {
170 ID_APPEND => (AlgorithmsOp::Append, 1),
171 ID_HEAD => (AlgorithmsOp::Head, 1),
172 ID_EXCLUDE => (AlgorithmsOp::Exclude, 1),
173 _ => (AlgorithmsOp::Set, 0),
174 };
175
176 let algos = s[start..]
177 .split(',')
178 .map(|s| s.trim().to_string())
179 .collect::<Vec<String>>();
180
181 match op {
182 AlgorithmsOp::Append => Ok(Self::Append(algos)),
183 AlgorithmsOp::Head => Ok(Self::Head(algos)),
184 AlgorithmsOp::Exclude => Ok(Self::Exclude(algos)),
185 AlgorithmsOp::Set => Ok(Self::Set(algos)),
186 }
187 }
188}
189
190impl fmt::Display for AlgorithmsRule {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 let op = self.op();
193 write!(f, "{op}")
194 }
195}
196
197impl fmt::Display for AlgorithmsOp {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match &self {
200 Self::Append => write!(f, "{ID_APPEND}"),
201 Self::Head => write!(f, "{ID_HEAD}"),
202 Self::Exclude => write!(f, "{ID_EXCLUDE}"),
203 Self::Set => write!(f, ""),
204 }
205 }
206}
207
208impl fmt::Display for Algorithms {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 if let Some(rule) = self.rule.as_ref() {
211 write!(f, "{rule}",)
212 } else {
213 write!(f, "{}", self.algos.join(","))
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220
221 use pretty_assertions::assert_eq;
222
223 use super::*;
224
225 #[test]
226 fn test_should_parse_algos_set() {
227 let algo =
228 AlgorithmsRule::from_str("aes128-ctr,aes192-ctr,aes256-ctr").expect("failed to parse");
229 assert_eq!(
230 algo,
231 AlgorithmsRule::Set(vec![
232 "aes128-ctr".to_string(),
233 "aes192-ctr".to_string(),
234 "aes256-ctr".to_string()
235 ])
236 );
237 }
238
239 #[test]
240 fn test_should_parse_algos_append() {
241 let algo =
242 AlgorithmsRule::from_str("+aes128-ctr,aes192-ctr,aes256-ctr").expect("failed to parse");
243 assert_eq!(
244 algo,
245 AlgorithmsRule::Append(vec![
246 "aes128-ctr".to_string(),
247 "aes192-ctr".to_string(),
248 "aes256-ctr".to_string()
249 ])
250 );
251 }
252
253 #[test]
254 fn test_should_parse_algos_head() {
255 let algo =
256 AlgorithmsRule::from_str("^aes128-ctr,aes192-ctr,aes256-ctr").expect("failed to parse");
257 assert_eq!(
258 algo,
259 AlgorithmsRule::Head(vec![
260 "aes128-ctr".to_string(),
261 "aes192-ctr".to_string(),
262 "aes256-ctr".to_string()
263 ])
264 );
265 }
266
267 #[test]
268 fn test_should_parse_algos_exclude() {
269 let algo =
270 AlgorithmsRule::from_str("-aes128-ctr,aes192-ctr,aes256-ctr").expect("failed to parse");
271 assert_eq!(
272 algo,
273 AlgorithmsRule::Exclude(vec![
274 "aes128-ctr".to_string(),
275 "aes192-ctr".to_string(),
276 "aes256-ctr".to_string()
277 ])
278 );
279 }
280
281 #[test]
282 fn test_should_apply_append() {
283 let mut algo1 = Algorithms::new(&["aes128-ctr", "aes192-ctr"]);
284 let algo2 = AlgorithmsRule::from_str("+aes256-ctr").expect("failed to parse");
285 algo1.apply(algo2);
286 assert_eq!(
287 algo1.algorithms(),
288 vec![
289 "aes128-ctr".to_string(),
290 "aes192-ctr".to_string(),
291 "aes256-ctr".to_string()
292 ]
293 );
294 }
295
296 #[test]
297 fn test_should_merge_append_if_undefined() {
298 let algos: Vec<String> = vec![];
299 let mut algo1 = Algorithms::new(algos);
300 let algo2 = AlgorithmsRule::from_str("+aes256-ctr").expect("failed to parse");
301 algo1.apply(algo2);
302 assert_eq!(algo1.algorithms(), vec!["aes256-ctr".to_string()]);
303 }
304
305 #[test]
306 fn test_should_merge_head() {
307 let mut algo1 = Algorithms::new(&["aes128-ctr", "aes192-ctr"]);
308 let algo2 = AlgorithmsRule::from_str("^aes256-ctr").expect("failed to parse");
309 algo1.apply(algo2);
310 assert_eq!(
311 algo1.algorithms(),
312 vec![
313 "aes256-ctr".to_string(),
314 "aes128-ctr".to_string(),
315 "aes192-ctr".to_string()
316 ]
317 );
318 }
319
320 #[test]
321 fn test_should_apply_head() {
322 let mut algo1 = Algorithms::new(&["aes128-ctr", "aes192-ctr"]);
323 let algo2 = AlgorithmsRule::from_str("^aes256-ctr").expect("failed to parse");
324 algo1.apply(algo2);
325 assert_eq!(
326 algo1.algorithms(),
327 vec![
328 "aes256-ctr".to_string(),
329 "aes128-ctr".to_string(),
330 "aes192-ctr".to_string()
331 ]
332 );
333 }
334
335 #[test]
336 fn test_should_merge_exclude() {
337 let mut algo1 = Algorithms::new(&["aes128-ctr", "aes192-ctr", "aes256-ctr"]);
338 let algo2 = AlgorithmsRule::from_str("-aes192-ctr").expect("failed to parse");
339 algo1.apply(algo2);
340 assert_eq!(
341 algo1.algorithms(),
342 vec!["aes128-ctr".to_string(), "aes256-ctr".to_string()]
343 );
344 }
345
346 #[test]
347 fn test_should_merge_set() {
348 let mut algo1 = Algorithms::new(&["aes128-ctr", "aes192-ctr"]);
349 let algo2 = AlgorithmsRule::from_str("aes256-ctr").expect("failed to parse");
350 algo1.apply(algo2);
351 assert_eq!(algo1.algorithms(), vec!["aes256-ctr".to_string()]);
352 }
353
354 #[test]
355 fn test_should_not_apply_twice() {
356 let mut algo1 = Algorithms::new(&["aes128-ctr", "aes192-ctr"]);
357 let algo2 = AlgorithmsRule::from_str("aes256-ctr").expect("failed to parse");
358 algo1.apply(algo2);
359 assert_eq!(algo1.algorithms(), vec!["aes256-ctr".to_string(),]);
360
361 let algo3 = AlgorithmsRule::from_str("aes128-ctr").expect("failed to parse");
362 algo1.apply(algo3);
363 assert_eq!(algo1.algorithms(), vec!["aes256-ctr".to_string()]);
364 assert_eq!(algo1.overridden, true);
365 }
366}