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 {
35 Self {
36 max_connections: 16,
37 max_registrations: 64,
38 max_service_watches_per_connection: 1024,
39 max_parallel_operations: 4,
40 max_watch_streams: 64,
41 max_instances_per_service: 4096,
42 max_total_instances: 65536,
43 max_snapshot_bytes: 64 * 1024 * 1024,
44 max_transport_tasks: 4096,
45 }
46 }
47}
48
49impl Limits {
50 pub fn validate(&self) -> Result<()> {
54 for value in [
55 self.max_connections,
56 self.max_registrations,
57 self.max_service_watches_per_connection,
58 self.max_parallel_operations,
59 self.max_watch_streams,
60 self.max_instances_per_service,
61 self.max_total_instances,
62 self.max_snapshot_bytes,
63 self.max_transport_tasks,
64 ] {
65 if value == 0 || value > u32::MAX as usize {
66 return Err(invalid("capacity is outside the supported range"));
67 }
68 }
69 if self.max_instances_per_service > self.max_total_instances {
70 return Err(invalid("per-service capacity exceeds total capacity"));
71 }
72 if self.max_connections > 256
73 || self.max_registrations > 65536
74 || self.max_service_watches_per_connection > 65536
75 || self.max_parallel_operations > 4096
76 || self.max_transport_tasks > 65536
77 || self.max_watch_streams > 4096
78 || self
79 .max_connections
80 .saturating_mul(self.max_service_watches_per_connection)
81 > 65536
82 {
83 return Err(invalid(
84 "task or connection capacity exceeds the hard limit",
85 ));
86 }
87 Ok(())
88 }
89}
90
91#[derive(Clone, Debug, Deserialize, Serialize)]
93#[serde(default, deny_unknown_fields)]
94pub struct ConnectionPolicy {
95 pub startup_required: bool,
97 pub request_timeout_ms: u64,
99 pub poll_interval_ms: u64,
101 pub max_age_ms: u64,
103 pub stale_if_error_ms: u64,
105 pub idle_timeout_ms: u64,
107 pub initial_backoff_ms: u64,
109 pub max_backoff_ms: u64,
111}
112
113impl Default for ConnectionPolicy {
114 fn default() -> Self {
118 Self {
119 startup_required: true,
120 request_timeout_ms: 3000,
121 poll_interval_ms: 5000,
122 max_age_ms: 15000,
123 stale_if_error_ms: 0,
124 idle_timeout_ms: 30000,
125 initial_backoff_ms: 1000,
126 max_backoff_ms: 30000,
127 }
128 }
129}
130
131impl ConnectionPolicy {
132 pub fn validate(&self) -> Result<()> {
136 for value in [
137 self.request_timeout_ms,
138 self.poll_interval_ms,
139 self.max_age_ms,
140 self.idle_timeout_ms,
141 self.initial_backoff_ms,
142 self.max_backoff_ms,
143 ] {
144 if value == 0 || value > 86_400_000 {
145 return Err(invalid("time budget is outside the supported range"));
146 }
147 }
148 if self.stale_if_error_ms > self.max_age_ms
149 || self.poll_interval_ms + self.request_timeout_ms >= self.max_age_ms
150 || self.initial_backoff_ms > self.max_backoff_ms
151 {
152 return Err(invalid("observation and retry budgets are inconsistent"));
153 }
154 Ok(())
155 }
156}
157
158#[derive(Clone, Debug, Deserialize, Serialize)]
160#[serde(default, deny_unknown_fields)]
161pub struct RegistrationPolicy {
162 pub startup_required: bool,
164 pub readiness_critical: bool,
166 pub monitor_interval_ms: u64,
168 pub observation_grace_ms: u64,
170 pub deregister_timeout_ms: u64,
172}
173
174impl Default for RegistrationPolicy {
175 fn default() -> Self {
179 Self {
180 startup_required: true,
181 readiness_critical: false,
182 monitor_interval_ms: 5000,
183 observation_grace_ms: 15000,
184 deregister_timeout_ms: 3000,
185 }
186 }
187}
188
189impl RegistrationPolicy {
190 pub fn validate(&self, connection: &ConnectionPolicy) -> Result<()> {
194 if self.monitor_interval_ms == 0
195 || self.deregister_timeout_ms == 0
196 || self.monitor_interval_ms > 86_400_000
197 || self.deregister_timeout_ms > 86_400_000
198 || self.observation_grace_ms > 86_400_000
199 || self.monitor_interval_ms + connection.request_timeout_ms >= self.observation_grace_ms
200 {
201 return Err(invalid("registration observation budgets are inconsistent"));
202 }
203 Ok(())
204 }
205}
206
207pub fn validate_name(name: &str) -> Result<()> {
211 let bytes = name.as_bytes();
212 if bytes.is_empty()
213 || bytes.len() > 64
214 || !bytes[0].is_ascii_alphanumeric()
215 || !bytes
216 .iter()
217 .all(|b| b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-')
218 {
219 return Err(invalid("invalid local discovery name"));
220 }
221 Ok(())
222}
223
224pub(crate) fn invalid(reason: &'static str) -> DiscoveryError {
228 DiscoveryError::new(ErrorCode::InvalidConfig, reason)
229}