--- /dev/null
+// Copyright 2021 Ian Jackson and contributors to Hippotat
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// There is NO WARRANTY.
+
+use crate::prelude::*;
+
+#[derive(Debug,Clone)]
+struct ServerName(pub String);
+
+#[derive(Debug,Clone,Copy)]
+struct ClientName(pub Ipv4Addr);
+
+#[derive(Debug,Clone)]
+enum SectionName {
+ Link { server: ServerName, client: ClientName },
+ Client(ClientName),
+ Server(ServerName), // includes SERVER, which is slightly special
+ Common,
+ Default,
+}
+pub use SectionName as SN;
+
+impl FromStr for ClientName {
+ type Err = AE;
+ #[throws(AE)]
+ fn from_str(s: &str) -> Self {
+ let v4addr = s.parse().context("invalid client name (IPv4 address)")?;
+ if s != v4addr.to_string() {
+ throw!(anyhow!("invalid client name (unusual IPv4 address syntax)"));
+ }
+ ClientName(v4addr)
+ }
+}
+
+impl FromStr for ServerName {
+ type Err = AE;
+ #[throws(AE)]
+ fn from_str(s: &str) -> Self {
+ if let Some(bad) = s.chars.find(|c| !{
+ c.is_ascii_alphanumeric() || c=='.' || c=='-'
+ }) {
+ throw!(anyhow!("invalid server name: bad character {:?}", c));
+ }
+ if ! s.split('.').all(
+ |d| matches!(d.chars().next(),
+ Some(c) if c.is_ascii_alphanumeric() )) {
+ throw!(anyhow!("invalid server name: must be valid domain name"));
+ }
+ if s == SERVER
+
+ d == "." ||
+
+ ClientName(s.parse().context(
+ "failed to parse client name (IPv4 address)"
+ )?)
+ }
+}
+
+impl FromStr for SectionName {
+ type Err = AE;
+ #[throws(AE)]
+ fn from_str(s: &str) -> Self {
+ match s {
+ "COMMON" => return SN::Common,
+ "DEFAULT" => return SN::Default,
+ };
+
+ )) {
+ // looks like a domain name or IP address
+ if let Ok(v4addr) = s.parse() {
+ return SN::Client(ClientName(v4addr))
+ } else if (s.chars().any(|c| c==':')) {
+ throw!(anyhow!("colons not allowed in section names \
+ (IPv6 transport is not supported)"));
+ } else {
+ return SN::Server(
+
+ }