use configparser::ini::Ini;
+#[derive(hippotat_macros::ResolveConfig)]
+#[derive(Debug,Clone)]
+pub struct InstanceConfig {
+ // Exceptional settings
+ #[special(special_link, SKL::ServerName)] pub link: LinkName,
+ pub secret: Secret,
+ #[special(special_ipif, SKL::Ordinary)] pub ipif: String,
+
+ // Capped settings:
+ #[limited] pub max_batch_down: u32,
+ #[limited] pub max_queue_time: Duration,
+ #[limited] pub http_timeout: Duration,
+ #[limited] pub target_requests_outstanding: u32,
+
+ // Ordinary settings:
+ pub addrs: Vec<IpAddr>,
+ pub vnetwork: Vec<IpNet>,
+ pub vaddr: IpAddr,
+ pub vrelay: IpAddr,
+ pub port: u16,
+ pub mtu: u32,
+ pub ifname_server: String,
+ pub ifname_client: String,
+
+ // Ordinary settings, used by server only:
+ #[server] pub max_clock_skew: Duration,
+
+ // Ordinary settings, used by client only:
+ #[client] pub http_timeout_grace: Duration,
+ #[client] pub max_requests_outstanding: u32,
+ #[client] pub max_batch_up: u32,
+ #[client] pub http_retry: Duration,
+ #[client] pub url: Uri,
+ #[client] pub vroutes: Vec<IpNet>,
+}
+
static DEFAULT_CONFIG: &str = r#"
[COMMON]
max_batch_down = 65536
http_retry = 5
port = 80
vroutes = ''
-ifname_client = hippo%%d
-ifname_server = shippo%%d
+ifname_client = hippo%d
+ifname_server = shippo%d
max_clock_skew = 300
-ipif = userv root ipif %(local)s,%(peer)s,%(mtu)s,slip,%(ifname)s %(rnets)s
+ipif = userv root ipif %{local},%{peer},%{mtu},slip,%{ifname} '%{rnets}'
mtu = 1500
-vvnetwork = 172.24.230.192
+vnetwork = 172.24.230.192
[LIMIT]
max_batch_down = 262144
fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Secret(***)")? }
}
-#[derive(hippotat_macros::ResolveConfig)]
-#[derive(Debug,Clone)]
-pub struct InstanceConfig {
- // Exceptional settings
- #[special(special_server, SKL::ServerName)] pub server: ServerName,
- pub secret: Secret,
- #[special(special_ipif, SKL::Ordinary)] pub ipif: String,
-
- // Capped settings:
- #[limited] pub max_batch_down: u32,
- #[limited] pub max_queue_time: Duration,
- #[limited] pub http_timeout: Duration,
- #[limited] pub target_requests_outstanding: u32,
-
- // Ordinary settings:
- pub addrs: Vec<IpAddr>,
- pub vnetwork: Vec<IpNet>,
- pub vaddr: Vec<IpAddr>,
- pub vrelay: IpAddr,
- pub port: u16,
- pub mtu: u32,
- pub ifname_server: String,
- pub ifname_client: String,
-
- // Ordinary settings, used by server only:
- #[server] pub max_clock_skew: Duration,
-
- // Ordinary settings, used by client only:
- #[client] pub http_timeout_grace: Duration,
- #[client] pub max_requests_outstanding: u32,
- #[client] pub max_batch_up: u32,
- #[client] pub http_retry: Duration,
- #[client] pub url: Uri,
- #[client] pub vroutes: Vec<IpNet>,
-}
-
#[derive(Debug,Clone,Hash,Eq,PartialEq)]
pub enum SectionName {
Link(LinkName),
struct RawVal { raw: Option<String>, loc: Arc<PathBuf> }
type SectionMap = HashMap<String, RawVal>;
+#[derive(Debug)]
struct RawValRef<'v,'l,'s> {
raw: Option<&'v str>,
key: &'static str,
fn try_map<F,T>(&self, f: F) -> T
where F: FnOnce(Option<&'v str>) -> Result<T, AE> {
f(self.raw)
- .with_context(|| format!(r#"file {:?}, section "{:?}", key "{}"#,
+ .with_context(|| format!(r#"file {:?}, section {}, key "{}""#,
self.loc, self.section, self.key))?
}
}
pub struct Config {
- opts: Opts,
+ pub opts: Opts,
}
static OUTSIDE_SECTION: &str = "[";
#[derive(Default,Debug)]
struct Aggregate {
+ keys_allowed: HashMap<&'static str, SectionKindList>,
sections: HashMap<SectionName, SectionMap>,
}
SN::Link(LinkName { server, client })
}
}
+impl Display for InstanceConfig {
+ #[throws(fmt::Error)]
+ fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.link, f)? }
+}
+
+impl Display for SectionName {
+ #[throws(fmt::Error)]
+ fn fmt(&self, f: &mut fmt::Formatter) {
+ match self {
+ SN::Link (ref l) => Display::fmt(l, f)?,
+ SN::Client(ref c) => write!(f, "[{}]" , c)?,
+ SN::Server(ref s) => write!(f, "[{}]" , s)?,
+ SN::ServerLimit(ref s) => write!(f, "[{} LIMIT] ", s)?,
+ SN::GlobalLimit => write!(f, "[LIMIT]" )?,
+ SN::Common => write!(f, "[COMMON]" )?,
+ }
+ }
+}
impl Aggregate {
#[throws(AE)] // AE does not include path
let loc = Arc::new(path_for_loc.to_owned());
for (sn, vars) in map {
- //dbg!( InstanceConfig::FIELDS );// check xxx vars are in fields
-
let sn = sn.parse().dcontext(&sn)?;
- self.sections.entry(sn)
- .or_default()
- .extend(
- vars.into_iter()
- .map(|(k,raw)| {
- (k.replace('-',"_"),
- RawVal { raw, loc: loc.clone() })
- })
- );
+
+ for key in vars.keys() {
+ let skl = self.keys_allowed.get(key.as_str()).ok_or_else(
+ || anyhow!("unknown configuration key {:?}", key)
+ )?;
+ if ! skl.contains(&sn) {
+ throw!(anyhow!("configuration key {:?} not applicable \
+ in this kind of section {:?}", key, &sn))
+ }
+ }
+
+ let ent = self.sections.entry(sn).or_default();
+ for (key, raw) in vars {
+ let raw = match raw {
+ Some(raw) if raw.starts_with('\'') || raw.starts_with('"') => Some(
+ (||{
+ if raw.contains('\\') {
+ throw!(
+ anyhow!("quoted value contains backslash, not supported")
+ );
+ }
+ let unq = raw[1..].strip_suffix(&raw[0..1])
+ .ok_or_else(
+ || anyhow!("mismatched quotes around quoted value")
+ )?
+ .to_owned();
+ Ok::<_,AE>(unq)
+ })()
+ .with_context(|| format!("key {:?}", key))
+ .dcontext(path_for_loc)?
+ ),
+ x => x,
+ };
+ let key = key.replace('-',"_");
+ ent.insert(key, RawVal { raw, loc: loc.clone() });
+ }
}
-
}
#[throws(AE)] // AE includes path
#[throws(AE)]
pub fn client<T>(&self, key: &'static str) -> T
- where T: Parseable {
+ where T: Parseable + Default {
match self.end {
LinkEnd::Client => self.ordinary(key)?,
- LinkEnd::Server => Parseable::default_for_key(key)?,
+ LinkEnd::Server => default(),
}
}
#[throws(AE)]
pub fn server<T>(&self, key: &'static str) -> T
- where T: Parseable {
+ where T: Parseable + Default {
match self.end {
LinkEnd::Server => self.ordinary(key)?,
- LinkEnd::Client => Parseable::default_for_key(key)?,
+ LinkEnd::Client => default(),
}
}
}
#[throws(AE)]
- pub fn special_server(&self, key: &'static str) -> ServerName {
- self.link.server.clone()
+ pub fn special_link(&self, _key: &'static str) -> LinkName {
+ self.link.clone()
+ }
+}
+
+impl InstanceConfig {
+ #[throws(AE)]
+ fn complete(&mut self, end: LinkEnd) {
+ let mut vhosts = self.vnetwork.iter()
+ .map(|n| n.hosts()).flatten()
+ .filter({ let vaddr = self.vaddr; move |v| v != &vaddr });
+
+ if self.vaddr.is_unspecified() {
+ self.vaddr = vhosts.next().ok_or_else(
+ || anyhow!("vnetwork too small to generate vaddrr")
+ )?;
+ }
+ if self.vrelay.is_unspecified() {
+ self.vrelay = vhosts.next().ok_or_else(
+ || anyhow!("vnetwork too small to generate vrelay")
+ )?;
+ }
+
+ match end {
+ LinkEnd::Client => {
+ if &self.url == &default::<Uri>() {
+ let addr = self.addrs.get(0).ok_or_else(
+ || anyhow!("client needs addrs or url set")
+ )?;
+ self.url = format!(
+ "http://{}{}/",
+ match addr {
+ IpAddr::V4(a) => format!("{}", a),
+ IpAddr::V6(a) => format!("[{}]", a),
+ },
+ match self.port {
+ 80 => format!(""),
+ p => format!(":{}", p),
+ })
+ .parse().unwrap()
+ }
+ },
+
+ LinkEnd::Server => {
+ if self.addrs.is_empty() {
+ throw!(anyhow!("missing 'addrs' setting"))
+ }
+ },
+ }
+
+ #[throws(AE)]
+ fn subst(var: &mut String,
+ kv: &mut dyn Iterator<Item=(&'static str, &dyn Display)>
+ ) {
+ let substs = kv
+ .map(|(k,v)| (k.to_string(), v.to_string()))
+ .collect::<HashMap<String, String>>();
+ let bad = parking_lot::Mutex::new(vec![]);
+ *var = regex_replace_all!(
+ r#"%(?:%|\((\w+)\)s|\{(\w+)\}|.)"#,
+ &var,
+ |whole, k1, k2| (|| Ok::<_,String>({
+ if whole == "%%" { "%" }
+ else if let Some(&k) = [k1,k2].iter().find(|&&s| s != "") {
+ substs.get(k).ok_or_else(
+ || format!("unknown key %({})s", k)
+ )?
+ } else {
+ throw!(format!("bad percent escape {:?}", &whole));
+ }
+ }))().unwrap_or_else(|e| { bad.lock().push(e); "" })
+ ).into_owned();
+ let bad = bad.into_inner();
+ if ! bad.is_empty() {
+ throw!(anyhow!("substitution failed: {}", bad.iter().format("; ")));
+ }
+ }
+
+ {
+ use LinkEnd::*;
+ type DD<'d> = &'d dyn Display;
+ fn dv<T:Display>(v: &[T]) -> String {
+ format!("{}", v.iter().format(" "))
+ }
+ let mut ipif = mem::take(&mut self.ipif); // lets us borrow all of self
+ let s = &self; // just for abbreviation, below
+ let vnetwork = dv(&s.vnetwork);
+ let vroutes = dv(&s.vroutes);
+
+ let keys = &["local", "peer", "rnets", "ifname"];
+ let values = match end {
+ Server => [&s.vaddr as DD , &s.vrelay, &vnetwork, &s.ifname_server],
+ Client => [&s.link.client as DD, &s.vaddr, &vroutes, &s.ifname_client],
+ };
+ let always = [
+ ( "mtu", &s.mtu as DD ),
+ ];
+
+ subst(
+ &mut ipif,
+ &mut keys.iter().cloned()
+ .zip_eq(values)
+ .chain(always.iter().cloned()),
+ ).context("ipif")?;
+ self.ipif = ipif;
+ }
}
}
let agg = (||{
let mut agg = Aggregate::default();
+ agg.keys_allowed.extend(
+ InstanceConfig::FIELDS.iter().cloned()
+ );
agg.read_string(DEFAULT_CONFIG.into(),
"<build-in defaults>".as_ref()).unwrap();
agg.read_extra(extra).context("extra config")?;
}
- eprintln!("GOT {:#?}", agg);
+ //eprintln!("GOT {:#?}", agg);
Ok::<_,AE>(agg)
})().context("read configuration")?;
],
};
- let ic = InstanceConfig::resolve_instance(&rctx)
- .with_context(|| format!(r#"resolve config for "{:?}""#, &link))?;
+ let mut ic = InstanceConfig::resolve_instance(&rctx)
+ .with_context(|| format!("resolve config for {}", &link))?;
+
+ ic.complete(end)
+ .with_context(|| format!("complete config for {}", &link))?;
ics.push(ic);
}