use nadisc::managed::{DiscoveryError, ErrorCode, Result};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Limits {
pub max_connections: usize,
pub max_registrations: usize,
pub max_service_watches_per_connection: usize,
pub max_parallel_operations: usize,
pub max_watch_streams: usize,
pub max_instances_per_service: usize,
pub max_total_instances: usize,
pub max_snapshot_bytes: usize,
pub max_transport_tasks: usize,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_connections: 16,
max_registrations: 64,
max_service_watches_per_connection: 1024,
max_parallel_operations: 4,
max_watch_streams: 64,
max_instances_per_service: 4096,
max_total_instances: 65536,
max_snapshot_bytes: 64 * 1024 * 1024,
max_transport_tasks: 4096,
}
}
}
impl Limits {
pub fn validate(&self) -> Result<()> {
for value in [
self.max_connections,
self.max_registrations,
self.max_service_watches_per_connection,
self.max_parallel_operations,
self.max_watch_streams,
self.max_instances_per_service,
self.max_total_instances,
self.max_snapshot_bytes,
self.max_transport_tasks,
] {
if value == 0 || value > u32::MAX as usize {
return Err(invalid("capacity is outside the supported range"));
}
}
if self.max_instances_per_service > self.max_total_instances {
return Err(invalid("per-service capacity exceeds total capacity"));
}
if self.max_connections > 256
|| self.max_registrations > 65536
|| self.max_service_watches_per_connection > 65536
|| self.max_parallel_operations > 4096
|| self.max_transport_tasks > 65536
|| self.max_watch_streams > 4096
|| self
.max_connections
.saturating_mul(self.max_service_watches_per_connection)
> 65536
{
return Err(invalid(
"task or connection capacity exceeds the hard limit",
));
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ConnectionPolicy {
pub startup_required: bool,
pub request_timeout_ms: u64,
pub poll_interval_ms: u64,
pub max_age_ms: u64,
pub stale_if_error_ms: u64,
pub idle_timeout_ms: u64,
pub initial_backoff_ms: u64,
pub max_backoff_ms: u64,
}
impl Default for ConnectionPolicy {
fn default() -> Self {
Self {
startup_required: true,
request_timeout_ms: 3000,
poll_interval_ms: 5000,
max_age_ms: 15000,
stale_if_error_ms: 0,
idle_timeout_ms: 30000,
initial_backoff_ms: 1000,
max_backoff_ms: 30000,
}
}
}
impl ConnectionPolicy {
pub fn validate(&self) -> Result<()> {
for value in [
self.request_timeout_ms,
self.poll_interval_ms,
self.max_age_ms,
self.idle_timeout_ms,
self.initial_backoff_ms,
self.max_backoff_ms,
] {
if value == 0 || value > 86_400_000 {
return Err(invalid("time budget is outside the supported range"));
}
}
if self.stale_if_error_ms > self.max_age_ms
|| self.poll_interval_ms + self.request_timeout_ms >= self.max_age_ms
|| self.initial_backoff_ms > self.max_backoff_ms
{
return Err(invalid("observation and retry budgets are inconsistent"));
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct RegistrationPolicy {
pub startup_required: bool,
pub readiness_critical: bool,
pub monitor_interval_ms: u64,
pub observation_grace_ms: u64,
pub deregister_timeout_ms: u64,
}
impl Default for RegistrationPolicy {
fn default() -> Self {
Self {
startup_required: true,
readiness_critical: false,
monitor_interval_ms: 5000,
observation_grace_ms: 15000,
deregister_timeout_ms: 3000,
}
}
}
impl RegistrationPolicy {
pub fn validate(&self, connection: &ConnectionPolicy) -> Result<()> {
if self.monitor_interval_ms == 0
|| self.deregister_timeout_ms == 0
|| self.monitor_interval_ms > 86_400_000
|| self.deregister_timeout_ms > 86_400_000
|| self.observation_grace_ms > 86_400_000
|| self.monitor_interval_ms + connection.request_timeout_ms >= self.observation_grace_ms
{
return Err(invalid("registration observation budgets are inconsistent"));
}
Ok(())
}
}
pub fn validate_name(name: &str) -> Result<()> {
let bytes = name.as_bytes();
if bytes.is_empty()
|| bytes.len() > 64
|| !bytes[0].is_ascii_alphanumeric()
|| !bytes
.iter()
.all(|b| b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-')
{
return Err(invalid("invalid local discovery name"));
}
Ok(())
}
pub(crate) fn invalid(reason: &'static str) -> DiscoveryError {
DiscoveryError::new(ErrorCode::InvalidConfig, reason)
}