//! 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, } pub struct UpstreamTable(pub Vec); impl UpstreamTable { pub fn get(&self, id: u8) -> Option<&Upstream> { self.0.iter().find(|u| u.id == id) } pub fn entries(&self) -> Vec { 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 { 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 { 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)) }