84 lines
2.2 KiB
Rust
84 lines
2.2 KiB
Rust
|
|
//! Upstream table: cmdline parsing and MANIFEST entry construction.
|
||
|
|
|
||
|
|
use crate::frame::{UpstreamEntry, PROTO_TCP, PROTO_UDP};
|
||
|
|
|
||
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
|
|
pub enum Proto {
|
||
|
|
Tcp,
|
||
|
|
Udp,
|
||
|
|
}
|
||
|
|
impl Proto {
|
||
|
|
pub fn as_u8(self) -> u8 {
|
||
|
|
match self {
|
||
|
|
Proto::Tcp => PROTO_TCP,
|
||
|
|
Proto::Udp => PROTO_UDP,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Clone, Debug)]
|
||
|
|
pub struct Upstream {
|
||
|
|
pub id: u8,
|
||
|
|
pub proto: Proto,
|
||
|
|
pub port: u16,
|
||
|
|
pub label: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct UpstreamTable(pub Vec<Upstream>);
|
||
|
|
|
||
|
|
impl UpstreamTable {
|
||
|
|
pub fn get(&self, id: u8) -> Option<&Upstream> {
|
||
|
|
self.0.iter().find(|u| u.id == id)
|
||
|
|
}
|
||
|
|
pub fn entries(&self) -> Vec<UpstreamEntry> {
|
||
|
|
self.0
|
||
|
|
.iter()
|
||
|
|
.map(|u| UpstreamEntry {
|
||
|
|
id: u.id,
|
||
|
|
proto: u.proto.as_u8(),
|
||
|
|
port: u.port,
|
||
|
|
label: u.label.clone(),
|
||
|
|
})
|
||
|
|
.collect()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Parse a single `PORT[:label]` argument. v1 only accepts bare integers or
|
||
|
|
/// `int:label`; an explicit `proto:` prefix is reserved for the UDP extension.
|
||
|
|
pub fn parse_port_arg(id: u8, s: &str) -> Result<Upstream, String> {
|
||
|
|
let (port_str, label) = match s.split_once(':') {
|
||
|
|
Some((p, l)) => (p, Some(l.to_string())),
|
||
|
|
None => (s, None),
|
||
|
|
};
|
||
|
|
let port: u16 = port_str
|
||
|
|
.parse()
|
||
|
|
.map_err(|_| format!("invalid port value: {port_str}"))?;
|
||
|
|
if port == 0 {
|
||
|
|
return Err(format!("port must be > 0: {s}"));
|
||
|
|
}
|
||
|
|
if let Some(l) = &label {
|
||
|
|
if l.is_empty() {
|
||
|
|
return Err(format!("empty label: {s}"));
|
||
|
|
}
|
||
|
|
if l.len() > 255 {
|
||
|
|
return Err(format!("label too long (max 255 bytes): {s}"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Ok(Upstream { id, proto: Proto::Tcp, port, label })
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn build_table(args: &[String]) -> Result<UpstreamTable, String> {
|
||
|
|
if args.is_empty() {
|
||
|
|
return Err("at least one PORT is required".into());
|
||
|
|
}
|
||
|
|
if args.len() > 255 {
|
||
|
|
return Err("too many upstreams (max 255)".into());
|
||
|
|
}
|
||
|
|
let mut v = Vec::with_capacity(args.len());
|
||
|
|
for (i, a) in args.iter().enumerate() {
|
||
|
|
let id = (i + 1) as u8;
|
||
|
|
v.push(parse_port_arg(id, a)?);
|
||
|
|
}
|
||
|
|
Ok(UpstreamTable(v))
|
||
|
|
}
|