1use bytes::{BufMut, BytesMut};
2use std::io;
3use tokio_util::codec::{Decoder, Encoder};
4
5use crate::command::{OvpnCommand, ResponseKind};
6use crate::kill_target::KillTarget;
7use crate::message::{Notification, OvpnMessage};
8use crate::proxy_action::ProxyAction;
9use crate::remote_action::RemoteAction;
10use crate::status_format::StatusFormat;
11use crate::unrecognized::UnrecognizedKind;
12
13fn quote_and_escape(s: &str) -> String {
22 let mut out = String::with_capacity(s.len() + 2);
23 out.push('"');
24 for c in s.chars() {
25 match c {
26 '\\' => out.push_str("\\\\"),
27 '"' => out.push_str("\\\""),
28 '\n' | '\r' | '\0' => {} _ => out.push(c),
30 }
31 }
32 out.push('"');
33 out
34}
35
36fn sanitize_line(s: &str) -> String {
39 if s.contains('\n') || s.contains('\r') || s.contains('\0') {
40 s.chars()
41 .filter(|&c| c != '\n' && c != '\r' && c != '\0')
42 .collect()
43 } else {
44 s.to_string()
45 }
46}
47
48use crate::client_event::ClientEvent;
49use crate::log_level::LogLevel;
50use crate::openvpn_state::OpenVpnState;
51use crate::transport_protocol::TransportProtocol;
52
53#[derive(Debug)]
55struct ClientNotifAccum {
56 event: ClientEvent,
57 cid: u64,
58 kid: Option<u64>,
59 env: Vec<(String, String)>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum AccumulationLimit {
66 Unlimited,
68 Max(usize),
70}
71
72pub struct OvpnCodec {
94 expected: ResponseKind,
100
101 multi_line_buf: Option<Vec<String>>,
103
104 client_notif: Option<ClientNotifAccum>,
107
108 max_multi_line_lines: AccumulationLimit,
110
111 max_client_env_entries: AccumulationLimit,
113}
114
115impl OvpnCodec {
116 pub fn new() -> Self {
119 Self {
120 expected: ResponseKind::SuccessOrError,
125 multi_line_buf: None,
126 client_notif: None,
127 max_multi_line_lines: AccumulationLimit::Unlimited,
128 max_client_env_entries: AccumulationLimit::Unlimited,
129 }
130 }
131
132 pub fn with_max_multi_line_lines(mut self, limit: AccumulationLimit) -> Self {
135 self.max_multi_line_lines = limit;
136 self
137 }
138
139 pub fn with_max_client_env_entries(mut self, limit: AccumulationLimit) -> Self {
142 self.max_client_env_entries = limit;
143 self
144 }
145}
146
147fn check_accumulation_limit(
148 current_len: usize,
149 limit: AccumulationLimit,
150 what: &str,
151) -> Result<(), io::Error> {
152 if let AccumulationLimit::Max(max) = limit
153 && current_len >= max
154 {
155 return Err(io::Error::other(format!(
156 "{what} accumulation limit exceeded ({max})"
157 )));
158 }
159 Ok(())
160}
161
162impl Default for OvpnCodec {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168impl Encoder<OvpnCommand> for OvpnCodec {
171 type Error = io::Error;
172
173 fn encode(&mut self, item: OvpnCommand, dst: &mut BytesMut) -> Result<(), Self::Error> {
174 debug_assert!(
175 self.multi_line_buf.is_none() && self.client_notif.is_none(),
176 "encode() called while the decoder is mid-accumulation \
177 (multi_line_buf or client_notif is active). \
178 Drain decode() before sending a new command."
179 );
180
181 self.expected = item.expected_response();
184
185 match item {
186 OvpnCommand::Status(StatusFormat::V1) => write_line(dst, "status"),
188 OvpnCommand::Status(ref fmt) => write_line(dst, &format!("status {fmt}")),
189 OvpnCommand::State => write_line(dst, "state"),
190 OvpnCommand::StateStream(ref m) => write_line(dst, &format!("state {m}")),
191 OvpnCommand::Version => write_line(dst, "version"),
192 OvpnCommand::Pid => write_line(dst, "pid"),
193 OvpnCommand::Help => write_line(dst, "help"),
194 OvpnCommand::Net => write_line(dst, "net"),
195 OvpnCommand::Verb(Some(n)) => write_line(dst, &format!("verb {n}")),
196 OvpnCommand::Verb(None) => write_line(dst, "verb"),
197 OvpnCommand::Mute(Some(n)) => write_line(dst, &format!("mute {n}")),
198 OvpnCommand::Mute(None) => write_line(dst, "mute"),
199
200 OvpnCommand::Log(ref m) => write_line(dst, &format!("log {m}")),
202 OvpnCommand::Echo(ref m) => write_line(dst, &format!("echo {m}")),
203 OvpnCommand::ByteCount(n) => write_line(dst, &format!("bytecount {n}")),
204
205 OvpnCommand::Signal(sig) => write_line(dst, &format!("signal {sig}")),
207 OvpnCommand::Kill(KillTarget::CommonName(ref cn)) => {
208 write_line(dst, &format!("kill {}", sanitize_line(cn)));
209 }
210 OvpnCommand::Kill(KillTarget::Address { ref ip, port }) => {
211 write_line(dst, &format!("kill {}:{port}", sanitize_line(ip)));
212 }
213 OvpnCommand::HoldQuery => write_line(dst, "hold"),
214 OvpnCommand::HoldOn => write_line(dst, "hold on"),
215 OvpnCommand::HoldOff => write_line(dst, "hold off"),
216 OvpnCommand::HoldRelease => write_line(dst, "hold release"),
217
218 OvpnCommand::Username {
223 ref auth_type,
224 ref value,
225 } => {
226 let at = quote_and_escape(&auth_type.to_string());
230 let val = quote_and_escape(value);
231 write_line(dst, &format!("username {at} {val}"));
232 }
233 OvpnCommand::Password {
234 ref auth_type,
235 ref value,
236 } => {
237 let at = quote_and_escape(&auth_type.to_string());
238 let val = quote_and_escape(value);
239 write_line(dst, &format!("password {at} {val}"));
240 }
241 OvpnCommand::AuthRetry(mode) => write_line(dst, &format!("auth-retry {mode}")),
242 OvpnCommand::ForgetPasswords => write_line(dst, "forget-passwords"),
243
244 OvpnCommand::ChallengeResponse {
246 ref state_id,
247 ref response,
248 } => {
249 let value = format!("CRV1::{state_id}::{response}");
250 let escaped = quote_and_escape(&value);
251 write_line(dst, &format!("password \"Auth\" {escaped}"));
252 }
253 OvpnCommand::StaticChallengeResponse {
254 ref password_b64,
255 ref response_b64,
256 } => {
257 let value = format!("SCRV1:{password_b64}:{response_b64}");
258 let escaped = quote_and_escape(&value);
259 write_line(dst, &format!("password \"Auth\" {escaped}"));
260 }
261
262 OvpnCommand::NeedOk { ref name, response } => {
264 write_line(dst, &format!("needok {} {response}", sanitize_line(name)));
265 }
266 OvpnCommand::NeedStr {
267 ref name,
268 ref value,
269 } => {
270 let escaped = quote_and_escape(value);
271 write_line(dst, &format!("needstr {} {escaped}", sanitize_line(name)));
272 }
273
274 OvpnCommand::Pkcs11IdCount => write_line(dst, "pkcs11-id-count"),
276 OvpnCommand::Pkcs11IdGet(idx) => write_line(dst, &format!("pkcs11-id-get {idx}")),
277
278 OvpnCommand::RsaSig { ref base64_lines } => write_block(dst, "rsa-sig", base64_lines),
286
287 OvpnCommand::ClientAuth {
295 cid,
296 kid,
297 ref config_lines,
298 } => write_block(dst, &format!("client-auth {cid} {kid}"), config_lines),
299
300 OvpnCommand::ClientAuthNt { cid, kid } => {
301 write_line(dst, &format!("client-auth-nt {cid} {kid}"));
302 }
303
304 OvpnCommand::ClientDeny {
305 cid,
306 kid,
307 ref reason,
308 ref client_reason,
309 } => {
310 let r = quote_and_escape(reason);
311 match client_reason {
312 Some(cr) => {
313 let cr_esc = quote_and_escape(cr);
314 write_line(dst, &format!("client-deny {cid} {kid} {r} {cr_esc}"));
315 }
316 None => write_line(dst, &format!("client-deny {cid} {kid} {r}")),
317 }
318 }
319
320 OvpnCommand::ClientKill { cid } => write_line(dst, &format!("client-kill {cid}")),
321
322 OvpnCommand::ClientPf {
329 cid,
330 ref filter_lines,
331 } => write_block(dst, &format!("client-pf {cid}"), filter_lines),
332
333 OvpnCommand::LoadStats => write_line(dst, "load-stats"),
335
336 OvpnCommand::ClientPendingAuth {
338 cid,
339 kid,
340 timeout,
341 ref extra,
342 } => write_line(
343 dst,
344 &format!(
345 "client-pending-auth {cid} {kid} {timeout} {}",
346 sanitize_line(extra)
347 ),
348 ),
349
350 OvpnCommand::ClientDenyV2 {
351 cid,
352 kid,
353 ref reason,
354 ref client_reason,
355 ref redirect_url,
356 } => {
357 let r = quote_and_escape(reason);
358 let mut cmd = format!("client-deny-v2 {cid} {kid} {r}");
359 if let Some(cr) = client_reason {
360 cmd.push(' ');
361 cmd.push_str("e_and_escape(cr));
362 if let Some(url) = redirect_url {
363 cmd.push(' ');
364 cmd.push_str("e_and_escape(url));
365 }
366 }
367 write_line(dst, &cmd);
368 }
369
370 OvpnCommand::CrResponse {
371 cid,
372 kid,
373 ref response,
374 } => write_line(
375 dst,
376 &format!("cr-response {cid} {kid} {}", sanitize_line(response)),
377 ),
378
379 OvpnCommand::Certificate { ref pem_lines } => {
381 write_block(dst, "certificate", pem_lines);
382 }
383
384 OvpnCommand::BypassMessage(ref msg) => {
386 let escaped = quote_and_escape(msg);
387 write_line(dst, &format!("bypass-message {escaped}"));
388 }
389
390 OvpnCommand::Remote(RemoteAction::Accept) => write_line(dst, "remote ACCEPT"),
392 OvpnCommand::Remote(RemoteAction::Skip) => write_line(dst, "remote SKIP"),
393 OvpnCommand::Remote(RemoteAction::Modify { ref host, port }) => {
394 write_line(dst, &format!("remote MOD {} {port}", sanitize_line(host)));
395 }
396 OvpnCommand::Proxy(ProxyAction::None) => write_line(dst, "proxy NONE"),
397 OvpnCommand::Proxy(ProxyAction::Http {
398 ref host,
399 port,
400 non_cleartext_only,
401 }) => {
402 let nct = if non_cleartext_only { " nct" } else { "" };
403 write_line(
404 dst,
405 &format!("proxy HTTP {} {port}{nct}", sanitize_line(host)),
406 );
407 }
408 OvpnCommand::Proxy(ProxyAction::Socks { ref host, port }) => {
409 write_line(dst, &format!("proxy SOCKS {} {port}", sanitize_line(host)));
410 }
411
412 OvpnCommand::ManagementPassword(ref pw) => {
416 write_line(dst, &sanitize_line(pw));
417 }
418
419 OvpnCommand::Exit => write_line(dst, "exit"),
421 OvpnCommand::Quit => write_line(dst, "quit"),
422
423 OvpnCommand::Raw(ref cmd) | OvpnCommand::RawMultiLine(ref cmd) => {
425 write_line(dst, &sanitize_line(cmd));
426 }
427 }
428
429 Ok(())
430 }
431}
432
433fn write_line(dst: &mut BytesMut, s: &str) {
435 dst.reserve(s.len() + 1);
436 dst.put_slice(s.as_bytes());
437 dst.put_u8(b'\n');
438}
439
440fn write_block(dst: &mut BytesMut, header: &str, lines: &[String]) {
446 let total: usize = header.len() + 1 + lines.iter().map(|l| l.len() + 2).sum::<usize>() + 4;
447 dst.reserve(total);
448 dst.put_slice(header.as_bytes());
449 dst.put_u8(b'\n');
450 for line in lines {
451 let needs_sanitize = line.contains('\n') || line.contains('\r') || line.contains('\0');
452 if !needs_sanitize && line != "END" {
453 dst.put_slice(line.as_bytes());
454 } else {
455 let sanitized: String = if needs_sanitize {
456 line.chars()
457 .filter(|&c| c != '\n' && c != '\r' && c != '\0')
458 .collect()
459 } else {
460 line.to_string()
461 };
462 if sanitized == "END" {
463 dst.put_slice(b" END");
464 } else {
465 dst.put_slice(sanitized.as_bytes());
466 }
467 }
468 dst.put_u8(b'\n');
469 }
470 dst.put_slice(b"END\n");
471}
472
473impl Decoder for OvpnCodec {
476 type Item = OvpnMessage;
477 type Error = io::Error;
478
479 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
480 loop {
481 let Some(newline_pos) = src.iter().position(|&b| b == b'\n') else {
483 return Ok(None); };
485
486 let line_bytes = src.split_to(newline_pos + 1);
488 let line = match std::str::from_utf8(&line_bytes) {
489 Ok(s) => s,
490 Err(e) => {
491 self.multi_line_buf = None;
494 self.client_notif = None;
495 self.expected = ResponseKind::SuccessOrError;
496 return Err(io::Error::new(io::ErrorKind::InvalidData, e));
497 }
498 }
499 .trim_end_matches(['\r', '\n'])
500 .to_string();
501
502 if let Some(ref mut accum) = self.client_notif
511 && let Some(rest) = line.strip_prefix(">CLIENT:ENV,")
512 {
513 if rest == "END" {
514 let finished = self.client_notif.take().expect("guarded by if-let");
515 return Ok(Some(OvpnMessage::Notification(Notification::Client {
516 event: finished.event,
517 cid: finished.cid,
518 kid: finished.kid,
519 env: finished.env,
520 })));
521 } else {
522 let (k, v) = rest
524 .split_once('=')
525 .map(|(k, v)| (k.to_string(), v.to_string()))
526 .unwrap_or_else(|| (rest.to_string(), String::new()));
527 check_accumulation_limit(
528 accum.env.len(),
529 self.max_client_env_entries,
530 "client ENV",
531 )?;
532 accum.env.push((k, v));
533 continue; }
535 }
536 if let Some(ref mut buf) = self.multi_line_buf {
541 if line == "END" {
542 let lines = self.multi_line_buf.take().expect("guarded by if-let");
543 return Ok(Some(OvpnMessage::MultiLine(lines)));
544 }
545 if line.starts_with('>') {
550 if let Some(msg) = self.parse_notification(&line) {
551 return Ok(Some(msg));
552 }
553 continue;
556 }
557 check_accumulation_limit(
558 buf.len(),
559 self.max_multi_line_lines,
560 "multi-line response",
561 )?;
562 buf.push(line);
563 continue; }
565
566 if let Some(rest) = line.strip_prefix("SUCCESS:") {
572 return Ok(Some(OvpnMessage::Success(
573 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
574 )));
575 }
576 if let Some(rest) = line.strip_prefix("ERROR:") {
577 return Ok(Some(OvpnMessage::Error(
578 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
579 )));
580 }
581
582 if line == "ENTER PASSWORD:" {
584 return Ok(Some(OvpnMessage::PasswordPrompt));
585 }
586
587 if line.starts_with('>') {
589 if let Some(msg) = self.parse_notification(&line) {
590 return Ok(Some(msg));
591 }
592 continue;
594 }
595
596 match self.expected {
602 ResponseKind::MultiLine => {
603 if line == "END" {
604 return Ok(Some(OvpnMessage::MultiLine(Vec::new())));
606 }
607 self.multi_line_buf = Some(vec![line]);
608 continue; }
610 ResponseKind::SingleValue => {
611 if let Some(parsed) = parse_pkcs11id_entry(&line) {
612 return Ok(Some(parsed));
613 }
614 return Ok(Some(OvpnMessage::SingleValue(line)));
615 }
616 ResponseKind::SuccessOrError | ResponseKind::NoResponse => {
617 return Ok(Some(OvpnMessage::Unrecognized {
618 line,
619 kind: UnrecognizedKind::UnexpectedLine,
620 }));
621 }
622 }
623 }
624 }
625}
626
627impl OvpnCodec {
628 fn parse_notification(&mut self, line: &str) -> Option<OvpnMessage> {
632 let inner = &line[1..]; let Some((kind, payload)) = inner.split_once(':') else {
635 return Some(OvpnMessage::Unrecognized {
637 line: line.to_string(),
638 kind: UnrecognizedKind::MalformedNotification,
639 });
640 };
641
642 if kind == "INFO" {
645 return Some(OvpnMessage::Info(payload.to_string()));
646 }
647
648 if kind == "CLIENT" {
650 let (event, args) = payload
651 .split_once(',')
652 .map(|(e, a)| (e.to_string(), a.to_string()))
653 .unwrap_or_else(|| (payload.to_string(), String::new()));
654
655 if event == "ADDRESS" {
657 let mut parts = args.splitn(3, ',');
658 let cid = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
659 let addr = parts.next().unwrap_or("").to_string();
660 let primary = parts.next() == Some("1");
661 return Some(OvpnMessage::Notification(Notification::ClientAddress {
662 cid,
663 addr,
664 primary,
665 }));
666 }
667
668 let mut id_parts = args.splitn(3, ',');
673 let cid = id_parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
674 let kid = id_parts.next().and_then(|s| s.parse().ok());
675
676 self.client_notif = Some(ClientNotifAccum {
678 event: ClientEvent::parse(&event),
679 cid,
680 kid,
681 env: Vec::new(),
682 });
683 return None; }
685
686 let notification = match kind {
688 "STATE" => parse_state(payload),
689 "BYTECOUNT" => parse_bytecount(payload),
690 "BYTECOUNT_CLI" => parse_bytecount_cli(payload),
691 "LOG" => parse_log(payload),
692 "ECHO" => parse_echo(payload),
693 "HOLD" => Some(Notification::Hold {
694 text: payload.to_string(),
695 }),
696 "FATAL" => Some(Notification::Fatal {
697 message: payload.to_string(),
698 }),
699 "PKCS11ID-COUNT" => parse_pkcs11id_count(payload),
700 "NEED-OK" => parse_need_ok(payload),
701 "NEED-STR" => parse_need_str(payload),
702 "RSA_SIGN" => Some(Notification::RsaSign {
703 data: payload.to_string(),
704 }),
705 "REMOTE" => parse_remote(payload),
706 "PROXY" => parse_proxy(payload),
707 "PASSWORD" => parse_password(payload),
708 _ => None,
709 };
710
711 Some(OvpnMessage::Notification(notification.unwrap_or(
712 Notification::Simple {
713 kind: kind.to_string(),
714 payload: payload.to_string(),
715 },
716 )))
717 }
718}
719
720fn parse_state(payload: &str) -> Option<Notification> {
728 let mut parts = payload.splitn(9, ',');
733 let timestamp = parts.next()?.parse().ok()?;
734 let name = OpenVpnState::parse(parts.next()?);
735 let description = parts.next()?.to_string();
736 let local_ip = parts.next()?.to_string();
737 let remote_ip = parts.next()?.to_string();
738 let local_port = parts.next().unwrap_or("").to_string();
739 let _local_addr = parts.next(); let remote_port = parts.next().unwrap_or("").to_string();
741 Some(Notification::State {
742 timestamp,
743 name,
744 description,
745 local_ip,
746 remote_ip,
747 local_port,
748 remote_port,
749 })
750}
751
752fn parse_bytecount(payload: &str) -> Option<Notification> {
753 let (a, b) = payload.split_once(',')?;
754 Some(Notification::ByteCount {
755 bytes_in: a.parse().ok()?,
756 bytes_out: b.parse().ok()?,
757 })
758}
759
760fn parse_bytecount_cli(payload: &str) -> Option<Notification> {
761 let mut parts = payload.splitn(3, ',');
762 let cid = parts.next()?.parse().ok()?;
763 let bytes_in = parts.next()?.parse().ok()?;
764 let bytes_out = parts.next()?.parse().ok()?;
765 Some(Notification::ByteCountCli {
766 cid,
767 bytes_in,
768 bytes_out,
769 })
770}
771
772fn parse_log(payload: &str) -> Option<Notification> {
773 let (ts_str, rest) = payload.split_once(',')?;
774 let timestamp = ts_str.parse().ok()?;
775 let (level_str, message) = rest.split_once(',')?;
776 Some(Notification::Log {
777 timestamp,
778 level: LogLevel::parse(level_str),
779 message: message.to_string(),
780 })
781}
782
783fn parse_echo(payload: &str) -> Option<Notification> {
784 let (ts_str, param) = payload.split_once(',')?;
785 let timestamp = ts_str.parse().ok()?;
786 Some(Notification::Echo {
787 timestamp,
788 param: param.to_string(),
789 })
790}
791
792fn parse_pkcs11id_count(payload: &str) -> Option<Notification> {
793 let count = payload.trim().parse().ok()?;
794 Some(Notification::Pkcs11IdCount { count })
795}
796
797fn parse_pkcs11id_entry(line: &str) -> Option<OvpnMessage> {
799 let rest = line.strip_prefix("PKCS11ID-ENTRY:'")?;
800 let (index, rest) = rest.split_once("', ID:'")?;
801 let (id, rest) = rest.split_once("', BLOB:'")?;
802 let blob = rest.strip_suffix('\'')?;
803 Some(OvpnMessage::Pkcs11IdEntry {
804 index: index.to_string(),
805 id: id.to_string(),
806 blob: blob.to_string(),
807 })
808}
809
810fn parse_need_ok(payload: &str) -> Option<Notification> {
812 let rest = payload.strip_prefix("Need '")?;
814 let (name, rest) = rest.split_once('\'')?;
815 let msg = rest.split_once("MSG:")?.1;
816 Some(Notification::NeedOk {
817 name: name.to_string(),
818 message: msg.to_string(),
819 })
820}
821
822fn parse_need_str(payload: &str) -> Option<Notification> {
824 let rest = payload.strip_prefix("Need '")?;
825 let (name, rest) = rest.split_once('\'')?;
826 let msg = rest.split_once("MSG:")?.1;
827 Some(Notification::NeedStr {
828 name: name.to_string(),
829 message: msg.to_string(),
830 })
831}
832
833fn parse_remote(payload: &str) -> Option<Notification> {
834 let mut parts = payload.splitn(3, ',');
835 let host = parts.next()?.to_string();
836 let port = parts.next()?.parse().ok()?;
837 let protocol = TransportProtocol::parse(parts.next()?);
838 Some(Notification::Remote {
839 host,
840 port,
841 protocol,
842 })
843}
844
845fn parse_proxy(payload: &str) -> Option<Notification> {
846 let mut parts = payload.splitn(4, ',');
847 let proto_num = parts.next()?.parse().ok()?;
848 let proto_type = TransportProtocol::parse(parts.next()?);
849 let host = parts.next()?.to_string();
850 let port = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
851 Some(Notification::Proxy {
852 proto_num,
853 proto_type,
854 host,
855 port,
856 })
857}
858
859use crate::message::PasswordNotification;
860
861use crate::auth::AuthType;
862
863fn parse_auth_type(s: &str) -> AuthType {
865 match s {
866 "Auth" => AuthType::Auth,
867 "Private Key" => AuthType::PrivateKey,
868 "HTTP Proxy" => AuthType::HttpProxy,
869 "SOCKS Proxy" => AuthType::SocksProxy,
870 other => AuthType::Custom(other.to_string()),
871 }
872}
873
874fn parse_password(payload: &str) -> Option<Notification> {
875 if let Some(rest) = payload.strip_prefix("Verification Failed: '") {
877 let auth_type = rest.strip_suffix('\'')?;
878 return Some(Notification::Password(
879 PasswordNotification::VerificationFailed {
880 auth_type: parse_auth_type(auth_type),
881 },
882 ));
883 }
884
885 let rest = payload.strip_prefix("Need '")?;
888 let (auth_type_str, rest) = rest.split_once('\'')?;
889 let rest = rest.trim_start();
890
891 if let Some(after_up) = rest.strip_prefix("username/password") {
893 let after_up = after_up.trim_start();
894
895 if let Some(sc) = after_up.strip_prefix("SC:") {
897 let (echo_str, challenge) = sc.split_once(',')?;
898 return Some(Notification::Password(
899 PasswordNotification::StaticChallenge {
900 echo: echo_str == "1",
901 challenge: challenge.to_string(),
902 },
903 ));
904 }
905
906 if let Some(crv1) = after_up.strip_prefix("CRV1:") {
908 let mut parts = crv1.splitn(4, ':');
909 let flags = parts.next()?.to_string();
910 let state_id = parts.next()?.to_string();
911 let username_b64 = parts.next()?.to_string();
912 let challenge = parts.next()?.to_string();
913 return Some(Notification::Password(
914 PasswordNotification::DynamicChallenge {
915 flags,
916 state_id,
917 username_b64,
918 challenge,
919 },
920 ));
921 }
922
923 return Some(Notification::Password(PasswordNotification::NeedAuth {
925 auth_type: parse_auth_type(auth_type_str),
926 }));
927 }
928
929 if rest.starts_with("password") {
931 return Some(Notification::Password(PasswordNotification::NeedPassword {
932 auth_type: parse_auth_type(auth_type_str),
933 }));
934 }
935
936 None }
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942 use crate::auth::AuthType;
943 use crate::client_event::ClientEvent;
944 use crate::message::PasswordNotification;
945 use crate::signal::Signal;
946 use crate::status_format::StatusFormat;
947 use crate::stream_mode::StreamMode;
948 use bytes::BytesMut;
949 use tokio_util::codec::{Decoder, Encoder};
950
951 fn encode_to_string(cmd: OvpnCommand) -> String {
953 let mut codec = OvpnCodec::new();
954 let mut buf = BytesMut::new();
955 codec.encode(cmd, &mut buf).unwrap();
956 String::from_utf8(buf.to_vec()).unwrap()
957 }
958
959 fn decode_all(input: &str) -> Vec<OvpnMessage> {
961 let mut codec = OvpnCodec::new();
962 let mut buf = BytesMut::from(input);
963 let mut msgs = Vec::new();
964 while let Some(msg) = codec.decode(&mut buf).unwrap() {
965 msgs.push(msg);
966 }
967 msgs
968 }
969
970 fn encode_then_decode(cmd: OvpnCommand, response: &str) -> Vec<OvpnMessage> {
972 let mut codec = OvpnCodec::new();
973 let mut enc_buf = BytesMut::new();
974 codec.encode(cmd, &mut enc_buf).unwrap();
975 let mut dec_buf = BytesMut::from(response);
976 let mut msgs = Vec::new();
977 while let Some(msg) = codec.decode(&mut dec_buf).unwrap() {
978 msgs.push(msg);
979 }
980 msgs
981 }
982
983 #[test]
986 fn encode_status_v1() {
987 assert_eq!(
988 encode_to_string(OvpnCommand::Status(StatusFormat::V1)),
989 "status\n"
990 );
991 }
992
993 #[test]
994 fn encode_status_v3() {
995 assert_eq!(
996 encode_to_string(OvpnCommand::Status(StatusFormat::V3)),
997 "status 3\n"
998 );
999 }
1000
1001 #[test]
1002 fn encode_signal() {
1003 assert_eq!(
1004 encode_to_string(OvpnCommand::Signal(Signal::SigUsr1)),
1005 "signal SIGUSR1\n"
1006 );
1007 }
1008
1009 #[test]
1010 fn encode_state_on_all() {
1011 assert_eq!(
1012 encode_to_string(OvpnCommand::StateStream(StreamMode::OnAll)),
1013 "state on all\n"
1014 );
1015 }
1016
1017 #[test]
1018 fn encode_state_recent() {
1019 assert_eq!(
1020 encode_to_string(OvpnCommand::StateStream(StreamMode::Recent(5))),
1021 "state 5\n"
1022 );
1023 }
1024
1025 #[test]
1026 fn encode_password_escaping() {
1027 let wire = encode_to_string(OvpnCommand::Password {
1030 auth_type: AuthType::PrivateKey,
1031 value: r#"foo\"bar"#.to_string(),
1032 });
1033 assert_eq!(wire, "password \"Private Key\" \"foo\\\\\\\"bar\"\n");
1034 }
1035
1036 #[test]
1037 fn encode_password_simple() {
1038 let wire = encode_to_string(OvpnCommand::Password {
1039 auth_type: AuthType::Auth,
1040 value: "hunter2".to_string(),
1041 });
1042 assert_eq!(wire, "password \"Auth\" \"hunter2\"\n");
1043 }
1044
1045 #[test]
1046 fn encode_client_auth_with_config() {
1047 let wire = encode_to_string(OvpnCommand::ClientAuth {
1048 cid: 42,
1049 kid: 0,
1050 config_lines: vec![
1051 "push \"route 10.0.0.0 255.255.0.0\"".to_string(),
1052 "push \"dhcp-option DNS 10.0.0.1\"".to_string(),
1053 ],
1054 });
1055 assert_eq!(
1056 wire,
1057 "client-auth 42 0\n\
1058 push \"route 10.0.0.0 255.255.0.0\"\n\
1059 push \"dhcp-option DNS 10.0.0.1\"\n\
1060 END\n"
1061 );
1062 }
1063
1064 #[test]
1065 fn encode_client_auth_empty_config() {
1066 let wire = encode_to_string(OvpnCommand::ClientAuth {
1067 cid: 1,
1068 kid: 0,
1069 config_lines: vec![],
1070 });
1071 assert_eq!(wire, "client-auth 1 0\nEND\n");
1072 }
1073
1074 #[test]
1075 fn encode_client_deny_with_client_reason() {
1076 let wire = encode_to_string(OvpnCommand::ClientDeny {
1077 cid: 5,
1078 kid: 0,
1079 reason: "cert revoked".to_string(),
1080 client_reason: Some("Your access has been revoked.".to_string()),
1081 });
1082 assert_eq!(
1083 wire,
1084 "client-deny 5 0 \"cert revoked\" \"Your access has been revoked.\"\n"
1085 );
1086 }
1087
1088 #[test]
1089 fn encode_rsa_sig() {
1090 let wire = encode_to_string(OvpnCommand::RsaSig {
1091 base64_lines: vec!["AAAA".to_string(), "BBBB".to_string()],
1092 });
1093 assert_eq!(wire, "rsa-sig\nAAAA\nBBBB\nEND\n");
1094 }
1095
1096 #[test]
1097 fn encode_remote_modify() {
1098 let wire = encode_to_string(OvpnCommand::Remote(RemoteAction::Modify {
1099 host: "vpn.example.com".to_string(),
1100 port: 1234,
1101 }));
1102 assert_eq!(wire, "remote MOD vpn.example.com 1234\n");
1103 }
1104
1105 #[test]
1106 fn encode_proxy_http_nct() {
1107 let wire = encode_to_string(OvpnCommand::Proxy(ProxyAction::Http {
1108 host: "proxy.local".to_string(),
1109 port: 8080,
1110 non_cleartext_only: true,
1111 }));
1112 assert_eq!(wire, "proxy HTTP proxy.local 8080 nct\n");
1113 }
1114
1115 #[test]
1116 fn encode_needok() {
1117 use crate::need_ok::NeedOkResponse;
1118 let wire = encode_to_string(OvpnCommand::NeedOk {
1119 name: "token-insertion-request".to_string(),
1120 response: NeedOkResponse::Ok,
1121 });
1122 assert_eq!(wire, "needok token-insertion-request ok\n");
1123 }
1124
1125 #[test]
1126 fn encode_needstr() {
1127 let wire = encode_to_string(OvpnCommand::NeedStr {
1128 name: "name".to_string(),
1129 value: "John".to_string(),
1130 });
1131 assert_eq!(wire, "needstr name \"John\"\n");
1132 }
1133
1134 #[test]
1135 fn encode_forget_passwords() {
1136 assert_eq!(
1137 encode_to_string(OvpnCommand::ForgetPasswords),
1138 "forget-passwords\n"
1139 );
1140 }
1141
1142 #[test]
1143 fn encode_hold_query() {
1144 assert_eq!(encode_to_string(OvpnCommand::HoldQuery), "hold\n");
1145 }
1146
1147 #[test]
1148 fn encode_echo_on_all() {
1149 assert_eq!(
1150 encode_to_string(OvpnCommand::Echo(StreamMode::OnAll)),
1151 "echo on all\n"
1152 );
1153 }
1154
1155 #[test]
1156 fn encode_client_pf() {
1157 let wire = encode_to_string(OvpnCommand::ClientPf {
1158 cid: 42,
1159 filter_lines: vec![
1160 "[CLIENTS ACCEPT]".to_string(),
1161 "-accounting".to_string(),
1162 "[SUBNETS DROP]".to_string(),
1163 "+10.0.0.0/8".to_string(),
1164 "[END]".to_string(),
1165 ],
1166 });
1167 assert_eq!(
1168 wire,
1169 "client-pf 42\n\
1170 [CLIENTS ACCEPT]\n\
1171 -accounting\n\
1172 [SUBNETS DROP]\n\
1173 +10.0.0.0/8\n\
1174 [END]\n\
1175 END\n"
1176 );
1177 }
1178
1179 #[test]
1182 fn decode_success() {
1183 let msgs = decode_all("SUCCESS: pid=12345\n");
1184 assert_eq!(msgs.len(), 1);
1185 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "pid=12345"));
1186 }
1187
1188 #[test]
1189 fn decode_success_bare() {
1190 let msgs = decode_all("SUCCESS:\n");
1192 assert_eq!(msgs.len(), 1);
1193 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s.is_empty()));
1194 }
1195
1196 #[test]
1197 fn decode_error() {
1198 let msgs = decode_all("ERROR: unknown command\n");
1199 assert_eq!(msgs.len(), 1);
1200 assert!(matches!(&msgs[0], OvpnMessage::Error(s) if s == "unknown command"));
1201 }
1202
1203 #[test]
1204 fn decode_info_notification() {
1205 let msgs = decode_all(">INFO:OpenVPN Management Interface Version 5\n");
1206 assert_eq!(msgs.len(), 1);
1207 assert!(matches!(
1208 &msgs[0],
1209 OvpnMessage::Info(s) if s == "OpenVPN Management Interface Version 5"
1210 ));
1211 }
1212
1213 #[test]
1214 fn decode_state_notification() {
1215 let msgs = decode_all(">STATE:1234567890,CONNECTED,SUCCESS,,10.0.0.1\n");
1216 assert_eq!(msgs.len(), 1);
1217 match &msgs[0] {
1218 OvpnMessage::Notification(Notification::State {
1219 timestamp,
1220 name,
1221 description,
1222 local_ip,
1223 remote_ip,
1224 ..
1225 }) => {
1226 assert_eq!(*timestamp, 1234567890);
1227 assert_eq!(*name, OpenVpnState::Connected);
1228 assert_eq!(description, "SUCCESS");
1229 assert_eq!(local_ip, "");
1230 assert_eq!(remote_ip, "10.0.0.1");
1231 }
1232 other => panic!("unexpected: {other:?}"),
1233 }
1234 }
1235
1236 #[test]
1237 fn decode_multiline_with_command_tracking() {
1238 let msgs = encode_then_decode(
1242 OvpnCommand::Status(StatusFormat::V1),
1243 "OpenVPN CLIENT LIST\nCommon Name,Real Address\ntest,1.2.3.4:1234\nEND\n",
1244 );
1245 assert_eq!(msgs.len(), 1);
1246 match &msgs[0] {
1247 OvpnMessage::MultiLine(lines) => {
1248 assert_eq!(lines.len(), 3);
1249 assert_eq!(lines[0], "OpenVPN CLIENT LIST");
1250 assert_eq!(lines[2], "test,1.2.3.4:1234");
1251 }
1252 other => panic!("unexpected: {other:?}"),
1253 }
1254 }
1255
1256 #[test]
1257 fn decode_hold_query_single_value() {
1258 let msgs = encode_then_decode(OvpnCommand::HoldQuery, "0\n");
1260 assert_eq!(msgs.len(), 1);
1261 assert!(matches!(&msgs[0], OvpnMessage::SingleValue(s) if s == "0"));
1262 }
1263
1264 #[test]
1265 fn decode_bare_state_single_value() {
1266 let msgs = encode_then_decode(
1267 OvpnCommand::State,
1268 "1234567890,CONNECTED,SUCCESS,,10.0.0.1,,\n",
1269 );
1270 assert_eq!(msgs.len(), 1);
1271 assert!(matches!(&msgs[0], OvpnMessage::SingleValue(s) if s.starts_with("1234567890")));
1272 }
1273
1274 #[test]
1275 fn decode_notification_during_multiline() {
1276 let msgs = encode_then_decode(
1279 OvpnCommand::Status(StatusFormat::V1),
1280 "header line\n>BYTECOUNT:1000,2000\ndata line\nEND\n",
1281 );
1282 assert_eq!(msgs.len(), 2);
1283 assert!(matches!(
1285 &msgs[0],
1286 OvpnMessage::Notification(Notification::ByteCount {
1287 bytes_in: 1000,
1288 bytes_out: 2000
1289 })
1290 ));
1291 match &msgs[1] {
1293 OvpnMessage::MultiLine(lines) => {
1294 assert_eq!(lines, &["header line", "data line"]);
1295 }
1296 other => panic!("unexpected: {other:?}"),
1297 }
1298 }
1299
1300 #[test]
1301 fn decode_client_connect_multiline_notification() {
1302 let input = "\
1303 >CLIENT:CONNECT,0,1\n\
1304 >CLIENT:ENV,untrusted_ip=1.2.3.4\n\
1305 >CLIENT:ENV,common_name=TestClient\n\
1306 >CLIENT:ENV,END\n";
1307 let msgs = decode_all(input);
1308 assert_eq!(msgs.len(), 1);
1309 match &msgs[0] {
1310 OvpnMessage::Notification(Notification::Client {
1311 event,
1312 cid,
1313 kid,
1314 env,
1315 }) => {
1316 assert_eq!(*event, ClientEvent::Connect);
1317 assert_eq!(*cid, 0);
1318 assert_eq!(*kid, Some(1));
1319 assert_eq!(env.len(), 2);
1320 assert_eq!(env[0], ("untrusted_ip".to_string(), "1.2.3.4".to_string()));
1321 assert_eq!(
1322 env[1],
1323 ("common_name".to_string(), "TestClient".to_string())
1324 );
1325 }
1326 other => panic!("unexpected: {other:?}"),
1327 }
1328 }
1329
1330 #[test]
1331 fn decode_client_address_single_line() {
1332 let msgs = decode_all(">CLIENT:ADDRESS,3,10.0.0.5,1\n");
1333 assert_eq!(msgs.len(), 1);
1334 match &msgs[0] {
1335 OvpnMessage::Notification(Notification::ClientAddress { cid, addr, primary }) => {
1336 assert_eq!(*cid, 3);
1337 assert_eq!(addr, "10.0.0.5");
1338 assert!(*primary);
1339 }
1340 other => panic!("unexpected: {other:?}"),
1341 }
1342 }
1343
1344 #[test]
1345 fn decode_client_disconnect() {
1346 let input = "\
1347 >CLIENT:DISCONNECT,5\n\
1348 >CLIENT:ENV,bytes_received=12345\n\
1349 >CLIENT:ENV,bytes_sent=67890\n\
1350 >CLIENT:ENV,END\n";
1351 let msgs = decode_all(input);
1352 assert_eq!(msgs.len(), 1);
1353 match &msgs[0] {
1354 OvpnMessage::Notification(Notification::Client {
1355 event,
1356 cid,
1357 kid,
1358 env,
1359 }) => {
1360 assert_eq!(*event, ClientEvent::Disconnect);
1361 assert_eq!(*cid, 5);
1362 assert_eq!(*kid, None);
1363 assert_eq!(env.len(), 2);
1364 }
1365 other => panic!("unexpected: {other:?}"),
1366 }
1367 }
1368
1369 #[test]
1370 fn decode_password_notification() {
1371 let msgs = decode_all(">PASSWORD:Need 'Auth' username/password\n");
1372 assert_eq!(msgs.len(), 1);
1373 match &msgs[0] {
1374 OvpnMessage::Notification(Notification::Password(PasswordNotification::NeedAuth {
1375 auth_type,
1376 })) => {
1377 assert_eq!(*auth_type, AuthType::Auth);
1378 }
1379 other => panic!("unexpected: {other:?}"),
1380 }
1381 }
1382
1383 #[test]
1384 fn quote_and_escape_special_chars() {
1385 assert_eq!(quote_and_escape(r#"foo"bar"#), r#""foo\"bar""#);
1386 assert_eq!(quote_and_escape(r"a\b"), r#""a\\b""#);
1387 assert_eq!(quote_and_escape("simple"), r#""simple""#);
1388 }
1389
1390 #[test]
1391 fn decode_empty_multiline() {
1392 let msgs = encode_then_decode(OvpnCommand::Status(StatusFormat::V1), "END\n");
1394 assert_eq!(msgs.len(), 1);
1395 assert!(matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines.is_empty()));
1396 }
1397
1398 #[test]
1399 fn decode_need_ok_notification() {
1400 let msgs = decode_all(
1401 ">NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your token\n",
1402 );
1403 assert_eq!(msgs.len(), 1);
1404 match &msgs[0] {
1405 OvpnMessage::Notification(Notification::NeedOk { name, message }) => {
1406 assert_eq!(name, "token-insertion-request");
1407 assert_eq!(message, "Please insert your token");
1408 }
1409 other => panic!("unexpected: {other:?}"),
1410 }
1411 }
1412
1413 #[test]
1414 fn decode_hold_notification() {
1415 let msgs = decode_all(">HOLD:Waiting for hold release\n");
1416 assert_eq!(msgs.len(), 1);
1417 match &msgs[0] {
1418 OvpnMessage::Notification(Notification::Hold { text }) => {
1419 assert_eq!(text, "Waiting for hold release");
1420 }
1421 other => panic!("unexpected: {other:?}"),
1422 }
1423 }
1424
1425 #[test]
1428 fn encode_raw_multiline() {
1429 assert_eq!(
1430 encode_to_string(OvpnCommand::RawMultiLine("custom-cmd arg".to_string())),
1431 "custom-cmd arg\n"
1432 );
1433 }
1434
1435 #[test]
1436 fn raw_multiline_expects_multiline_response() {
1437 let msgs = encode_then_decode(
1438 OvpnCommand::RawMultiLine("custom".to_string()),
1439 "line1\nline2\nEND\n",
1440 );
1441 assert_eq!(msgs.len(), 1);
1442 match &msgs[0] {
1443 OvpnMessage::MultiLine(lines) => {
1444 assert_eq!(lines, &["line1", "line2"]);
1445 }
1446 other => panic!("expected MultiLine, got: {other:?}"),
1447 }
1448 }
1449
1450 #[test]
1451 fn raw_multiline_sanitizes_newlines() {
1452 let wire = encode_to_string(OvpnCommand::RawMultiLine("cmd\ninjected".to_string()));
1453 assert_eq!(wire, "cmdinjected\n");
1454 }
1455
1456 #[test]
1459 #[should_panic(expected = "mid-accumulation")]
1460 fn encode_during_multiline_accumulation_panics() {
1461 let mut codec = OvpnCodec::new();
1462 let mut buf = BytesMut::new();
1463 codec
1465 .encode(OvpnCommand::Status(StatusFormat::V1), &mut buf)
1466 .unwrap();
1467 let mut dec = BytesMut::from("header line\n");
1469 let _ = codec.decode(&mut dec); codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1472 }
1473
1474 #[test]
1475 #[should_panic(expected = "mid-accumulation")]
1476 fn encode_during_client_notif_accumulation_panics() {
1477 let mut codec = OvpnCodec::new();
1478 let mut buf = BytesMut::new();
1479 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1481 let _ = codec.decode(&mut dec);
1482 codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1484 }
1485
1486 #[test]
1489 fn unlimited_accumulation_default() {
1490 let mut codec = OvpnCodec::new();
1491 let mut enc = BytesMut::new();
1492 codec
1493 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1494 .unwrap();
1495 let mut data = String::new();
1497 for i in 0..500 {
1498 data.push_str(&format!("line {i}\n"));
1499 }
1500 data.push_str("END\n");
1501 let mut dec = BytesMut::from(data.as_str());
1502 let mut msgs = Vec::new();
1503 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1504 msgs.push(msg);
1505 }
1506 assert_eq!(msgs.len(), 1);
1507 match &msgs[0] {
1508 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 500),
1509 other => panic!("expected MultiLine, got: {other:?}"),
1510 }
1511 }
1512
1513 #[test]
1514 fn multi_line_limit_exceeded() {
1515 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1516 let mut enc = BytesMut::new();
1517 codec
1518 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1519 .unwrap();
1520 let mut dec = BytesMut::from("a\nb\nc\nd\nEND\n");
1521 let result = loop {
1522 match codec.decode(&mut dec) {
1523 Ok(Some(msg)) => break Ok(msg),
1524 Ok(None) => continue,
1525 Err(e) => break Err(e),
1526 }
1527 };
1528 assert!(result.is_err(), "expected error when limit exceeded");
1529 let err = result.unwrap_err();
1530 assert!(
1531 err.to_string().contains("multi-line response"),
1532 "error should mention multi-line: {err}"
1533 );
1534 }
1535
1536 #[test]
1537 fn multi_line_limit_exact_boundary_passes() {
1538 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1539 let mut enc = BytesMut::new();
1540 codec
1541 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1542 .unwrap();
1543 let mut dec = BytesMut::from("a\nb\nc\nEND\n");
1545 let mut msgs = Vec::new();
1546 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1547 msgs.push(msg);
1548 }
1549 assert_eq!(msgs.len(), 1);
1550 match &msgs[0] {
1551 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 3),
1552 other => panic!("expected MultiLine, got: {other:?}"),
1553 }
1554 }
1555
1556 #[test]
1557 fn client_env_limit_exceeded() {
1558 let mut codec = OvpnCodec::new().with_max_client_env_entries(AccumulationLimit::Max(2));
1559 let mut dec = BytesMut::from(
1560 ">CLIENT:CONNECT,0,1\n\
1561 >CLIENT:ENV,a=1\n\
1562 >CLIENT:ENV,b=2\n\
1563 >CLIENT:ENV,c=3\n\
1564 >CLIENT:ENV,END\n",
1565 );
1566 let result = loop {
1567 match codec.decode(&mut dec) {
1568 Ok(Some(msg)) => break Ok(msg),
1569 Ok(None) => continue,
1570 Err(e) => break Err(e),
1571 }
1572 };
1573 assert!(
1574 result.is_err(),
1575 "expected error when client ENV limit exceeded"
1576 );
1577 let err = result.unwrap_err();
1578 assert!(
1579 err.to_string().contains("client ENV"),
1580 "error should mention client ENV: {err}"
1581 );
1582 }
1583
1584 #[test]
1587 fn utf8_error_resets_multiline_state() {
1588 let mut codec = OvpnCodec::new();
1589 let mut enc = BytesMut::new();
1590 codec
1591 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1592 .unwrap();
1593 let mut dec = BytesMut::from("header\n");
1595 assert!(codec.decode(&mut dec).unwrap().is_none());
1596 dec.extend_from_slice(b"bad \xff line\n");
1598 assert!(codec.decode(&mut dec).is_err());
1599 dec.extend_from_slice(b"SUCCESS: recovered\n");
1602 let msg = codec.decode(&mut dec).unwrap();
1603 match msg {
1604 Some(OvpnMessage::Success(ref s)) if s.contains("recovered") => {}
1605 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1606 }
1607 }
1608
1609 #[test]
1610 fn utf8_error_resets_client_notif_state() {
1611 let mut codec = OvpnCodec::new();
1612 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1614 assert!(codec.decode(&mut dec).unwrap().is_none());
1615 dec.extend_from_slice(b">CLIENT:ENV,\xff\n");
1617 assert!(codec.decode(&mut dec).is_err());
1618 dec.extend_from_slice(b"SUCCESS: ok\n");
1620 let msg = codec.decode(&mut dec).unwrap();
1621 match msg {
1622 Some(OvpnMessage::Success(_)) => {}
1623 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1624 }
1625 }
1626}