1use nadisc::managed::{DiscoveryError, ErrorCode, Result};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Deserialize, Serialize)]
8#[serde(default, deny_unknown_fields)]
9pub struct Limits {
10 pub max_connections: usize,
12 pub max_registrations: usize,
14 pub max_service_watches_per_connection: usize,
16 pub max_parallel_operations: usize,
18 pub max_watch_streams: usize,
20 pub max_instances_per_service: usize,
22 pub max_total_instances: usize,
24 pub max_snapshot_bytes: usize,
26 pub max_transport_tasks: usize,
28}
29
30impl Default for Limits {
31 fn default() -> Self {
37 Self {
38 max_connections: 16,
39 max_registrations: 64,
40 max_service_watches_per_connection: 1024,
41 max_parallel_operations: 4,
42 max_watch_streams: 64,
43 max_instances_per_service: 4096,
44 max_total_instances: 65536,
45 max_snapshot_bytes: 64 * 1024 * 1024,
46 max_transport_tasks: 4096,
47 }
48 }
49}
50
51impl Limits {
52 pub fn validate(&self) -> Result<()> {
58 for value in [
59 self.max_connections,
60 self.max_registrations,
61 self.max_service_watches_per_connection,
62 self.max_parallel_operations,
63 self.max_watch_streams,
64 self.max_instances_per_service,
65 self.max_total_instances,
66 self.max_snapshot_bytes,
67 self.max_transport_tasks,
68 ] {
69 if value == 0 || value > u32::MAX as usize {
70 return Err(invalid("capacity is outside the supported range"));
71 }
72 }
73 if self.max_instances_per_service > self.max_total_instances {
74 return Err(invalid("per-service capacity exceeds total capacity"));
75 }
76 if self.max_connections > 256
77 || self.max_registrations > 65536
78 || self.max_service_watches_per_connection > 65536
79 || self.max_parallel_operations > 4096
80 || self.max_transport_tasks > 65536
81 || self.max_watch_streams > 4096
82 || self
83 .max_connections
84 .saturating_mul(self.max_service_watches_per_connection)
85 > 65536
86 {
87 return Err(invalid(
88 "task or connection capacity exceeds the hard limit",
89 ));
90 }
91 Ok(())
92 }
93}
94
95#[derive(Clone, Debug, Deserialize, Serialize)]
97#[serde(default, deny_unknown_fields)]
98pub struct ConnectionPolicy {
99 pub startup_required: bool,
101 pub request_timeout_ms: u64,
103 pub poll_interval_ms: u64,
105 pub max_age_ms: u64,
107 pub stale_if_error_ms: u64,
109 pub idle_timeout_ms: u64,
111 pub initial_backoff_ms: u64,
113 pub max_backoff_ms: u64,
115}
116
117impl Default for ConnectionPolicy {
118 fn default() -> Self {
124 Self {
125 startup_required: true,
126 request_timeout_ms: 3000,
127 poll_interval_ms: 5000,
128 max_age_ms: 15000,
129 stale_if_error_ms: 0,
130 idle_timeout_ms: 30000,
131 initial_backoff_ms: 1000,
132 max_backoff_ms: 30000,
133 }
134 }
135}
136
137impl ConnectionPolicy {
138 pub fn validate(&self) -> Result<()> {
144 for value in [
145 self.request_timeout_ms,
146 self.poll_interval_ms,
147 self.max_age_ms,
148 self.idle_timeout_ms,
149 self.initial_backoff_ms,
150 self.max_backoff_ms,
151 ] {
152 if value == 0 || value > 86_400_000 {
153 return Err(invalid("time budget is outside the supported range"));
154 }
155 }
156 if self.stale_if_error_ms > self.max_age_ms
157 || self.poll_interval_ms + self.request_timeout_ms >= self.max_age_ms
158 || self.initial_backoff_ms > self.max_backoff_ms
159 {
160 return Err(invalid("observation and retry budgets are inconsistent"));
161 }
162 Ok(())
163 }
164}
165
166#[derive(Clone, Debug, Deserialize, Serialize)]
168#[serde(default, deny_unknown_fields)]
169pub struct RegistrationPolicy {
170 pub startup_required: bool,
172 pub readiness_critical: bool,
174 pub monitor_interval_ms: u64,
176 pub observation_grace_ms: u64,
178 pub deregister_timeout_ms: u64,
180}
181
182impl Default for RegistrationPolicy {
183 fn default() -> Self {
189 Self {
190 startup_required: true,
191 readiness_critical: false,
192 monitor_interval_ms: 5000,
193 observation_grace_ms: 15000,
194 deregister_timeout_ms: 3000,
195 }
196 }
197}
198
199impl RegistrationPolicy {
200 pub fn validate(&self, connection: &ConnectionPolicy) -> Result<()> {
207 if self.monitor_interval_ms == 0
208 || self.deregister_timeout_ms == 0
209 || self.monitor_interval_ms > 86_400_000
210 || self.deregister_timeout_ms > 86_400_000
211 || self.observation_grace_ms > 86_400_000
212 || self.monitor_interval_ms + connection.request_timeout_ms >= self.observation_grace_ms
213 {
214 return Err(invalid("registration observation budgets are inconsistent"));
215 }
216 Ok(())
217 }
218}
219
220pub fn validate_name(name: &str) -> Result<()> {
227 let bytes = name.as_bytes();
228 if bytes.is_empty()
229 || bytes.len() > 64
230 || !bytes[0].is_ascii_alphanumeric()
231 || !bytes
232 .iter()
233 .all(|b| b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-')
234 {
235 return Err(invalid("invalid local discovery name"));
236 }
237 Ok(())
238}
239
240pub(crate) fn invalid(reason: &'static str) -> DiscoveryError {
247 DiscoveryError::new(ErrorCode::InvalidConfig, reason)
248}