1use bytes::{Buf, BufMut, BytesMut};
2use std::borrow::Cow;
3use std::io;
4use tokio_util::codec::{Decoder, Encoder};
5use tracing::{debug, warn};
6
7use crate::command::{OvpnCommand, ResponseKind};
8use crate::kill_target::KillTarget;
9use crate::message::{Notification, OvpnMessage};
10use crate::proxy_action::ProxyAction;
11use crate::redacted::Redacted;
12use crate::remote_action::RemoteAction;
13use crate::status_format::StatusFormat;
14use crate::unrecognized::UnrecognizedKind;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum EncoderMode {
20 #[default]
25 Sanitize,
26
27 Strict,
34}
35
36#[derive(Debug, thiserror::Error)]
43pub enum EncodeError {
44 #[error("{0} contains characters unsafe for the management protocol (\\n, \\r, or \\0)")]
46 UnsafeCharacters(&'static str),
47
48 #[error("block body line equals \"END\", which would terminate the block early")]
50 EndInBlockBody,
51}
52
53const WIRE_UNSAFE: &[char] = &['\n', '\r', '\0'];
56
57fn wire_safe<'a>(
65 s: &'a str,
66 field: &'static str,
67 mode: EncoderMode,
68) -> Result<Cow<'a, str>, io::Error> {
69 if !s.contains(WIRE_UNSAFE) {
70 return Ok(Cow::Borrowed(s));
71 }
72 match mode {
73 EncoderMode::Sanitize => Ok(Cow::Owned(
74 s.chars().filter(|c| !WIRE_UNSAFE.contains(c)).collect(),
75 )),
76 EncoderMode::Strict => Err(io::Error::other(EncodeError::UnsafeCharacters(field))),
77 }
78}
79
80fn quote_and_escape(s: &str) -> String {
92 let mut out = String::with_capacity(s.len() + 2);
93 out.push('"');
94 for c in s.chars() {
95 match c {
96 '\\' => out.push_str("\\\\"),
97 '"' => out.push_str("\\\""),
98 _ => out.push(c),
99 }
100 }
101 out.push('"');
102 out
103}
104
105use crate::client_event::ClientEvent;
106use crate::log_level::LogLevel;
107use crate::openvpn_state::OpenVpnState;
108use crate::transport_protocol::TransportProtocol;
109
110#[derive(Debug)]
112struct ClientNotifAccum {
113 event: ClientEvent,
114 cid: u64,
115 kid: Option<u64>,
116 env: Vec<(String, String)>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum AccumulationLimit {
123 Unlimited,
125 Max(usize),
127}
128
129pub struct OvpnCodec {
151 expected: ResponseKind,
157
158 multi_line_buf: Option<Vec<String>>,
160
161 client_notif: Option<ClientNotifAccum>,
164
165 max_multi_line_lines: AccumulationLimit,
167
168 max_client_env_entries: AccumulationLimit,
170
171 encoder_mode: EncoderMode,
173}
174
175impl OvpnCodec {
176 pub fn new() -> Self {
179 Self {
180 expected: ResponseKind::SuccessOrError,
185 multi_line_buf: None,
186 client_notif: None,
187 max_multi_line_lines: AccumulationLimit::Unlimited,
188 max_client_env_entries: AccumulationLimit::Unlimited,
189 encoder_mode: EncoderMode::default(),
190 }
191 }
192
193 pub fn with_max_multi_line_lines(mut self, limit: AccumulationLimit) -> Self {
196 self.max_multi_line_lines = limit;
197 self
198 }
199
200 pub fn with_max_client_env_entries(mut self, limit: AccumulationLimit) -> Self {
203 self.max_client_env_entries = limit;
204 self
205 }
206
207 pub fn with_encoder_mode(mut self, mode: EncoderMode) -> Self {
214 self.encoder_mode = mode;
215 self
216 }
217}
218
219fn check_accumulation_limit(
220 current_len: usize,
221 limit: AccumulationLimit,
222 what: &str,
223) -> Result<(), io::Error> {
224 if let AccumulationLimit::Max(max) = limit
225 && current_len >= max
226 {
227 return Err(io::Error::other(format!(
228 "{what} accumulation limit exceeded ({max})"
229 )));
230 }
231 Ok(())
232}
233
234impl Default for OvpnCodec {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240impl Encoder<OvpnCommand> for OvpnCodec {
243 type Error = io::Error;
244
245 fn encode(&mut self, item: OvpnCommand, dst: &mut BytesMut) -> Result<(), Self::Error> {
246 debug_assert!(
247 self.multi_line_buf.is_none() && self.client_notif.is_none(),
248 "encode() called while the decoder is mid-accumulation \
249 (multi_line_buf or client_notif is active). \
250 Drain decode() before sending a new command."
251 );
252
253 self.expected = item.expected_response();
256 let label: &'static str = (&item).into();
257 debug!(cmd = %label, expected = ?self.expected, "encoding command");
258
259 let mode = self.encoder_mode;
260
261 match item {
262 OvpnCommand::Status(StatusFormat::V1) => write_line(dst, "status"),
264 OvpnCommand::Status(ref fmt) => write_line(dst, &format!("status {fmt}")),
265 OvpnCommand::State => write_line(dst, "state"),
266 OvpnCommand::StateStream(ref m) => write_line(dst, &format!("state {m}")),
267 OvpnCommand::Version => write_line(dst, "version"),
268 OvpnCommand::Pid => write_line(dst, "pid"),
269 OvpnCommand::Help => write_line(dst, "help"),
270 OvpnCommand::Net => write_line(dst, "net"),
271 OvpnCommand::Verb(Some(n)) => write_line(dst, &format!("verb {n}")),
272 OvpnCommand::Verb(None) => write_line(dst, "verb"),
273 OvpnCommand::Mute(Some(n)) => write_line(dst, &format!("mute {n}")),
274 OvpnCommand::Mute(None) => write_line(dst, "mute"),
275
276 OvpnCommand::Log(ref m) => write_line(dst, &format!("log {m}")),
278 OvpnCommand::Echo(ref m) => write_line(dst, &format!("echo {m}")),
279 OvpnCommand::ByteCount(n) => write_line(dst, &format!("bytecount {n}")),
280
281 OvpnCommand::Signal(sig) => write_line(dst, &format!("signal {sig}")),
283 OvpnCommand::Kill(KillTarget::CommonName(ref cn)) => {
284 write_line(dst, &format!("kill {}", wire_safe(cn, "kill CN", mode)?));
285 }
286 OvpnCommand::Kill(KillTarget::Address {
287 ref protocol,
288 ref ip,
289 port,
290 }) => {
291 write_line(
292 dst,
293 &format!(
294 "kill {}:{}:{port}",
295 wire_safe(protocol, "kill address protocol", mode)?,
296 wire_safe(ip, "kill address ip", mode)?
297 ),
298 );
299 }
300 OvpnCommand::HoldQuery => write_line(dst, "hold"),
301 OvpnCommand::HoldOn => write_line(dst, "hold on"),
302 OvpnCommand::HoldOff => write_line(dst, "hold off"),
303 OvpnCommand::HoldRelease => write_line(dst, "hold release"),
304
305 OvpnCommand::Username {
310 ref auth_type,
311 ref value,
312 } => {
313 let at = quote_and_escape(&wire_safe(
317 &auth_type.to_string(),
318 "username auth_type",
319 mode,
320 )?);
321 let val = quote_and_escape(&wire_safe(value.expose(), "username value", mode)?);
322 write_line(dst, &format!("username {at} {val}"));
323 }
324 OvpnCommand::Password {
325 ref auth_type,
326 ref value,
327 } => {
328 let at = quote_and_escape(&wire_safe(
329 &auth_type.to_string(),
330 "password auth_type",
331 mode,
332 )?);
333 let val = quote_and_escape(&wire_safe(value.expose(), "password value", mode)?);
334 write_line(dst, &format!("password {at} {val}"));
335 }
336 OvpnCommand::AuthRetry(auth_retry_mode) => {
337 write_line(dst, &format!("auth-retry {auth_retry_mode}"));
338 }
339 OvpnCommand::ForgetPasswords => write_line(dst, "forget-passwords"),
340
341 OvpnCommand::ChallengeResponse {
343 ref state_id,
344 ref response,
345 } => {
346 let sid = wire_safe(state_id, "challenge-response state_id", mode)?;
347 let resp = wire_safe(response.expose(), "challenge-response response", mode)?;
348 let value = format!("CRV1::{sid}::{resp}");
349 let escaped = quote_and_escape(&value);
350 write_line(dst, &format!("password \"Auth\" {escaped}"));
351 }
352 OvpnCommand::StaticChallengeResponse {
353 ref password_b64,
354 ref response_b64,
355 } => {
356 let pw = wire_safe(password_b64.expose(), "static-challenge password_b64", mode)?;
357 let resp = wire_safe(response_b64.expose(), "static-challenge response_b64", mode)?;
358 let value = format!("SCRV1:{pw}:{resp}");
359 let escaped = quote_and_escape(&value);
360 write_line(dst, &format!("password \"Auth\" {escaped}"));
361 }
362
363 OvpnCommand::NeedOk { ref name, response } => {
365 write_line(
366 dst,
367 &format!(
368 "needok {} {response}",
369 wire_safe(name, "needok name", mode)?
370 ),
371 );
372 }
373 OvpnCommand::NeedStr {
374 ref name,
375 ref value,
376 } => {
377 let escaped = quote_and_escape(&wire_safe(value, "needstr value", mode)?);
378 write_line(
379 dst,
380 &format!(
381 "needstr {} {escaped}",
382 wire_safe(name, "needstr name", mode)?
383 ),
384 );
385 }
386
387 OvpnCommand::Pkcs11IdCount => write_line(dst, "pkcs11-id-count"),
389 OvpnCommand::Pkcs11IdGet(idx) => write_line(dst, &format!("pkcs11-id-get {idx}")),
390
391 OvpnCommand::RsaSig { ref base64_lines } => {
399 write_block(dst, "rsa-sig", base64_lines, mode)?;
400 }
401
402 OvpnCommand::ClientAuth {
410 cid,
411 kid,
412 ref config_lines,
413 } => {
414 write_block(dst, &format!("client-auth {cid} {kid}"), config_lines, mode)?;
415 }
416
417 OvpnCommand::ClientAuthNt { cid, kid } => {
418 write_line(dst, &format!("client-auth-nt {cid} {kid}"));
419 }
420
421 OvpnCommand::ClientDeny {
422 cid,
423 kid,
424 ref reason,
425 ref client_reason,
426 } => {
427 let r = quote_and_escape(&wire_safe(reason, "client-deny reason", mode)?);
428 match client_reason {
429 Some(cr) => {
430 let cr_esc =
431 quote_and_escape(&wire_safe(cr, "client-deny client_reason", mode)?);
432 write_line(dst, &format!("client-deny {cid} {kid} {r} {cr_esc}"));
433 }
434 None => write_line(dst, &format!("client-deny {cid} {kid} {r}")),
435 }
436 }
437
438 OvpnCommand::ClientKill { cid, ref message } => match message {
439 Some(msg) => write_line(
440 dst,
441 &format!(
442 "client-kill {cid} {}",
443 wire_safe(msg, "client-kill message", mode)?
444 ),
445 ),
446 None => write_line(dst, &format!("client-kill {cid}")),
447 },
448
449 OvpnCommand::LoadStats => write_line(dst, "load-stats"),
451
452 OvpnCommand::ClientPendingAuth {
459 cid,
460 kid,
461 ref extra,
462 timeout,
463 } => write_line(
464 dst,
465 &format!(
466 "client-pending-auth {cid} {kid} {} {timeout}",
467 wire_safe(extra, "client-pending-auth extra", mode)?
468 ),
469 ),
470
471 OvpnCommand::CrResponse { ref response } => {
472 write_line(
473 dst,
474 &format!(
475 "cr-response {}",
476 wire_safe(response.expose(), "cr-response", mode)?
477 ),
478 );
479 }
480
481 OvpnCommand::Certificate { ref pem_lines } => {
483 write_block(dst, "certificate", pem_lines, mode)?;
484 }
485
486 OvpnCommand::Remote(RemoteAction::Accept) => write_line(dst, "remote ACCEPT"),
488 OvpnCommand::Remote(RemoteAction::Skip) => write_line(dst, "remote SKIP"),
489 OvpnCommand::Remote(RemoteAction::Modify { ref host, port }) => {
490 write_line(
491 dst,
492 &format!(
493 "remote MOD {} {port}",
494 wire_safe(host, "remote MOD host", mode)?
495 ),
496 );
497 }
498 OvpnCommand::Proxy(ProxyAction::None) => write_line(dst, "proxy NONE"),
499 OvpnCommand::Proxy(ProxyAction::Http {
500 ref host,
501 port,
502 non_cleartext_only,
503 }) => {
504 let nct = if non_cleartext_only { " nct" } else { "" };
505 write_line(
506 dst,
507 &format!(
508 "proxy HTTP {} {port}{nct}",
509 wire_safe(host, "proxy HTTP host", mode)?
510 ),
511 );
512 }
513 OvpnCommand::Proxy(ProxyAction::Socks { ref host, port }) => {
514 write_line(
515 dst,
516 &format!(
517 "proxy SOCKS {} {port}",
518 wire_safe(host, "proxy SOCKS host", mode)?
519 ),
520 );
521 }
522
523 OvpnCommand::ManagementPassword(ref pw) => {
527 write_line(dst, &wire_safe(pw.expose(), "management password", mode)?);
528 }
529
530 OvpnCommand::Exit => write_line(dst, "exit"),
532 OvpnCommand::Quit => write_line(dst, "quit"),
533
534 OvpnCommand::Raw(ref cmd) | OvpnCommand::RawMultiLine(ref cmd) => {
536 write_line(dst, &wire_safe(cmd, "raw command", mode)?);
537 }
538 }
539
540 Ok(())
541 }
542}
543
544fn write_line(dst: &mut BytesMut, s: &str) {
546 dst.reserve(s.len() + 1);
547 dst.put_slice(s.as_bytes());
548 dst.put_u8(b'\n');
549}
550
551fn write_block(
560 dst: &mut BytesMut,
561 header: &str,
562 lines: &[String],
563 mode: EncoderMode,
564) -> Result<(), io::Error> {
565 let total: usize = header.len() + 1 + lines.iter().map(|l| l.len() + 2).sum::<usize>() + 4;
566 dst.reserve(total);
567 dst.put_slice(header.as_bytes());
568 dst.put_u8(b'\n');
569 for line in lines {
570 let clean = wire_safe(line, "block body line", mode)?;
571 if *clean == *"END" {
572 match mode {
573 EncoderMode::Sanitize => {
574 dst.put_slice(b" END");
575 dst.put_u8(b'\n');
576 continue;
577 }
578 EncoderMode::Strict => {
579 return Err(io::Error::other(EncodeError::EndInBlockBody));
580 }
581 }
582 }
583 dst.put_slice(clean.as_bytes());
584 dst.put_u8(b'\n');
585 }
586 dst.put_slice(b"END\n");
587 Ok(())
588}
589
590impl Decoder for OvpnCodec {
593 type Item = OvpnMessage;
594 type Error = io::Error;
595
596 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
597 loop {
598 const PW_PROMPT: &[u8] = b"ENTER PASSWORD:";
604
605 let Some(newline_pos) = src.iter().position(|&b| b == b'\n') else {
607 if src.starts_with(PW_PROMPT) {
614 let mut consume = PW_PROMPT.len();
615 if src.get(consume) == Some(&b'\r') {
616 consume += 1;
617 }
618 src.advance(consume);
619 return Ok(Some(OvpnMessage::PasswordPrompt));
620 }
621 return Ok(None); };
623
624 let line_bytes = src.split_to(newline_pos + 1);
626 let line = match std::str::from_utf8(&line_bytes) {
627 Ok(s) => s,
628 Err(e) => {
629 self.multi_line_buf = None;
632 self.client_notif = None;
633 self.expected = ResponseKind::SuccessOrError;
634 return Err(io::Error::new(io::ErrorKind::InvalidData, e));
635 }
636 }
637 .trim_end_matches(['\r', '\n'])
638 .to_string();
639
640 if line.is_empty()
647 && self.multi_line_buf.is_none()
648 && self.client_notif.is_none()
649 && !matches!(self.expected, ResponseKind::MultiLine)
650 {
651 continue;
652 }
653
654 if let Some(ref mut accum) = self.client_notif
663 && let Some(rest) = line.strip_prefix(">CLIENT:ENV,")
664 {
665 if rest == "END" {
666 let finished = self.client_notif.take().expect("guarded by if-let");
667 debug!(event = ?finished.event, cid = finished.cid, env_count = finished.env.len(), "decoded CLIENT notification");
668 return Ok(Some(OvpnMessage::Notification(Notification::Client {
669 event: finished.event,
670 cid: finished.cid,
671 kid: finished.kid,
672 env: finished.env,
673 })));
674 } else {
675 let (k, v) = rest
677 .split_once('=')
678 .map(|(k, v)| (k.to_string(), v.to_string()))
679 .unwrap_or_else(|| (rest.to_string(), String::new()));
680 check_accumulation_limit(
681 accum.env.len(),
682 self.max_client_env_entries,
683 "client ENV",
684 )?;
685 accum.env.push((k, v));
686 continue; }
688 }
689 if let Some(ref mut buf) = self.multi_line_buf {
694 if line == "END" {
695 let lines = self.multi_line_buf.take().expect("guarded by if-let");
696 debug!(line_count = lines.len(), "decoded multi-line response");
697 return Ok(Some(OvpnMessage::MultiLine(lines)));
698 }
699 if line.starts_with('>') {
704 if let Some(msg) = self.parse_notification(&line) {
705 return Ok(Some(msg));
706 }
707 continue;
710 }
711 check_accumulation_limit(
712 buf.len(),
713 self.max_multi_line_lines,
714 "multi-line response",
715 )?;
716 buf.push(line);
717 continue; }
719
720 if let Some(rest) = line.strip_prefix("SUCCESS:") {
726 return Ok(Some(OvpnMessage::Success(
727 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
728 )));
729 }
730 if let Some(rest) = line.strip_prefix("ERROR:") {
731 return Ok(Some(OvpnMessage::Error(
732 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
733 )));
734 }
735
736 if line == "ENTER PASSWORD:" {
738 return Ok(Some(OvpnMessage::PasswordPrompt));
739 }
740
741 if line.starts_with('>') {
743 if let Some(msg) = self.parse_notification(&line) {
744 return Ok(Some(msg));
745 }
746 continue;
748 }
749
750 match self.expected {
756 ResponseKind::MultiLine => {
757 if line == "END" {
758 return Ok(Some(OvpnMessage::MultiLine(Vec::new())));
760 }
761 self.multi_line_buf = Some(vec![line]);
762 continue; }
764 ResponseKind::SuccessOrError | ResponseKind::NoResponse => {
765 warn!(line = %line, "unrecognized line from server");
766 return Ok(Some(OvpnMessage::Unrecognized {
767 line,
768 kind: UnrecognizedKind::UnexpectedLine,
769 }));
770 }
771 }
772 }
773 }
774}
775
776impl OvpnCodec {
777 fn parse_notification(&mut self, line: &str) -> Option<OvpnMessage> {
781 let inner = &line[1..]; let Some((kind, payload)) = inner.split_once(':') else {
784 warn!(line = %line, "malformed notification (no colon)");
786 return Some(OvpnMessage::Unrecognized {
787 line: line.to_string(),
788 kind: UnrecognizedKind::MalformedNotification,
789 });
790 };
791
792 if kind == "INFO" {
795 return Some(OvpnMessage::Info(payload.to_string()));
796 }
797
798 if kind == "CLIENT" {
800 let (event, args) = payload
801 .split_once(',')
802 .map(|(e, a)| (e.to_string(), a.to_string()))
803 .unwrap_or_else(|| (payload.to_string(), String::new()));
804
805 if event == "ADDRESS" {
807 let mut parts = args.splitn(3, ',');
808 let cid = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
809 let addr = parts.next().unwrap_or("").to_string();
810 let primary = parts.next() == Some("1");
811 return Some(OvpnMessage::Notification(Notification::ClientAddress {
812 cid,
813 addr,
814 primary,
815 }));
816 }
817
818 let mut id_parts = args.splitn(3, ',');
822 let cid = id_parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
823 let kid = id_parts.next().and_then(|s| s.parse().ok());
824
825 let parsed_event = if event == "CR_RESPONSE" {
826 let response = id_parts.next().unwrap_or("").to_string();
827 ClientEvent::CrResponse(response)
828 } else {
829 ClientEvent::parse(&event)
830 };
831
832 self.client_notif = Some(ClientNotifAccum {
834 event: parsed_event,
835 cid,
836 kid,
837 env: Vec::new(),
838 });
839 return None; }
841
842 let notification = match kind {
844 "STATE" => parse_state(payload),
845 "BYTECOUNT" => parse_bytecount(payload),
846 "BYTECOUNT_CLI" => parse_bytecount_cli(payload),
847 "LOG" => parse_log(payload),
848 "ECHO" => parse_echo(payload),
849 "HOLD" => Some(Notification::Hold {
850 text: payload.to_string(),
851 }),
852 "FATAL" => Some(Notification::Fatal {
853 message: payload.to_string(),
854 }),
855 "PKCS11ID-COUNT" => parse_pkcs11id_count(payload),
856 "NEED-OK" => parse_need_ok(payload),
857 "NEED-STR" => parse_need_str(payload),
858 "RSA_SIGN" => Some(Notification::RsaSign {
859 data: payload.to_string(),
860 }),
861 "REMOTE" => parse_remote(payload),
862 "PROXY" => parse_proxy(payload),
863 "PASSWORD" => parse_password(payload),
864 "PKCS11ID-ENTRY" => {
865 return parse_pkcs11id_entry_notif(payload).or_else(|| {
866 Some(OvpnMessage::Notification(Notification::Simple {
867 kind: kind.to_string(),
868 payload: payload.to_string(),
869 }))
870 });
871 }
872 _ => None,
873 };
874
875 Some(OvpnMessage::Notification(notification.unwrap_or(
876 Notification::Simple {
877 kind: kind.to_string(),
878 payload: payload.to_string(),
879 },
880 )))
881 }
882}
883
884fn parse_state(payload: &str) -> Option<Notification> {
892 let mut parts = payload.splitn(9, ',');
896 let timestamp = parts.next()?.parse().ok()?;
897 let name = OpenVpnState::parse(parts.next()?);
898 let description = parts.next()?.to_string();
899 let local_ip = parts.next()?.to_string();
900 let remote_ip = parts.next()?.to_string();
901 let remote_port = parts.next().unwrap_or("").to_string();
902 let local_addr = parts.next().unwrap_or("").to_string();
903 let local_port = parts.next().unwrap_or("").to_string();
904 let local_ipv6 = parts.next().unwrap_or("").to_string();
905 Some(Notification::State {
906 timestamp,
907 name,
908 description,
909 local_ip,
910 remote_ip,
911 remote_port,
912 local_addr,
913 local_port,
914 local_ipv6,
915 })
916}
917
918fn parse_bytecount(payload: &str) -> Option<Notification> {
919 let (a, b) = payload.split_once(',')?;
920 Some(Notification::ByteCount {
921 bytes_in: a.parse().ok()?,
922 bytes_out: b.parse().ok()?,
923 })
924}
925
926fn parse_bytecount_cli(payload: &str) -> Option<Notification> {
927 let mut parts = payload.splitn(3, ',');
928 let cid = parts.next()?.parse().ok()?;
929 let bytes_in = parts.next()?.parse().ok()?;
930 let bytes_out = parts.next()?.parse().ok()?;
931 Some(Notification::ByteCountCli {
932 cid,
933 bytes_in,
934 bytes_out,
935 })
936}
937
938fn parse_log(payload: &str) -> Option<Notification> {
939 let (ts_str, rest) = payload.split_once(',')?;
940 let timestamp = ts_str.parse().ok()?;
941 let (level_str, message) = rest.split_once(',')?;
942 Some(Notification::Log {
943 timestamp,
944 level: LogLevel::parse(level_str),
945 message: message.to_string(),
946 })
947}
948
949fn parse_echo(payload: &str) -> Option<Notification> {
950 let (ts_str, param) = payload.split_once(',')?;
951 let timestamp = ts_str.parse().ok()?;
952 Some(Notification::Echo {
953 timestamp,
954 param: param.to_string(),
955 })
956}
957
958fn parse_pkcs11id_count(payload: &str) -> Option<Notification> {
959 let count = payload.trim().parse().ok()?;
960 Some(Notification::Pkcs11IdCount { count })
961}
962
963fn parse_pkcs11id_entry_notif(payload: &str) -> Option<OvpnMessage> {
966 let rest = payload.strip_prefix('\'')?;
967 let (index, rest) = rest.split_once("', ID:'")?;
968 let (id, rest) = rest.split_once("', BLOB:'")?;
969 let blob = rest.strip_suffix('\'')?;
970 Some(OvpnMessage::Pkcs11IdEntry {
971 index: index.to_string(),
972 id: id.to_string(),
973 blob: blob.to_string(),
974 })
975}
976
977fn parse_need_ok(payload: &str) -> Option<Notification> {
979 let rest = payload.strip_prefix("Need '")?;
981 let (name, rest) = rest.split_once('\'')?;
982 let msg = rest.split_once("MSG:")?.1;
983 Some(Notification::NeedOk {
984 name: name.to_string(),
985 message: msg.to_string(),
986 })
987}
988
989fn parse_need_str(payload: &str) -> Option<Notification> {
991 let rest = payload.strip_prefix("Need '")?;
992 let (name, rest) = rest.split_once('\'')?;
993 let msg = rest.split_once("MSG:")?.1;
994 Some(Notification::NeedStr {
995 name: name.to_string(),
996 message: msg.to_string(),
997 })
998}
999
1000fn parse_remote(payload: &str) -> Option<Notification> {
1001 let mut parts = payload.splitn(3, ',');
1002 let host = parts.next()?.to_string();
1003 let port = parts.next()?.parse().ok()?;
1004 let protocol = TransportProtocol::parse(parts.next()?);
1005 Some(Notification::Remote {
1006 host,
1007 port,
1008 protocol,
1009 })
1010}
1011
1012fn parse_proxy(payload: &str) -> Option<Notification> {
1013 let mut parts = payload.splitn(3, ',');
1015 let index = parts.next()?.parse().ok()?;
1016 let proxy_type = parts.next()?.to_string();
1017 let host = parts.next()?.to_string();
1018 Some(Notification::Proxy {
1019 index,
1020 proxy_type,
1021 host,
1022 })
1023}
1024
1025use crate::message::PasswordNotification;
1026
1027use crate::auth::AuthType;
1028
1029fn parse_auth_type(s: &str) -> AuthType {
1031 match s {
1032 "Auth" => AuthType::Auth,
1033 "Private Key" => AuthType::PrivateKey,
1034 "HTTP Proxy" => AuthType::HttpProxy,
1035 "SOCKS Proxy" => AuthType::SocksProxy,
1036 other => AuthType::Custom(other.to_string()),
1037 }
1038}
1039
1040fn parse_password(payload: &str) -> Option<Notification> {
1041 if let Some(token) = payload.strip_prefix("Auth-Token:") {
1044 return Some(Notification::Password(PasswordNotification::AuthToken {
1045 token: Redacted::new(token),
1046 }));
1047 }
1048
1049 if let Some(rest) = payload.strip_prefix("Verification Failed: '") {
1052 if let Some((auth_part, crv1_part)) = rest.split_once("' ['CRV1:") {
1054 let _ = auth_part; let crv1_data = crv1_part.strip_suffix("']")?;
1056 let mut parts = crv1_data.splitn(4, ':');
1057 let flags = parts.next()?.to_string();
1058 let state_id = parts.next()?.to_string();
1059 let username_b64 = parts.next()?.to_string();
1060 let challenge = parts.next()?.to_string();
1061 return Some(Notification::Password(
1062 PasswordNotification::DynamicChallenge {
1063 flags,
1064 state_id,
1065 username_b64,
1066 challenge,
1067 },
1068 ));
1069 }
1070 let auth_type = rest.strip_suffix('\'')?;
1072 return Some(Notification::Password(
1073 PasswordNotification::VerificationFailed {
1074 auth_type: parse_auth_type(auth_type),
1075 },
1076 ));
1077 }
1078
1079 let rest = payload.strip_prefix("Need '")?;
1082 let (auth_type_str, rest) = rest.split_once('\'')?;
1083 let rest = rest.trim_start();
1084
1085 if let Some(after_up) = rest.strip_prefix("username/password") {
1086 let after_up = after_up.trim_start();
1087
1088 if let Some(sc) = after_up.strip_prefix("SC:") {
1091 let (flag_str, challenge) = sc.split_once(',')?;
1092 let flags: u32 = flag_str.parse().ok()?;
1093 return Some(Notification::Password(
1094 PasswordNotification::StaticChallenge {
1095 echo: flags & 1 != 0,
1096 response_concat: flags & 2 != 0,
1097 challenge: challenge.to_string(),
1098 },
1099 ));
1100 }
1101
1102 return Some(Notification::Password(PasswordNotification::NeedAuth {
1104 auth_type: parse_auth_type(auth_type_str),
1105 }));
1106 }
1107
1108 if rest.starts_with("password") {
1110 return Some(Notification::Password(PasswordNotification::NeedPassword {
1111 auth_type: parse_auth_type(auth_type_str),
1112 }));
1113 }
1114
1115 None }
1117
1118#[cfg(test)]
1119mod tests {
1120 use super::*;
1121 use crate::auth::AuthType;
1122 use crate::client_event::ClientEvent;
1123 use crate::message::PasswordNotification;
1124 use crate::signal::Signal;
1125 use crate::status_format::StatusFormat;
1126 use crate::stream_mode::StreamMode;
1127 use bytes::BytesMut;
1128 use tokio_util::codec::{Decoder, Encoder};
1129
1130 fn encode_to_string(cmd: OvpnCommand) -> String {
1132 let mut codec = OvpnCodec::new();
1133 let mut buf = BytesMut::new();
1134 codec.encode(cmd, &mut buf).unwrap();
1135 String::from_utf8(buf.to_vec()).unwrap()
1136 }
1137
1138 fn decode_all(input: &str) -> Vec<OvpnMessage> {
1140 let mut codec = OvpnCodec::new();
1141 let mut buf = BytesMut::from(input);
1142 let mut msgs = Vec::new();
1143 while let Some(msg) = codec.decode(&mut buf).unwrap() {
1144 msgs.push(msg);
1145 }
1146 msgs
1147 }
1148
1149 fn encode_then_decode(cmd: OvpnCommand, response: &str) -> Vec<OvpnMessage> {
1151 let mut codec = OvpnCodec::new();
1152 let mut enc_buf = BytesMut::new();
1153 codec.encode(cmd, &mut enc_buf).unwrap();
1154 let mut dec_buf = BytesMut::from(response);
1155 let mut msgs = Vec::new();
1156 while let Some(msg) = codec.decode(&mut dec_buf).unwrap() {
1157 msgs.push(msg);
1158 }
1159 msgs
1160 }
1161
1162 #[test]
1165 fn encode_status_v1() {
1166 assert_eq!(
1167 encode_to_string(OvpnCommand::Status(StatusFormat::V1)),
1168 "status\n"
1169 );
1170 }
1171
1172 #[test]
1173 fn encode_status_v3() {
1174 assert_eq!(
1175 encode_to_string(OvpnCommand::Status(StatusFormat::V3)),
1176 "status 3\n"
1177 );
1178 }
1179
1180 #[test]
1181 fn encode_signal() {
1182 assert_eq!(
1183 encode_to_string(OvpnCommand::Signal(Signal::SigUsr1)),
1184 "signal SIGUSR1\n"
1185 );
1186 }
1187
1188 #[test]
1189 fn encode_state_on_all() {
1190 assert_eq!(
1191 encode_to_string(OvpnCommand::StateStream(StreamMode::OnAll)),
1192 "state on all\n"
1193 );
1194 }
1195
1196 #[test]
1197 fn encode_state_recent() {
1198 assert_eq!(
1199 encode_to_string(OvpnCommand::StateStream(StreamMode::Recent(5))),
1200 "state 5\n"
1201 );
1202 }
1203
1204 #[test]
1205 fn encode_password_escaping() {
1206 let wire = encode_to_string(OvpnCommand::Password {
1209 auth_type: AuthType::PrivateKey,
1210 value: r#"foo\"bar"#.into(),
1211 });
1212 assert_eq!(wire, "password \"Private Key\" \"foo\\\\\\\"bar\"\n");
1213 }
1214
1215 #[test]
1216 fn encode_password_simple() {
1217 let wire = encode_to_string(OvpnCommand::Password {
1218 auth_type: AuthType::Auth,
1219 value: "hunter2".into(),
1220 });
1221 assert_eq!(wire, "password \"Auth\" \"hunter2\"\n");
1222 }
1223
1224 #[test]
1225 fn encode_client_auth_with_config() {
1226 let wire = encode_to_string(OvpnCommand::ClientAuth {
1227 cid: 42,
1228 kid: 0,
1229 config_lines: vec![
1230 "push \"route 10.0.0.0 255.255.0.0\"".to_string(),
1231 "push \"dhcp-option DNS 10.0.0.1\"".to_string(),
1232 ],
1233 });
1234 assert_eq!(
1235 wire,
1236 "client-auth 42 0\n\
1237 push \"route 10.0.0.0 255.255.0.0\"\n\
1238 push \"dhcp-option DNS 10.0.0.1\"\n\
1239 END\n"
1240 );
1241 }
1242
1243 #[test]
1244 fn encode_client_auth_empty_config() {
1245 let wire = encode_to_string(OvpnCommand::ClientAuth {
1246 cid: 1,
1247 kid: 0,
1248 config_lines: vec![],
1249 });
1250 assert_eq!(wire, "client-auth 1 0\nEND\n");
1251 }
1252
1253 #[test]
1254 fn encode_client_deny_with_client_reason() {
1255 let wire = encode_to_string(OvpnCommand::ClientDeny {
1256 cid: 5,
1257 kid: 0,
1258 reason: "cert revoked".to_string(),
1259 client_reason: Some("Your access has been revoked.".to_string()),
1260 });
1261 assert_eq!(
1262 wire,
1263 "client-deny 5 0 \"cert revoked\" \"Your access has been revoked.\"\n"
1264 );
1265 }
1266
1267 #[test]
1268 fn encode_rsa_sig() {
1269 let wire = encode_to_string(OvpnCommand::RsaSig {
1270 base64_lines: vec!["AAAA".to_string(), "BBBB".to_string()],
1271 });
1272 assert_eq!(wire, "rsa-sig\nAAAA\nBBBB\nEND\n");
1273 }
1274
1275 #[test]
1276 fn encode_remote_modify() {
1277 let wire = encode_to_string(OvpnCommand::Remote(RemoteAction::Modify {
1278 host: "vpn.example.com".to_string(),
1279 port: 1234,
1280 }));
1281 assert_eq!(wire, "remote MOD vpn.example.com 1234\n");
1282 }
1283
1284 #[test]
1285 fn encode_proxy_http_nct() {
1286 let wire = encode_to_string(OvpnCommand::Proxy(ProxyAction::Http {
1287 host: "proxy.local".to_string(),
1288 port: 8080,
1289 non_cleartext_only: true,
1290 }));
1291 assert_eq!(wire, "proxy HTTP proxy.local 8080 nct\n");
1292 }
1293
1294 #[test]
1295 fn encode_needok() {
1296 use crate::need_ok::NeedOkResponse;
1297 let wire = encode_to_string(OvpnCommand::NeedOk {
1298 name: "token-insertion-request".to_string(),
1299 response: NeedOkResponse::Ok,
1300 });
1301 assert_eq!(wire, "needok token-insertion-request ok\n");
1302 }
1303
1304 #[test]
1305 fn encode_needstr() {
1306 let wire = encode_to_string(OvpnCommand::NeedStr {
1307 name: "name".to_string(),
1308 value: "John".to_string(),
1309 });
1310 assert_eq!(wire, "needstr name \"John\"\n");
1311 }
1312
1313 #[test]
1314 fn encode_forget_passwords() {
1315 assert_eq!(
1316 encode_to_string(OvpnCommand::ForgetPasswords),
1317 "forget-passwords\n"
1318 );
1319 }
1320
1321 #[test]
1322 fn encode_hold_query() {
1323 assert_eq!(encode_to_string(OvpnCommand::HoldQuery), "hold\n");
1324 }
1325
1326 #[test]
1327 fn encode_echo_on_all() {
1328 assert_eq!(
1329 encode_to_string(OvpnCommand::Echo(StreamMode::OnAll)),
1330 "echo on all\n"
1331 );
1332 }
1333
1334 #[test]
1337 fn decode_success() {
1338 let msgs = decode_all("SUCCESS: pid=12345\n");
1339 assert_eq!(msgs.len(), 1);
1340 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "pid=12345"));
1341 }
1342
1343 #[test]
1344 fn decode_success_bare() {
1345 let msgs = decode_all("SUCCESS:\n");
1347 assert_eq!(msgs.len(), 1);
1348 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s.is_empty()));
1349 }
1350
1351 #[test]
1352 fn decode_error() {
1353 let msgs = decode_all("ERROR: unknown command\n");
1354 assert_eq!(msgs.len(), 1);
1355 assert!(matches!(&msgs[0], OvpnMessage::Error(s) if s == "unknown command"));
1356 }
1357
1358 #[test]
1359 fn decode_info_notification() {
1360 let msgs = decode_all(">INFO:OpenVPN Management Interface Version 5\n");
1361 assert_eq!(msgs.len(), 1);
1362 assert!(matches!(
1363 &msgs[0],
1364 OvpnMessage::Info(s) if s == "OpenVPN Management Interface Version 5"
1365 ));
1366 }
1367
1368 #[test]
1369 fn decode_state_notification() {
1370 let msgs = decode_all(">STATE:1234567890,CONNECTED,SUCCESS,,10.0.0.1\n");
1371 assert_eq!(msgs.len(), 1);
1372 match &msgs[0] {
1373 OvpnMessage::Notification(Notification::State {
1374 timestamp,
1375 name,
1376 description,
1377 local_ip,
1378 remote_ip,
1379 ..
1380 }) => {
1381 assert_eq!(*timestamp, 1234567890);
1382 assert_eq!(*name, OpenVpnState::Connected);
1383 assert_eq!(description, "SUCCESS");
1384 assert_eq!(local_ip, "");
1385 assert_eq!(remote_ip, "10.0.0.1");
1386 }
1387 other => panic!("unexpected: {other:?}"),
1388 }
1389 }
1390
1391 #[test]
1392 fn decode_multiline_with_command_tracking() {
1393 let msgs = encode_then_decode(
1397 OvpnCommand::Status(StatusFormat::V1),
1398 "OpenVPN CLIENT LIST\nCommon Name,Real Address\ntest,1.2.3.4:1234\nEND\n",
1399 );
1400 assert_eq!(msgs.len(), 1);
1401 match &msgs[0] {
1402 OvpnMessage::MultiLine(lines) => {
1403 assert_eq!(lines.len(), 3);
1404 assert_eq!(lines[0], "OpenVPN CLIENT LIST");
1405 assert_eq!(lines[2], "test,1.2.3.4:1234");
1406 }
1407 other => panic!("unexpected: {other:?}"),
1408 }
1409 }
1410
1411 #[test]
1412 fn decode_hold_query_success() {
1413 let msgs = encode_then_decode(OvpnCommand::HoldQuery, "SUCCESS: hold=0\n");
1415 assert_eq!(msgs.len(), 1);
1416 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "hold=0"));
1417 }
1418
1419 #[test]
1420 fn decode_bare_state_multiline() {
1421 let msgs = encode_then_decode(
1423 OvpnCommand::State,
1424 "1234567890,CONNECTED,SUCCESS,,10.0.0.1,,,,\nEND\n",
1425 );
1426 assert_eq!(msgs.len(), 1);
1427 match &msgs[0] {
1428 OvpnMessage::MultiLine(lines) => {
1429 assert_eq!(lines.len(), 1);
1430 assert!(lines[0].starts_with("1234567890"));
1431 }
1432 other => panic!("unexpected: {other:?}"),
1433 }
1434 }
1435
1436 #[test]
1437 fn decode_notification_during_multiline() {
1438 let msgs = encode_then_decode(
1441 OvpnCommand::Status(StatusFormat::V1),
1442 "header line\n>BYTECOUNT:1000,2000\ndata line\nEND\n",
1443 );
1444 assert_eq!(msgs.len(), 2);
1445 assert!(matches!(
1447 &msgs[0],
1448 OvpnMessage::Notification(Notification::ByteCount {
1449 bytes_in: 1000,
1450 bytes_out: 2000
1451 })
1452 ));
1453 match &msgs[1] {
1455 OvpnMessage::MultiLine(lines) => {
1456 assert_eq!(lines, &["header line", "data line"]);
1457 }
1458 other => panic!("unexpected: {other:?}"),
1459 }
1460 }
1461
1462 #[test]
1463 fn decode_client_connect_multiline_notification() {
1464 let input = "\
1465 >CLIENT:CONNECT,0,1\n\
1466 >CLIENT:ENV,untrusted_ip=1.2.3.4\n\
1467 >CLIENT:ENV,common_name=TestClient\n\
1468 >CLIENT:ENV,END\n";
1469 let msgs = decode_all(input);
1470 assert_eq!(msgs.len(), 1);
1471 match &msgs[0] {
1472 OvpnMessage::Notification(Notification::Client {
1473 event,
1474 cid,
1475 kid,
1476 env,
1477 }) => {
1478 assert_eq!(*event, ClientEvent::Connect);
1479 assert_eq!(*cid, 0);
1480 assert_eq!(*kid, Some(1));
1481 assert_eq!(env.len(), 2);
1482 assert_eq!(env[0], ("untrusted_ip".to_string(), "1.2.3.4".to_string()));
1483 assert_eq!(
1484 env[1],
1485 ("common_name".to_string(), "TestClient".to_string())
1486 );
1487 }
1488 other => panic!("unexpected: {other:?}"),
1489 }
1490 }
1491
1492 #[test]
1493 fn decode_client_address_single_line() {
1494 let msgs = decode_all(">CLIENT:ADDRESS,3,10.0.0.5,1\n");
1495 assert_eq!(msgs.len(), 1);
1496 match &msgs[0] {
1497 OvpnMessage::Notification(Notification::ClientAddress { cid, addr, primary }) => {
1498 assert_eq!(*cid, 3);
1499 assert_eq!(addr, "10.0.0.5");
1500 assert!(*primary);
1501 }
1502 other => panic!("unexpected: {other:?}"),
1503 }
1504 }
1505
1506 #[test]
1507 fn decode_client_disconnect() {
1508 let input = "\
1509 >CLIENT:DISCONNECT,5\n\
1510 >CLIENT:ENV,bytes_received=12345\n\
1511 >CLIENT:ENV,bytes_sent=67890\n\
1512 >CLIENT:ENV,END\n";
1513 let msgs = decode_all(input);
1514 assert_eq!(msgs.len(), 1);
1515 match &msgs[0] {
1516 OvpnMessage::Notification(Notification::Client {
1517 event,
1518 cid,
1519 kid,
1520 env,
1521 }) => {
1522 assert_eq!(*event, ClientEvent::Disconnect);
1523 assert_eq!(*cid, 5);
1524 assert_eq!(*kid, None);
1525 assert_eq!(env.len(), 2);
1526 }
1527 other => panic!("unexpected: {other:?}"),
1528 }
1529 }
1530
1531 #[test]
1532 fn decode_password_notification() {
1533 let msgs = decode_all(">PASSWORD:Need 'Auth' username/password\n");
1534 assert_eq!(msgs.len(), 1);
1535 match &msgs[0] {
1536 OvpnMessage::Notification(Notification::Password(PasswordNotification::NeedAuth {
1537 auth_type,
1538 })) => {
1539 assert_eq!(*auth_type, AuthType::Auth);
1540 }
1541 other => panic!("unexpected: {other:?}"),
1542 }
1543 }
1544
1545 #[test]
1546 fn quote_and_escape_special_chars() {
1547 assert_eq!(quote_and_escape(r#"foo"bar"#), r#""foo\"bar""#);
1548 assert_eq!(quote_and_escape(r"a\b"), r#""a\\b""#);
1549 assert_eq!(quote_and_escape("simple"), r#""simple""#);
1550 }
1551
1552 #[test]
1553 fn decode_empty_multiline() {
1554 let msgs = encode_then_decode(OvpnCommand::Status(StatusFormat::V1), "END\n");
1556 assert_eq!(msgs.len(), 1);
1557 assert!(matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines.is_empty()));
1558 }
1559
1560 #[test]
1561 fn decode_need_ok_notification() {
1562 let msgs = decode_all(
1563 ">NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your token\n",
1564 );
1565 assert_eq!(msgs.len(), 1);
1566 match &msgs[0] {
1567 OvpnMessage::Notification(Notification::NeedOk { name, message }) => {
1568 assert_eq!(name, "token-insertion-request");
1569 assert_eq!(message, "Please insert your token");
1570 }
1571 other => panic!("unexpected: {other:?}"),
1572 }
1573 }
1574
1575 #[test]
1576 fn decode_hold_notification() {
1577 let msgs = decode_all(">HOLD:Waiting for hold release\n");
1578 assert_eq!(msgs.len(), 1);
1579 match &msgs[0] {
1580 OvpnMessage::Notification(Notification::Hold { text }) => {
1581 assert_eq!(text, "Waiting for hold release");
1582 }
1583 other => panic!("unexpected: {other:?}"),
1584 }
1585 }
1586
1587 #[test]
1590 fn encode_raw_multiline() {
1591 assert_eq!(
1592 encode_to_string(OvpnCommand::RawMultiLine("custom-cmd arg".to_string())),
1593 "custom-cmd arg\n"
1594 );
1595 }
1596
1597 #[test]
1598 fn raw_multiline_expects_multiline_response() {
1599 let msgs = encode_then_decode(
1600 OvpnCommand::RawMultiLine("custom".to_string()),
1601 "line1\nline2\nEND\n",
1602 );
1603 assert_eq!(msgs.len(), 1);
1604 match &msgs[0] {
1605 OvpnMessage::MultiLine(lines) => {
1606 assert_eq!(lines, &["line1", "line2"]);
1607 }
1608 other => panic!("expected MultiLine, got: {other:?}"),
1609 }
1610 }
1611
1612 #[test]
1613 fn raw_multiline_sanitizes_newlines() {
1614 let wire = encode_to_string(OvpnCommand::RawMultiLine("cmd\ninjected".to_string()));
1616 assert_eq!(wire, "cmdinjected\n");
1617 }
1618
1619 #[test]
1620 fn raw_multiline_strict_rejects_newlines() {
1621 let mut codec = OvpnCodec::new().with_encoder_mode(EncoderMode::Strict);
1622 let mut buf = BytesMut::new();
1623 let result = codec.encode(
1624 OvpnCommand::RawMultiLine("cmd\ninjected".to_string()),
1625 &mut buf,
1626 );
1627 assert!(result.is_err());
1628 }
1629
1630 #[test]
1633 #[should_panic(expected = "mid-accumulation")]
1634 fn encode_during_multiline_accumulation_panics() {
1635 let mut codec = OvpnCodec::new();
1636 let mut buf = BytesMut::new();
1637 codec
1639 .encode(OvpnCommand::Status(StatusFormat::V1), &mut buf)
1640 .unwrap();
1641 let mut dec = BytesMut::from("header line\n");
1643 let _ = codec.decode(&mut dec); codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1646 }
1647
1648 #[test]
1649 #[should_panic(expected = "mid-accumulation")]
1650 fn encode_during_client_notif_accumulation_panics() {
1651 let mut codec = OvpnCodec::new();
1652 let mut buf = BytesMut::new();
1653 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1655 let _ = codec.decode(&mut dec);
1656 codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1658 }
1659
1660 #[test]
1663 fn unlimited_accumulation_default() {
1664 let mut codec = OvpnCodec::new();
1665 let mut enc = BytesMut::new();
1666 codec
1667 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1668 .unwrap();
1669 let mut data = String::new();
1671 for i in 0..500 {
1672 data.push_str(&format!("line {i}\n"));
1673 }
1674 data.push_str("END\n");
1675 let mut dec = BytesMut::from(data.as_str());
1676 let mut msgs = Vec::new();
1677 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1678 msgs.push(msg);
1679 }
1680 assert_eq!(msgs.len(), 1);
1681 match &msgs[0] {
1682 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 500),
1683 other => panic!("expected MultiLine, got: {other:?}"),
1684 }
1685 }
1686
1687 #[test]
1688 fn multi_line_limit_exceeded() {
1689 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1690 let mut enc = BytesMut::new();
1691 codec
1692 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1693 .unwrap();
1694 let mut dec = BytesMut::from("a\nb\nc\nd\nEND\n");
1695 let result = loop {
1696 match codec.decode(&mut dec) {
1697 Ok(Some(msg)) => break Ok(msg),
1698 Ok(None) => continue,
1699 Err(e) => break Err(e),
1700 }
1701 };
1702 assert!(result.is_err(), "expected error when limit exceeded");
1703 let err = result.unwrap_err();
1704 assert!(
1705 err.to_string().contains("multi-line response"),
1706 "error should mention multi-line: {err}"
1707 );
1708 }
1709
1710 #[test]
1711 fn multi_line_limit_exact_boundary_passes() {
1712 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1713 let mut enc = BytesMut::new();
1714 codec
1715 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1716 .unwrap();
1717 let mut dec = BytesMut::from("a\nb\nc\nEND\n");
1719 let mut msgs = Vec::new();
1720 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1721 msgs.push(msg);
1722 }
1723 assert_eq!(msgs.len(), 1);
1724 match &msgs[0] {
1725 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 3),
1726 other => panic!("expected MultiLine, got: {other:?}"),
1727 }
1728 }
1729
1730 #[test]
1731 fn client_env_limit_exceeded() {
1732 let mut codec = OvpnCodec::new().with_max_client_env_entries(AccumulationLimit::Max(2));
1733 let mut dec = BytesMut::from(
1734 ">CLIENT:CONNECT,0,1\n\
1735 >CLIENT:ENV,a=1\n\
1736 >CLIENT:ENV,b=2\n\
1737 >CLIENT:ENV,c=3\n\
1738 >CLIENT:ENV,END\n",
1739 );
1740 let result = loop {
1741 match codec.decode(&mut dec) {
1742 Ok(Some(msg)) => break Ok(msg),
1743 Ok(None) => continue,
1744 Err(e) => break Err(e),
1745 }
1746 };
1747 assert!(
1748 result.is_err(),
1749 "expected error when client ENV limit exceeded"
1750 );
1751 let err = result.unwrap_err();
1752 assert!(
1753 err.to_string().contains("client ENV"),
1754 "error should mention client ENV: {err}"
1755 );
1756 }
1757
1758 #[test]
1761 fn utf8_error_resets_multiline_state() {
1762 let mut codec = OvpnCodec::new();
1763 let mut enc = BytesMut::new();
1764 codec
1765 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1766 .unwrap();
1767 let mut dec = BytesMut::from("header\n");
1769 assert!(codec.decode(&mut dec).unwrap().is_none());
1770 dec.extend_from_slice(b"bad \xff line\n");
1772 assert!(codec.decode(&mut dec).is_err());
1773 dec.extend_from_slice(b"SUCCESS: recovered\n");
1776 let msg = codec.decode(&mut dec).unwrap();
1777 match msg {
1778 Some(OvpnMessage::Success(ref s)) if s.contains("recovered") => {}
1779 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1780 }
1781 }
1782
1783 #[test]
1784 fn utf8_error_resets_client_notif_state() {
1785 let mut codec = OvpnCodec::new();
1786 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1788 assert!(codec.decode(&mut dec).unwrap().is_none());
1789 dec.extend_from_slice(b">CLIENT:ENV,\xff\n");
1791 assert!(codec.decode(&mut dec).is_err());
1792 dec.extend_from_slice(b"SUCCESS: ok\n");
1794 let msg = codec.decode(&mut dec).unwrap();
1795 match msg {
1796 Some(OvpnMessage::Success(_)) => {}
1797 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1798 }
1799 }
1800}