Skip to main content

openvpn_mgmt_frame/
frame.rs

1use std::collections::BTreeMap;
2
3/// A classified line (or accumulated block) from the OpenVPN management
4/// interface.
5///
6/// The frame decoder emits one `Frame` per logical unit. Most variants
7/// map 1:1 to a wire line; [`ClientEnv`](Frame::ClientEnv) is the
8/// exception — it accumulates the full `>CLIENT:` header + ENV block
9/// before being emitted.
10///
11/// ```
12/// use bytes::BytesMut;
13/// use tokio_util::codec::Decoder;
14/// use openvpn_mgmt_frame::{Frame, FrameDecoder};
15///
16/// let mut decoder = FrameDecoder::new();
17/// let mut buf = BytesMut::from(
18///     "SUCCESS: pid=42\nERROR: unknown command\nEND\n"
19/// );
20///
21/// assert!(matches!(decoder.decode(&mut buf).unwrap(), Some(Frame::Success(_))));
22/// assert!(matches!(decoder.decode(&mut buf).unwrap(), Some(Frame::Error(_))));
23/// assert!(matches!(decoder.decode(&mut buf).unwrap(), Some(Frame::End)));
24/// ```
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum Frame {
28    /// `SUCCESS: [text]` — a command completed successfully.
29    Success(String),
30
31    /// `ERROR: [text]` — a command failed.
32    Error(String),
33
34    /// A `>` notification line (single-line).
35    ///
36    /// `kind` is the tag before the first `:` (e.g. `"STATE"`, `"LOG"`).
37    /// `payload` is everything after the `:`.
38    ///
39    /// `>CLIENT:` lines with ENV blocks are **not** emitted as
40    /// `Notification` — they become [`ClientEnv`](Frame::ClientEnv)
41    /// instead.
42    Notification {
43        /// Notification type tag (e.g. `"STATE"`, `"LOG"`, `"BYTECOUNT"`).
44        kind: String,
45        /// Everything after `>KIND:`.
46        payload: String,
47    },
48
49    /// A fully accumulated `>CLIENT:` notification with its ENV block.
50    ///
51    /// All `>CLIENT:ENV,key=value` lines have been collected; the
52    /// terminating `>CLIENT:ENV,END` has been consumed.
53    ClientEnv {
54        /// Raw event string (e.g. `"CONNECT"`, `"REAUTH"`, `"ADDRESS"`).
55        event: String,
56        /// Everything after the event on the header line (CID, KID, etc.),
57        /// as a raw comma-separated string.
58        args: String,
59        /// Accumulated ENV key-value pairs.
60        env: BTreeMap<String, String>,
61    },
62
63    /// `ENTER PASSWORD:` prompt.
64    PasswordPrompt,
65
66    /// A bare `END` line — terminates a multi-line response block.
67    End,
68
69    /// The first `>INFO:` line (the connection banner).
70    ///
71    /// Subsequent `>INFO:` lines are emitted as
72    /// [`Notification`](Frame::Notification) with `kind = "INFO"`.
73    Info(String),
74
75    /// Any line that is not self-describing (no `SUCCESS:`/`ERROR:`/`>`
76    /// prefix, not `END`, not `ENTER PASSWORD:`).
77    ///
78    /// Higher layers use command-tracking state to decide whether this
79    /// belongs to a multi-line response or is an unrecognized line.
80    Line(String),
81}