use std::net::{IpAddr, SocketAddr};
use wire::AuthToken;
#[derive(Debug, Clone)]
pub struct ClientConfig {
pub client_id: String,
pub token: Option<AuthToken>,
pub auth_proof_key_pem: Option<String>,
pub authenticated_principal: Option<String>,
pub server_key: Option<String>,
pub tls_enabled: bool,
pub tls_domain_name: Option<String>,
pub tls_ca_certificate_pem: Option<String>,
pub tls_skip_verify: bool,
pub allow_insecure: bool,
pub timeout_secs: u64,
pub compression: bool,
pub chunk_size: usize,
pub chunked_transfer: bool,
pub resumable_transfer: bool,
pub pack_transfer: bool,
pub partial_fetch: bool,
}
impl ClientConfig {
pub fn new(client_id: impl Into<String>) -> Self {
Self {
client_id: client_id.into(),
token: None,
auth_proof_key_pem: None,
authenticated_principal: None,
server_key: None,
tls_enabled: false,
tls_domain_name: None,
tls_ca_certificate_pem: None,
tls_skip_verify: false,
allow_insecure: false,
timeout_secs: 30,
compression: true,
chunk_size: 64 * 1024,
chunked_transfer: true,
resumable_transfer: true,
pack_transfer: true,
partial_fetch: true,
}
}
pub fn with_token(mut self, token: AuthToken) -> Self {
self.token = Some(token);
self
}
pub fn with_auth_proof_key_pem(mut self, pem: impl Into<String>) -> Self {
self.auth_proof_key_pem = Some(pem.into());
self
}
pub fn with_authenticated_principal(mut self, principal: impl Into<String>) -> Self {
self.authenticated_principal = Some(principal.into());
self
}
pub fn with_server_key(mut self, key: impl Into<String>) -> Self {
self.server_key = Some(key.into());
self
}
pub fn with_tls(mut self, skip_verify: bool) -> Self {
self.tls_enabled = true;
self.tls_skip_verify = skip_verify;
self
}
pub fn with_tls_domain_name(mut self, domain_name: impl Into<String>) -> Self {
self.tls_domain_name = Some(domain_name.into());
self
}
pub fn with_tls_ca_certificate_pem(mut self, pem: impl Into<String>) -> Self {
self.tls_enabled = true;
self.tls_ca_certificate_pem = Some(pem.into());
self
}
pub fn with_timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
pub fn without_compression(mut self) -> Self {
self.compression = false;
self
}
pub fn with_chunk_size(mut self, size: usize) -> Self {
self.chunk_size = size;
self
}
pub fn with_chunked_transfer(mut self, enabled: bool) -> Self {
self.chunked_transfer = enabled;
self
}
pub fn with_resumable_transfer(mut self, enabled: bool) -> Self {
self.resumable_transfer = enabled;
self
}
pub fn with_pack_transfer(mut self, enabled: bool) -> Self {
self.pack_transfer = enabled;
self
}
pub fn with_partial_fetch(mut self, enabled: bool) -> Self {
self.partial_fetch = enabled;
self
}
pub fn with_allow_insecure(mut self, allow: bool) -> Self {
self.allow_insecure = allow;
self
}
}
impl Default for ClientConfig {
fn default() -> Self {
Self::new("heddle-client")
}
}
pub fn is_loopback_ip(ip: IpAddr) -> bool {
ip.is_loopback()
}
pub fn cleartext_connect_allowed(
addr: SocketAddr,
tls_enabled: bool,
allow_insecure: bool,
) -> bool {
tls_enabled || is_loopback_ip(addr.ip()) || allow_insecure
}
pub fn cleartext_refused_message(addr: SocketAddr) -> String {
format!(
"refusing cleartext connection to non-loopback address {addr}; \
enable TLS (remote.tls_enabled / HEDDLE_REMOTE_TLS) or pass --insecure \
(or set remote.insecure=true / HEDDLE_REMOTE_INSECURE=1) for intentional cleartext \
(e.g. VPN → VPS testing)"
)
}
#[cfg(test)]
mod tests {
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use super::*;
#[test]
fn loopback_cleartext_allowed_without_insecure() {
let v4 = SocketAddr::from((Ipv4Addr::LOCALHOST, 8421));
let v6 = SocketAddr::from((Ipv6Addr::LOCALHOST, 8421));
assert!(cleartext_connect_allowed(v4, false, false));
assert!(cleartext_connect_allowed(v6, false, false));
}
#[test]
fn non_loopback_cleartext_requires_insecure() {
let addr = SocketAddr::from((Ipv4Addr::new(203, 0, 113, 10), 8421));
assert!(!cleartext_connect_allowed(addr, false, false));
assert!(cleartext_connect_allowed(addr, false, true));
assert!(cleartext_connect_allowed(addr, true, false));
}
#[test]
fn is_loopback_classifies_hosts() {
assert!(is_loopback_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
assert!(is_loopback_ip(IpAddr::V6(Ipv6Addr::LOCALHOST)));
assert!(!is_loopback_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));
}
}