1use std::{cmp::Ordering, collections::BTreeMap, fmt, net::SocketAddr};
2
3use crate::{
4 proto::command::{
5 AddBackend, FilteredTimeSerie, Header, HstsConfig, LoadBalancingParams, PathRule,
6 PathRuleKind, RequestHttpFrontend, RequestTcpFrontend, RequestUdpFrontend, Response,
7 ResponseContent, ResponseStatus, RulePosition, RunState, WorkerResponse,
8 },
9 state::ClusterId,
10};
11
12impl Response {
13 pub fn new(
14 status: ResponseStatus,
15 message: String,
16 content: Option<ResponseContent>,
17 ) -> Response {
18 Response {
19 status: status as i32,
20 message,
21 content,
22 }
23 }
24}
25
26#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub struct HttpFrontend {
29 pub cluster_id: Option<ClusterId>,
31 pub address: SocketAddr,
32 pub hostname: String,
33 #[serde(default)]
34 #[serde(skip_serializing_if = "is_default_path_rule")]
35 pub path: PathRule,
36 #[serde(default)]
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub method: Option<String>,
39 #[serde(default)]
40 pub position: RulePosition,
41 pub tags: Option<BTreeMap<String, String>>,
42 #[serde(default)]
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub redirect: Option<i32>,
50 #[serde(default)]
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub redirect_scheme: Option<i32>,
53 #[serde(default)]
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub redirect_template: Option<String>,
56 #[serde(default)]
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub rewrite_host: Option<String>,
59 #[serde(default)]
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub rewrite_path: Option<String>,
62 #[serde(default)]
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub rewrite_port: Option<u32>,
65 #[serde(default)]
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub required_auth: Option<bool>,
68 #[serde(default)]
69 #[serde(skip_serializing_if = "Vec::is_empty")]
70 pub headers: Vec<Header>,
71 #[serde(default)]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub hsts: Option<HstsConfig>,
76}
77
78impl fmt::Debug for HttpFrontend {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let tags_count = self.tags.as_ref().map(BTreeMap::len);
81 let tags_len = self.tags.as_ref().map(|tags| {
82 tags.iter().fold(0usize, |total, (key, value)| {
83 total.saturating_add(key.len()).saturating_add(value.len())
84 })
85 });
86 let headers_len = self.headers.iter().fold(0usize, |total, header| {
87 total
88 .saturating_add(header.key.len())
89 .saturating_add(header.val.len())
90 });
91
92 f.debug_struct("HttpFrontend")
93 .field("cluster_id_len", &self.cluster_id.as_ref().map(String::len))
94 .field("address", &self.address)
95 .field("hostname_len", &self.hostname.len())
96 .field("path_kind", &self.path.kind)
97 .field("path_len", &self.path.value.len())
98 .field("method_len", &self.method.as_ref().map(String::len))
99 .field("position", &self.position)
100 .field("tags_count", &tags_count)
101 .field("tags_len", &tags_len)
102 .field("redirect", &self.redirect)
103 .field("required_auth", &self.required_auth)
104 .field("redirect_scheme", &self.redirect_scheme)
105 .field(
106 "redirect_template_len",
107 &self.redirect_template.as_ref().map(String::len),
108 )
109 .field(
110 "rewrite_host_len",
111 &self.rewrite_host.as_ref().map(String::len),
112 )
113 .field(
114 "rewrite_path_len",
115 &self.rewrite_path.as_ref().map(String::len),
116 )
117 .field("rewrite_port", &self.rewrite_port)
118 .field("headers_count", &self.headers.len())
119 .field("headers_len", &headers_len)
120 .field("hsts", &self.hsts)
121 .finish_non_exhaustive()
122 }
123}
124
125impl From<HttpFrontend> for RequestHttpFrontend {
126 fn from(val: HttpFrontend) -> Self {
127 let source_address = val.address;
128 let source_hostname = val.hostname.clone();
129 let request_frontend = RequestHttpFrontend {
130 cluster_id: val.cluster_id,
131 address: val.address.into(),
132 hostname: val.hostname,
133 path: val.path,
134 method: val.method,
135 position: val.position.into(),
136 tags: val.tags.unwrap_or_default(),
137 redirect: val.redirect,
138 redirect_scheme: val.redirect_scheme,
139 redirect_template: val.redirect_template,
140 rewrite_host: val.rewrite_host,
141 rewrite_path: val.rewrite_path,
142 rewrite_port: val.rewrite_port,
143 required_auth: val.required_auth,
144 headers: val.headers,
145 hsts: val.hsts,
146 };
147
148 debug_assert_eq!(
153 SocketAddr::from(request_frontend.address),
154 source_address,
155 "frontend address must round-trip through the proto encoding"
156 );
157 debug_assert_eq!(
158 request_frontend.hostname, source_hostname,
159 "frontend hostname must survive the proto conversion"
160 );
161 request_frontend
162 }
163}
164
165impl From<Backend> for AddBackend {
166 fn from(val: Backend) -> Self {
167 let source_address = val.address;
168 let source_cluster_id = val.cluster_id.clone();
169 let source_backend_id = val.backend_id.clone();
170 let add_backend = AddBackend {
171 cluster_id: val.cluster_id,
172 backend_id: val.backend_id,
173 address: val.address.into(),
174 sticky_id: val.sticky_id,
175 load_balancing_parameters: val.load_balancing_parameters,
176 backup: val.backup,
177 };
178
179 debug_assert_eq!(
184 add_backend.cluster_id, source_cluster_id,
185 "backend cluster_id must survive the proto conversion"
186 );
187 debug_assert_eq!(
188 add_backend.backend_id, source_backend_id,
189 "backend_id must survive the proto conversion"
190 );
191 debug_assert_eq!(
192 SocketAddr::from(add_backend.address),
193 source_address,
194 "backend address must round-trip through the proto encoding"
195 );
196 add_backend
197 }
198}
199
200impl PathRule {
201 pub fn prefix<S>(value: S) -> Self
202 where
203 S: ToString,
204 {
205 let rule = Self {
206 kind: PathRuleKind::Prefix.into(),
207 value: value.to_string(),
208 };
209 debug_assert_eq!(
212 PathRuleKind::try_from(rule.kind),
213 Ok(PathRuleKind::Prefix),
214 "prefix() must encode a Prefix-kind rule"
215 );
216 rule
217 }
218
219 pub fn regex<S>(value: S) -> Self
220 where
221 S: ToString,
222 {
223 let rule = Self {
224 kind: PathRuleKind::Regex.into(),
225 value: value.to_string(),
226 };
227 debug_assert_eq!(
228 PathRuleKind::try_from(rule.kind),
229 Ok(PathRuleKind::Regex),
230 "regex() must encode a Regex-kind rule"
231 );
232 rule
233 }
234
235 pub fn equals<S>(value: S) -> Self
236 where
237 S: ToString,
238 {
239 let rule = Self {
240 kind: PathRuleKind::Equals.into(),
241 value: value.to_string(),
242 };
243 debug_assert_eq!(
244 PathRuleKind::try_from(rule.kind),
245 Ok(PathRuleKind::Equals),
246 "equals() must encode an Equals-kind rule"
247 );
248 rule
249 }
250
251 pub fn from_cli_options(
252 path_prefix: Option<String>,
253 path_regex: Option<String>,
254 path_equals: Option<String>,
255 ) -> Self {
256 let had_prefix = path_prefix.is_some();
260 let had_regex = path_regex.is_some();
261 let rule = match (path_prefix, path_regex, path_equals) {
262 (Some(prefix), _, _) => PathRule {
263 kind: PathRuleKind::Prefix as i32,
264 value: prefix,
265 },
266 (None, Some(regex), _) => PathRule {
267 kind: PathRuleKind::Regex as i32,
268 value: regex,
269 },
270 (None, None, Some(equals)) => PathRule {
271 kind: PathRuleKind::Equals as i32,
272 value: equals,
273 },
274 _ => PathRule::default(),
275 };
276
277 debug_assert!(
281 !had_prefix || rule.kind == PathRuleKind::Prefix as i32,
282 "a path prefix must produce a Prefix rule regardless of other flags"
283 );
284 debug_assert!(
285 had_prefix || !had_regex || rule.kind == PathRuleKind::Regex as i32,
286 "absent a prefix, a regex must produce a Regex rule"
287 );
288 rule
289 }
290}
291
292pub fn is_default_path_rule(p: &PathRule) -> bool {
293 PathRuleKind::try_from(p.kind) == Ok(PathRuleKind::Prefix) && p.value.is_empty()
294}
295
296impl fmt::Display for PathRule {
297 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
298 match PathRuleKind::try_from(self.kind) {
299 Ok(PathRuleKind::Prefix) => write!(f, "prefix '{}'", self.value),
300 Ok(PathRuleKind::Regex) => write!(f, "regexp '{}'", self.value),
301 Ok(PathRuleKind::Equals) => write!(f, "equals '{}'", self.value),
302 Err(_) => write!(f, ""),
303 }
304 }
305}
306
307#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
309pub struct TcpFrontend {
310 pub cluster_id: String,
311 pub address: SocketAddr,
312 pub tags: BTreeMap<String, String>,
314 #[serde(default)]
317 pub sni: Option<String>,
318 #[serde(default)]
321 pub alpn: Vec<String>,
322}
323
324impl From<TcpFrontend> for RequestTcpFrontend {
325 fn from(val: TcpFrontend) -> Self {
326 let source_address = val.address;
327 let source_cluster_id = val.cluster_id.clone();
328 let source_sni = val.sni.clone();
329 let source_alpn = val.alpn.clone();
330 let request_frontend = RequestTcpFrontend {
331 cluster_id: val.cluster_id,
332 address: val.address.into(),
333 tags: val.tags,
334 sni: val.sni,
335 alpn: val.alpn,
336 };
337
338 debug_assert_eq!(
341 request_frontend.cluster_id, source_cluster_id,
342 "TCP frontend cluster_id must survive the proto conversion"
343 );
344 debug_assert_eq!(
345 SocketAddr::from(request_frontend.address),
346 source_address,
347 "TCP frontend address must round-trip through the proto encoding"
348 );
349 debug_assert_eq!(
353 request_frontend.sni, source_sni,
354 "TCP frontend sni must survive the proto conversion"
355 );
356 debug_assert_eq!(
357 request_frontend.alpn, source_alpn,
358 "TCP frontend alpn must survive the proto conversion"
359 );
360 request_frontend
361 }
362}
363
364#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
366pub struct UdpFrontend {
367 pub cluster_id: String,
368 pub address: SocketAddr,
369 pub tags: BTreeMap<String, String>,
371}
372
373impl From<UdpFrontend> for RequestUdpFrontend {
374 fn from(val: UdpFrontend) -> Self {
375 RequestUdpFrontend {
376 cluster_id: val.cluster_id,
377 address: val.address.into(),
378 tags: val.tags,
379 }
380 }
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
385pub struct Backend {
386 pub cluster_id: String,
387 pub backend_id: String,
388 pub address: SocketAddr,
389 #[serde(default)]
390 #[serde(skip_serializing_if = "Option::is_none")]
391 pub sticky_id: Option<String>,
392 #[serde(default)]
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub load_balancing_parameters: Option<LoadBalancingParams>,
395 #[serde(default)]
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub backup: Option<bool>,
398}
399
400impl Ord for Backend {
401 fn cmp(&self, o: &Backend) -> Ordering {
402 let fields_all_equal = self.cluster_id == o.cluster_id
408 && self.backend_id == o.backend_id
409 && self.sticky_id == o.sticky_id
410 && self.load_balancing_parameters == o.load_balancing_parameters
411 && self.backup == o.backup
412 && self.address == o.address;
413
414 let ordering = self
415 .cluster_id
416 .cmp(&o.cluster_id)
417 .then(self.backend_id.cmp(&o.backend_id))
418 .then(self.sticky_id.cmp(&o.sticky_id))
419 .then(
420 self.load_balancing_parameters
421 .cmp(&o.load_balancing_parameters),
422 )
423 .then(self.backup.cmp(&o.backup))
424 .then(socketaddr_cmp(&self.address, &o.address));
425
426 debug_assert_eq!(
427 ordering == Ordering::Equal,
428 fields_all_equal,
429 "Backend::cmp returns Equal iff every keyed field is equal"
430 );
431 ordering
432 }
433}
434
435impl PartialOrd for Backend {
436 fn partial_cmp(&self, other: &Backend) -> Option<Ordering> {
437 Some(self.cmp(other))
438 }
439}
440
441impl Backend {
442 pub fn to_add_backend(self) -> AddBackend {
443 let source_address = self.address;
444 let source_backend_id = self.backend_id.clone();
445 let add_backend = AddBackend {
446 cluster_id: self.cluster_id,
447 address: self.address.into(),
448 sticky_id: self.sticky_id,
449 backend_id: self.backend_id,
450 load_balancing_parameters: self.load_balancing_parameters,
451 backup: self.backup,
452 };
453
454 debug_assert_eq!(
457 add_backend.backend_id, source_backend_id,
458 "backend_id must survive to_add_backend"
459 );
460 debug_assert_eq!(
461 SocketAddr::from(add_backend.address),
462 source_address,
463 "backend address must round-trip through to_add_backend"
464 );
465 add_backend
466 }
467}
468
469impl fmt::Display for RunState {
470 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471 write!(f, "{self:?}")
472 }
473}
474
475pub type MessageId = String;
476
477impl WorkerResponse {
478 pub fn ok<T>(id: T) -> Self
479 where
480 T: ToString,
481 {
482 Self {
483 id: id.to_string(),
484 message: String::new(),
485 status: ResponseStatus::Ok.into(),
486 content: None,
487 }
488 }
489
490 pub fn ok_with_content<T>(id: T, content: ResponseContent) -> Self
491 where
492 T: ToString,
493 {
494 Self {
495 id: id.to_string(),
496 status: ResponseStatus::Ok.into(),
497 message: String::new(),
498 content: Some(content),
499 }
500 }
501
502 pub fn error<T, U>(id: T, error: U) -> Self
503 where
504 T: ToString,
505 U: ToString,
506 {
507 Self {
508 id: id.to_string(),
509 message: error.to_string(),
510 status: ResponseStatus::Failure.into(),
511 content: None,
512 }
513 }
514
515 pub fn processing<T>(id: T) -> Self
516 where
517 T: ToString,
518 {
519 Self {
520 id: id.to_string(),
521 message: String::new(),
522 status: ResponseStatus::Processing.into(),
523 content: None,
524 }
525 }
526
527 pub fn with_status<T>(id: T, status: ResponseStatus) -> Self
528 where
529 T: ToString,
530 {
531 Self {
532 id: id.to_string(),
533 message: String::new(),
534 status: status.into(),
535 content: None,
536 }
537 }
538
539 pub fn is_failure(&self) -> bool {
540 self.status == ResponseStatus::Failure as i32
541 }
542}
543
544impl fmt::Display for WorkerResponse {
545 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
546 write!(f, "{}-{:?}", self.id, self.status)
547 }
548}
549
550impl fmt::Display for FilteredTimeSerie {
551 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
552 write!(
553 f,
554 "FilteredTimeSerie {{\nlast_second: {},\nlast_minute:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\nlast_hour:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n}}",
555 self.last_second,
556 &self.last_minute[0..10],
557 &self.last_minute[10..20],
558 &self.last_minute[20..30],
559 &self.last_minute[30..40],
560 &self.last_minute[40..50],
561 &self.last_minute[50..60],
562 &self.last_hour[0..10],
563 &self.last_hour[10..20],
564 &self.last_hour[20..30],
565 &self.last_hour[30..40],
566 &self.last_hour[40..50],
567 &self.last_hour[50..60]
568 )
569 }
570}
571
572fn socketaddr_cmp(a: &SocketAddr, b: &SocketAddr) -> Ordering {
573 let ordering = a.ip().cmp(&b.ip()).then(a.port().cmp(&b.port()));
574 debug_assert_eq!(
578 ordering == Ordering::Equal,
579 a.ip() == b.ip() && a.port() == b.port(),
580 "socketaddr_cmp is Equal iff ip and port both match"
581 );
582 ordering
583}