openvpn_mgmt_frame/lib.rs
1//! Low-level line framing for the OpenVPN management protocol.
2//!
3//! This crate provides:
4//!
5//! - [`Frame`] — a classified line from the management interface
6//! - [`FrameDecoder`] — a stateless-ish [`tokio_util::codec::Decoder`] that
7//! splits the byte stream into [`Frame`] values
8//! - Encoder primitives ([`write_line`], [`write_block`], [`wire_safe`],
9//! [`escape`], [`quote`]) for building wire-format commands
10//!
11//! The decoder does **not** track which command was sent — it classifies
12//! each line purely from its content. Multi-line response accumulation
13//! (grouping `Line`/`End` sequences) is left to higher layers.
14//!
15//! `>CLIENT:ENV` accumulation **is** handled here because the protocol
16//! guarantees atomicity for that block and the individual ENV lines are
17//! not meaningful on their own.
18//!
19//! # Decoding frames
20//!
21//! ```
22//! use bytes::BytesMut;
23//! use tokio_util::codec::Decoder;
24//! use openvpn_mgmt_frame::{Frame, FrameDecoder};
25//!
26//! let mut decoder = FrameDecoder::new();
27//! let mut buf = BytesMut::from("SUCCESS: pid=1234\n>HOLD:Waiting for hold release:0\n");
28//!
29//! assert_eq!(
30//! decoder.decode(&mut buf).unwrap(),
31//! Some(Frame::Success("pid=1234".to_string())),
32//! );
33//! assert_eq!(
34//! decoder.decode(&mut buf).unwrap(),
35//! Some(Frame::Notification {
36//! kind: "HOLD".to_string(),
37//! payload: "Waiting for hold release:0".to_string(),
38//! }),
39//! );
40//! assert_eq!(decoder.decode(&mut buf).unwrap(), None); // buffer drained
41//! ```
42//!
43//! # Encoding commands
44//!
45//! ```
46//! use bytes::BytesMut;
47//! use openvpn_mgmt_frame::{write_line, write_block, escape, quote, EncoderMode};
48//!
49//! let mut buf = BytesMut::new();
50//! write_line(&mut buf, "status 3");
51//! assert_eq!(&buf[..], b"status 3\n");
52//!
53//! buf.clear();
54//! write_block(&mut buf, "client-auth 1 2", &["push-reply".to_string()], EncoderMode::Sanitize).unwrap();
55//! assert_eq!(&buf[..], b"client-auth 1 2\npush-reply\nEND\n");
56//! ```
57#![deny(unsafe_code)]
58#![warn(missing_docs)]
59
60mod decoder;
61mod encoder;
62mod frame;
63
64pub use decoder::FrameDecoder;
65pub use encoder::{
66 AccumulationLimit, EncodeError, EncoderMode, escape, quote, wire_safe, write_block, write_line,
67};
68pub use frame::Frame;