burp: Begin BURP stats client implementation

The `burp` module contains an implementation of a BURP stats client.  It
uses *tokio* for asynchronous network communication with the BURP stats
TCP socket.  The `ClientConnector` struct follows the builder pattern
for specifying connection options, ultimately producing a `Client`
struct that manages communication over the socket.

BURP uses mutual TLS authentication for all its communication.  The
client authenticates the server by verifying its certificate using a
trusted CA certificate.  This certificate is not usually trusted
system-wide, but specifically by BURP clients.  The server also
authenticates the client using a certificate.  The official BURP client
uses a normal PEM-encoded X.509 certificate and PKCS #8 key, however,
the *native-tls* library does not support loading these.  As such, the
certificate and private key must be encapsulated in a PKCS #12
container.
Dustin 2022-02-08 20:20:52 -06:00
parent 727aaa6b8f
commit ce3b9703bd
5 changed files with 836 additions and 0 deletions

522
Cargo.lock generated
View File

@ -2,6 +2,528 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "burp_exporter"
version = "0.1.0"
dependencies = [
"openssl",
"tokio",
"tokio-native-tls",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core-foundation"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"instant",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "native-tls"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "openssl"
version = "0.10.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "security-framework"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "tokio"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
dependencies = [
"bytes",
"libc",
"memchr",
"mio",
"once_cell",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tracing"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
dependencies = [
"lazy_static",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa"
dependencies = [
"ansi_term",
"lazy_static",
"matchers",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -6,3 +6,14 @@ license = "MIT OR Apache-2.0"
edition = "2021"
[dependencies]
openssl = "^0.10.38"
tokio-native-tls = "^0.3.0"
tracing = "^0.1.30"
[dependencies.tokio]
version = "^1.16.1"
features = ["io-util", "macros", "net", "rt", "signal"]
[dependencies.tracing-subscriber]
version = "^0.3.8"
features = ["env-filter"]

259
src/burp/client.rs Normal file
View File

@ -0,0 +1,259 @@
//! BURP client
use std::char;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::str;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_native_tls::native_tls::TlsConnector as NativeTlsConnector;
use tokio_native_tls::native_tls::{Certificate, Identity, Protocol};
use tokio_native_tls::TlsConnector;
use tokio_native_tls::TlsStream;
use tracing::{debug, info, trace};
use super::error::Error;
/// The BURP client version emulated by this implementation
const CLIENT_VERSION: &str = "2.1.32";
#[doc(hidden)]
macro_rules! unexpected_response {
($msg:ident) => {
Err(Error::Protocol(format!(
"Unexpected response from server: {}",
$msg,
)))
};
}
/// Specify client connection options using the builder pattern
///
/// To connect to a BURP server, create a [`ClientConnector`] and use
/// its methods to specify connection options. After all options have
/// been set, call [`ClientConnector::connect`] to initiate the
/// connection.
///
/// ## Example
///
/// ```rust
/// let mut client = ClientConnector::new("::1")
/// .port(49720)
/// .server_name("burp.example.org")
/// .ca_cert(cert)
/// .connect()
/// .await?;
/// ```
pub struct ClientConnector {
host: String,
port: u16,
server_name: Option<String>,
ca_cert: Option<Certificate>,
identity: Option<Identity>,
}
impl ClientConnector {
/// Create a new client connector
///
/// `host` is the string hostname or IP address of the server
pub fn new(host: impl Into<String>) -> Self {
ClientConnector {
host: host.into(),
port: 4972,
server_name: None,
ca_cert: None,
identity: None,
}
}
/// Set the server port
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
/// Set the CA certificate used to verify the server's certificate
pub fn ca_cert(mut self, cert: Certificate) -> Self {
self.ca_cert = Some(cert);
self
}
/// Set the CA certificate from a file
///
/// This method may return an error if the certificate could not be
/// loaded from the specified file.
pub fn ca_cert_file(self, path: impl AsRef<Path>) -> Result<Self, Error> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
Ok(self.ca_cert(Certificate::from_pem(&buf)?))
}
/// Set the certificate and private key for client authentication
pub fn identity(mut self, identity: Identity) -> Self {
self.identity = Some(identity);
self
}
/// Set the certificate and private key from a file
///
/// This method may return an error if the certificate or private
/// key could not be loaded from the specified file, or the file is
/// not a valid PKCS#12 container.
pub fn identity_file(
self,
path: impl AsRef<Path>,
password: &str,
) -> Result<Self, Error> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
Ok(self.identity(Identity::from_pkcs12(&buf, password)?))
}
/// Set the name of the server to use for certificate validation
///
/// If the name used to connect to the server does not match any of
/// the names on its certificate, this method can be used to specify
/// the expected name.
pub fn server_name(mut self, name: impl Into<String>) -> Self {
self.server_name = Some(name.into());
self
}
/// Connect to the server
///
/// Returns an [`Error`] if the connection could not be established
/// for any reason (e.g. name does not resolve, address is
/// unreachable, TLS handshake fails, etc.)
pub async fn connect(self) -> Result<Client, Error> {
let stream =
TcpStream::connect((self.host.as_ref(), self.port)).await?;
info!(
"Successfully connected to {}, port {}",
&self.host, self.port
);
let mut builder = NativeTlsConnector::builder();
builder.min_protocol_version(Some(Protocol::Tlsv12));
if let Some(ca_cert) = self.ca_cert {
builder.add_root_certificate(ca_cert);
}
if let Some(identity) = self.identity {
builder.identity(identity);
}
let conn: TlsConnector = builder.build()?.into();
let socket = conn
.connect(&self.server_name.unwrap_or(self.host), stream)
.await?;
Ok(Client { socket })
}
}
/// BURP stats client
pub struct Client {
socket: TlsStream<TcpStream>,
}
impl Client {
/// Perform the initial handshake and authentication
///
/// * `name`: The BURP client name (must match certificate)
/// * `password`: (Optional) The client password; use `None` to
/// avoid sending a password to the server
///
/// If the handshake fails, [`Error`] is returned.
pub async fn handshake(
&mut self,
name: &str,
password: Option<&str>,
) -> Result<(), Error> {
let res = self
.command('c', &format!("hello:{}", CLIENT_VERSION))
.await?;
let mut parts = res.split(':');
if let Some(msg) = parts.next() {
if msg != "whoareyou" {
return unexpected_response!(res);
}
if let Some(version) = parts.next() {
info!("Server version: {}", version);
if version.is_empty() {
return Err(Error::UnsupportedVersion(
"Unknown server version".into(),
));
} else if !version.starts_with('2') {
return Err(Error::UnsupportedVersion(format!(
"Unsupported server version: {}",
version
)));
}
}
} else {
return unexpected_response!(res);
}
let res = self.command('c', name).await?;
if res != "okpassword" {
return unexpected_response!(res);
}
if let Some(password) = password {
let res = self.command('c', password).await?;
if res != "ok" {
return unexpected_response!(res);
}
}
let res = self.command('c', "nocsr").await?;
if res != "nocsr ok" {
return unexpected_response!(res);
}
let res = self.command('c', "extra_comms_begin").await?;
let mut parts = res.split_terminator(':');
if let Some(msg) = parts.next() {
if msg != "extra_comms_begin ok" {
return unexpected_response!(msg);
}
} else {
return unexpected_response!(res);
}
let capabilities: Vec<&str> = parts.collect();
info!("Server capabilities: {:?}", capabilities);
Ok(())
}
/// Send a command to the server
///
/// * `command`: BURP command (usually `c`)
/// * `data`: Data to send with the command, if applicable
///
/// If the server returns a successful (`c`) response, its data
/// will be returned as a string. If it returns an error (`e`)
/// response, its data will be returned in an [`Error`].
pub async fn command(
&mut self,
command: char,
data: &str,
) -> Result<String, Error> {
debug!("Sending command {} {}", &command, &data);
let msg = format!("{}{:>04X}{}\0", &command, data.len() + 1, &data);
trace!("Sending {:?}", msg);
self.socket.write_all(msg.as_bytes()).await?;
let mut buf = [0; 5];
self.socket.read_exact(&mut buf).await?;
trace!("Got response header {:?}", str::from_utf8(&buf));
let msgtype = buf[0];
let sz = usize::from_str_radix(str::from_utf8(&buf[1..]).unwrap(), 16)
.unwrap();
trace!("Response body length: {} bytes", sz);
let mut buf = vec![0; sz];
self.socket.read_exact(&mut buf).await?;
let response = String::from_utf8(buf).unwrap();
debug!("Server response: {} {}", char::from(msgtype), &response);
match msgtype {
b'c' => Ok(response),
b'e' => Err(Error::Command(response)),
_ => {
panic!("don't know what to do with {:?}", msg);
}
}
}
}

34
src/burp/error.rs Normal file
View File

@ -0,0 +1,34 @@
//! Client error handling
use std::io;
use tokio_native_tls::native_tls;
/// BURP client errors
///
/// Most functions and methods in this module return this error type.
/// Depending on the cause of the error, a more specific error type may
/// be encapsulated.
#[derive(Debug)]
pub enum Error {
/// An I/O (e.g. file, network) error has occurred
Io(io::Error),
/// A TLS error has occurred
Tls(native_tls::Error),
/// The BURP server returned an error response to a command
Command(String),
/// The BURP server did not return the expected response
Protocol(String),
/// The version of the BURP server is not supported
UnsupportedVersion(String),
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
impl From<native_tls::Error> for Error {
fn from(error: native_tls::Error) -> Self {
Self::Tls(error)
}
}

10
src/burp/mod.rs Normal file
View File

@ -0,0 +1,10 @@
//! BURP Stats Client
//!
//! This module provides a client implementation for the BURP stats
//! socket protocol. It can be used to connect to the stats socket of a
//! BURP server and retrieve statistics about BURP clients and their
//! backups.
#![warn(missing_docs)]
pub mod client;
pub mod error;