1use super::actions::{self as act, GkCommand};
32use super::h225;
33use crate::llm::action_helper::call_llm;
34use crate::llm::actions::protocol_trait::ActionResult;
35use crate::llm::ollama_client::OllamaClient;
36use crate::logging::emit::Log;
37use crate::protocol::Event;
38use crate::server::connection::ConnectionId;
39use crate::state::app_state::AppState;
40use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
41use crate::state::ServerId;
42use crate::utils::clock::Instant;
43use anyhow::Result;
44use serde_json::{json, Value};
45use std::collections::{HashMap, HashSet, VecDeque};
46use std::net::{IpAddr, SocketAddr};
47use std::sync::{Arc, Mutex};
48use std::time::Duration;
49use tokio::net::UdpSocket;
50use tokio::sync::mpsc;
51
52pub const MAX_RAS_DATAGRAM: usize = 4096;
55pub const MAX_REGISTRATIONS: usize = 256;
57pub const MAX_CALLS: usize = 512;
59pub const MAX_IN_FLIGHT: usize = 64;
61const ANSWER_CACHE: usize = 256;
63const ANSWER_TTL: Duration = Duration::from_secs(30);
65const RIP_DELAY_MS: u64 = 15_000;
67pub const DEFAULT_TTL_SECS: u64 = 300;
69pub const MAX_TTL_SECS: u64 = 3600;
71const TTL_GRACE: Duration = Duration::from_secs(30);
73
74pub const ADMISSION_REASONS: &[&str] = &[
75 "calledPartyNotRegistered",
76 "invalidPermission",
77 "requestDenied",
78 "undefinedReason",
79 "callerNotRegistered",
80 "routeCallToGatekeeper",
81 "invalidEndpointIdentifier",
82 "resourceUnavailable",
83 "securityDenial",
84 "qosControlNotSupported",
85 "incompleteAddress",
86 "aliasesInconsistent",
87 "exceedsCallCapacity",
88 "collectDestination",
89 "collectPIN",
90 "noRouteToDestination",
91 "unallocatedNumber",
92];
93
94pub const REGISTRATION_REASONS: &[&str] = &[
95 "discoveryRequired",
96 "invalidRevision",
97 "invalidCallSignalAddress",
98 "invalidRASAddress",
99 "duplicateAlias",
100 "invalidTerminalType",
101 "undefinedReason",
102 "transportNotSupported",
103 "transportQOSNotSupported",
104 "resourceUnavailable",
105 "invalidAlias",
106 "securityDenial",
107 "fullRegistrationRequired",
108];
109
110pub const GATEKEEPER_REASONS: &[&str] = &[
111 "resourceUnavailable",
112 "terminalExcluded",
113 "invalidRevision",
114 "undefinedReason",
115 "securityDenial",
116];
117
118pub const LOCATION_REASONS: &[&str] = &[
119 "notRegistered",
120 "invalidPermission",
121 "requestDenied",
122 "undefinedReason",
123 "securityDenial",
124 "aliasesInconsistent",
125 "resourceUnavailable",
126 "hopCountExceeded",
127 "incompleteAddress",
128 "noRouteToDestination",
129 "unallocatedNumber",
130];
131
132#[derive(Clone, Debug)]
134pub struct GatekeeperConfig {
135 pub gatekeeper_id: String,
136 pub default_ttl: u64,
137 pub max_bandwidth: u64,
138}
139
140#[derive(Clone, Debug)]
141struct Registration {
142 endpoint_id: String,
143 aliases: Vec<Value>,
144 call_signal: Vec<Value>,
145 ras: Vec<Value>,
146 ttl: u64,
147 expires: Instant,
148 endpoint: Value,
149}
150
151#[derive(Clone, Debug)]
152struct AdmittedCall {
153 endpoint_id: String,
154 bandwidth: u64,
155}
156
157#[derive(Default)]
158struct State {
159 registrations: HashMap<String, Registration>,
160 calls: HashMap<String, AdmittedCall>,
161 next_endpoint: u64,
162 answers: VecDeque<((SocketAddr, u16), Vec<u8>, Instant)>,
163 in_flight: HashSet<(SocketAddr, u16)>,
164}
165
166impl State {
167 fn expire(&mut self) {
168 let now = Instant::now();
169 self.registrations
170 .retain(|_, r| r.expires + TTL_GRACE > now);
171 let live: HashSet<String> = self.registrations.keys().cloned().collect();
172 self.calls.retain(|_, c| live.contains(&c.endpoint_id));
173 while self
174 .answers
175 .front()
176 .is_some_and(|(_, _, at)| now.duration_since(*at) > ANSWER_TTL)
177 {
178 self.answers.pop_front();
179 }
180 }
181
182 fn remember(&mut self, key: (SocketAddr, u16), answer: Vec<u8>) {
183 if self.answers.len() >= ANSWER_CACHE {
184 self.answers.pop_front();
185 }
186 self.answers.push_back((key, answer, Instant::now()));
187 }
188
189 fn cached(&self, key: (SocketAddr, u16)) -> Option<Vec<u8>> {
190 self.answers
191 .iter()
192 .rev()
193 .find(|(k, _, _)| *k == key)
194 .map(|(_, a, _)| a.clone())
195 }
196
197 fn holder_of(&self, alias: &Value) -> Option<&Registration> {
199 self.registrations
200 .values()
201 .find(|r| r.aliases.iter().any(|a| h225::same_alias(a, alias)))
202 }
203
204 fn by_call_signal(&self, addrs: &[Value]) -> Option<String> {
205 self.registrations
206 .values()
207 .find(|r| r.call_signal.iter().any(|a| addrs.contains(a)))
208 .map(|r| r.endpoint_id.clone())
209 }
210}
211
212struct Shared {
213 socket: Arc<UdpSocket>,
214 local: SocketAddr,
215 config: GatekeeperConfig,
216 state: Mutex<State>,
217 app_state: Arc<AppState>,
218 llm_client: OllamaClient,
219 status_tx: mpsc::UnboundedSender<String>,
220 server_id: ServerId,
221}
222
223impl Shared {
224 fn state(&self) -> std::sync::MutexGuard<'_, State> {
225 self.state.lock().unwrap_or_else(|p| p.into_inner())
226 }
227
228 fn log(&self) -> Log<'_> {
229 Log::new(Some(&self.status_tx))
230 }
231
232 fn ras_address_for(&self, peer: SocketAddr) -> SocketAddr {
235 if !self.local.ip().is_unspecified() {
236 return self.local;
237 }
238 let ip = route_source(peer).unwrap_or(self.local.ip());
239 SocketAddr::new(ip, self.local.port())
240 }
241}
242
243pub fn route_source(peer: SocketAddr) -> Option<IpAddr> {
245 let bind: SocketAddr = if peer.is_ipv4() {
246 "0.0.0.0:0".parse().ok()?
247 } else {
248 "[::]:0".parse().ok()?
249 };
250 let s = std::net::UdpSocket::bind(bind).ok()?;
251 s.connect(peer).ok()?;
252 s.local_addr().ok().map(|a| a.ip())
253}
254
255pub async fn spawn(
256 listen_addr: SocketAddr,
257 llm_client: OllamaClient,
258 app_state: Arc<AppState>,
259 status_tx: mpsc::UnboundedSender<String>,
260 server_id: ServerId,
261 config: GatekeeperConfig,
262) -> Result<SocketAddr> {
263 let socket = Arc::new(UdpSocket::bind(listen_addr).await?);
264 let local = socket.local_addr()?;
265 Log::new(Some(&status_tx)).info(format!(
266 "H.323 gatekeeper {} (RAS) listening on {local}",
267 config.gatekeeper_id
268 ));
269 let shared = Arc::new(Shared {
270 socket: socket.clone(),
271 local,
272 config,
273 state: Mutex::new(State::default()),
274 app_state: app_state.clone(),
275 llm_client,
276 status_tx: status_tx.clone(),
277 server_id,
278 });
279 let registrar = app_state.clone();
280 let task = tokio::spawn(async move {
281 let mut buf = vec![0u8; MAX_RAS_DATAGRAM];
282 loop {
283 let (n, peer) = match socket.recv_from(&mut buf).await {
284 Ok(x) => x,
285 Err(e) => {
286 Log::new(Some(&status_tx))
287 .error(format!("H.323 gatekeeper receive failed, stopping: {e}"));
288 break;
289 }
290 };
291 let datagram = buf[..n].to_vec();
292 let shared = shared.clone();
293 app_state
294 .spawn_server_task(server_id, async move {
295 handle_datagram(shared, datagram, peer).await;
296 })
297 .await;
298 }
299 });
300 registrar.register_server_task(server_id, task).await;
301 Ok(local)
302}
303
304async fn track(shared: &Shared, peer: SocketAddr, n: usize) -> ConnectionId {
305 let id = ConnectionId::new(shared.app_state.get_next_unified_id().await);
306 let now = Instant::now();
307 shared
308 .app_state
309 .add_connection_to_server(
310 shared.server_id,
311 ConnectionState {
312 id,
313 remote_addr: peer,
314 local_addr: shared.local,
315 bytes_sent: 0,
316 bytes_received: n as u64,
317 packets_sent: 0,
318 packets_received: 1,
319 last_activity: now,
320 status: ConnectionStatus::Active,
321 status_changed_at: now,
322 protocol_info: ProtocolConnectionInfo::empty(),
323 },
324 )
325 .await;
326 let _ = shared.status_tx.send("__UPDATE_UI__".to_string());
327 id
328}
329
330async fn send(
331 shared: &Shared,
332 id: ConnectionId,
333 peer: SocketAddr,
334 kind: &str,
335 body: Value,
336) -> Option<Vec<u8>> {
337 let bytes = match h225::encode_ras(kind, body) {
338 Ok(b) => b,
339 Err(e) => {
340 shared.log().error(format!(
341 "H.323 gatekeeper: could not encode {}: {e:#}",
342 h225::ras_abbreviation(kind)
343 ));
344 return None;
345 }
346 };
347 match shared.socket.send_to(&bytes, peer).await {
348 Ok(n) => {
349 shared
350 .app_state
351 .update_connection_stats(shared.server_id, id, None, Some(n as u64), None, Some(1))
352 .await;
353 shared.log().debug(format!(
354 "H.323 gatekeeper → {peer}: {}",
355 h225::ras_abbreviation(kind)
356 ));
357 Some(bytes)
358 }
359 Err(e) => {
360 shared
361 .log()
362 .error(format!("H.323 gatekeeper: send to {peer} failed: {e}"));
363 None
364 }
365 }
366}
367
368async fn handle_datagram(shared: Arc<Shared>, datagram: Vec<u8>, peer: SocketAddr) {
369 let id = track(&shared, peer, datagram.len()).await;
370 let (kind, body) = match h225::decode_ras(&datagram) {
371 Ok(x) => x,
372 Err(e) => {
373 shared.log().warn(format!(
374 "H.323 gatekeeper: {} octets from {peer} are not a RAS message decision=protocol_error: {e:#}",
375 datagram.len()
376 ));
377 return;
378 }
379 };
380 let abbrev = h225::ras_abbreviation(&kind);
381 let Some(seq) = h225::seq_num(&body) else {
382 shared.log().warn(format!(
383 "H.323 gatekeeper: {abbrev} from {peer} without a sequence number decision=protocol_error"
384 ));
385 return;
386 };
387 shared
388 .log()
389 .debug(format!("H.323 gatekeeper ← {peer}: {abbrev} seq {seq}"));
390 let key = (peer, seq);
391 let (cached, in_flight) = {
392 let mut st = shared.state();
393 st.expire();
394 (st.cached(key), st.in_flight.contains(&key))
395 };
396 if let Some(answer) = cached {
397 let _ = shared.socket.send_to(&answer, peer).await;
398 shared.log().debug(format!(
399 "H.323 gatekeeper: {abbrev} seq {seq} from {peer} retransmitted; answered again"
400 ));
401 return;
402 }
403 if in_flight {
404 send(
405 &shared,
406 id,
407 peer,
408 "requestInProgress",
409 json!({"requestSeqNum": seq, "delay": RIP_DELAY_MS}),
410 )
411 .await;
412 return;
413 }
414 let answer = match kind.as_str() {
415 "gatekeeperRequest" => gatekeeper_request(&shared, id, peer, seq, &body).await,
416 "registrationRequest" => registration_request(&shared, id, peer, seq, &body).await,
417 "unregistrationRequest" => unregistration_request(&shared, seq, &body),
418 "admissionRequest" => admission_request(&shared, id, peer, seq, &body).await,
419 "disengageRequest" => disengage_request(&shared, seq, &body),
420 "locationRequest" => location_request(&shared, id, peer, seq, &body).await,
421 "bandwidthRequest" => Some((
422 "bandwidthConfirm",
423 json!({
424 "requestSeqNum": seq,
425 "bandWidth": body.get("bandWidth").and_then(|b| b.as_u64()).unwrap_or(0).min(shared.config.max_bandwidth),
426 }),
427 )),
428 "infoRequestResponse" => {
429 let wants = body
430 .get("needResponse")
431 .and_then(|v| v.as_bool())
432 .unwrap_or(false);
433 wants.then(|| ("infoRequestAck", json!({"requestSeqNum": seq})))
434 }
435 "resourcesAvailableIndicate" => Some((
436 "resourcesAvailableConfirm",
437 json!({"requestSeqNum": seq, "protocolIdentifier": h225::PROTOCOL_ID}),
438 )),
439 other
440 if other.ends_with("Confirm")
441 || other.ends_with("Reject")
442 || other == "requestInProgress" =>
443 {
444 shared.log().debug(format!(
445 "H.323 gatekeeper: ignoring {abbrev} from {peer}: a gatekeeper sends these, it does not answer them"
446 ));
447 None
448 }
449 _ => {
450 shared.log().info(format!(
451 "H.323 gatekeeper: {abbrev} from {peer} is not handled; answering XRS"
452 ));
453 Some((
454 "unknownMessageResponse",
455 json!({"requestSeqNum": seq, "messageNotUnderstood": hex::encode(&datagram)}),
456 ))
457 }
458 };
459 if let Some((kind, body)) = answer {
460 if let Some(bytes) = send(&shared, id, peer, kind, body).await {
461 shared.state().remember(key, bytes);
462 }
463 }
464}
465
466async fn consult(
469 shared: &Shared,
470 id: ConnectionId,
471 key: (SocketAddr, u16),
472 event: Event,
473) -> Option<Consulted> {
474 {
475 let mut st = shared.state();
476 if st.in_flight.len() >= MAX_IN_FLIGHT {
477 return None;
478 }
479 st.in_flight.insert(key);
480 }
481 let result = call_llm(
482 &shared.llm_client,
483 &shared.app_state,
484 shared.server_id,
485 Some(id),
486 &event,
487 &act::H323GatekeeperProtocol::new(),
488 )
489 .await;
490 shared.state().in_flight.remove(&key);
491 let execution = match result {
492 Ok(x) => x,
493 Err(e) => {
494 let overloaded = matches!(
495 crate::utils::wire_failure::WireFailure::classify(&e),
496 crate::utils::wire_failure::WireFailure::Overloaded
497 );
498 shared.log().error(format!(
499 "H.323 gatekeeper: {} from {} not answered decision={}: {e}",
500 event.event_type.id,
501 key.0,
502 if overloaded {
503 "fail_closed_llm_overloaded"
504 } else {
505 "fail_closed_llm_error"
506 }
507 ));
508 return Some(Consulted::Failed { overloaded });
509 }
510 };
511 for m in execution.messages {
512 let _ = shared.status_tx.send(m);
513 }
514 let mut commands = Vec::new();
515 let mut stack = execution.protocol_results;
516 stack.reverse();
517 while let Some(r) = stack.pop() {
518 match r {
519 ActionResult::Custom { data, .. } => {
520 if let Ok(c) = act::parse_gk_command(&data) {
521 commands.push(c);
522 }
523 }
524 ActionResult::Multiple(inner) => stack.extend(inner.into_iter().rev()),
525 _ => {}
526 }
527 }
528 let refused: Vec<String> = execution
529 .failures
530 .iter()
531 .map(|f| format!("{}: {}", f.action, f.error))
532 .collect();
533 Some(Consulted::Answered { commands, refused })
534}
535
536enum Consulted {
537 Failed {
538 overloaded: bool,
539 },
540 Answered {
541 commands: Vec<GkCommand>,
542 refused: Vec<String>,
543 },
544}
545
546enum Verdict<C, R> {
548 Confirm(C),
549 Reject(R),
550 Refuse(&'static str),
552}
553
554fn verdict<C, R>(
555 shared: &Shared,
556 name: &str,
557 peer: SocketAddr,
558 consulted: Option<Consulted>,
559 pick: impl Fn(GkCommand) -> Option<Result<C, R>>,
560) -> Verdict<C, R> {
561 match consulted {
562 None => {
563 shared.log().warn(format!(
564 "H.323 gatekeeper: {name} from {peer} refused decision=fail_closed_in_flight_cap ({MAX_IN_FLIGHT} requests already with the model)"
565 ));
566 Verdict::Refuse("resourceUnavailable")
567 }
568 Some(Consulted::Failed { overloaded }) => Verdict::Refuse(if overloaded {
569 "resourceUnavailable"
570 } else {
571 "undefinedReason"
572 }),
573 Some(Consulted::Answered { commands, refused }) => {
574 for c in commands {
575 if let Some(v) = pick(c) {
576 return match v {
577 Ok(c) => Verdict::Confirm(c),
578 Err(r) => Verdict::Reject(r),
579 };
580 }
581 }
582 if refused.is_empty() {
583 shared.log().warn(format!(
584 "H.323 gatekeeper: {name} from {peer} answered with no confirm or reject decision=model_silent; refusing"
585 ));
586 Verdict::Refuse("undefinedReason")
587 } else {
588 shared.log().error(format!(
589 "H.323 gatekeeper: {name} from {peer}: no usable action decision=fail_closed_bad_action ({})",
590 refused.join("; ")
591 ));
592 Verdict::Refuse("undefinedReason")
593 }
594 }
595 }
596}
597
598fn gk_identifier_ok(s: &str) -> bool {
599 !s.is_empty() && s.chars().count() <= 128 && !crate::utils::sanitize::has_controls(s)
600}
601
602async fn gatekeeper_request(
603 shared: &Shared,
604 id: ConnectionId,
605 peer: SocketAddr,
606 seq: u16,
607 body: &Value,
608) -> Option<(&'static str, Value)> {
609 let reply_to = body.get("rasAddress").and_then(h225::socket_addr);
610 let event = Event::new(
611 &act::GRQ_EVENT,
612 json!({
613 "from": peer.to_string(),
614 "ras_address": reply_to.map(|a| a.to_string()),
615 "endpoint": h225::endpoint_summary(body.get("endpointType")),
616 "aliases": h225::aliases_text(body.get("endpointAlias")),
617 "requested_gatekeeper": body.get("gatekeeperIdentifier").and_then(|g| g.as_str()).map(crate::utils::sanitize::strip_controls),
618 "this_gatekeeper": shared.config.gatekeeper_id,
619 }),
620 );
621 let consulted = consult(shared, id, (peer, seq), event).await;
622 let v = verdict(shared, "GRQ", peer, consulted, |c| match c {
623 GkCommand::ConfirmGatekeeper => Some(Ok(())),
624 GkCommand::RejectGatekeeper(r) => Some(Err(r)),
625 _ => None,
626 });
627 let base = json!({
628 "requestSeqNum": seq,
629 "protocolIdentifier": h225::PROTOCOL_ID,
630 "gatekeeperIdentifier": shared.config.gatekeeper_id,
631 });
632 match v {
633 Verdict::Confirm(()) => {
634 shared.log().info(format!(
635 "H.323 gatekeeper: GRQ from {peer} decision=model_answer; GCF"
636 ));
637 let mut b = base;
638 b["rasAddress"] = h225::transport(shared.ras_address_for(peer));
639 Some(("gatekeeperConfirm", b))
640 }
641 Verdict::Reject(reason) => {
642 shared.log().info(format!(
643 "H.323 gatekeeper: GRQ from {peer} decision=model_reject; GRJ {reason}"
644 ));
645 let mut b = base;
646 b["rejectReason"] = json!({ reason: null });
647 Some(("gatekeeperReject", b))
648 }
649 Verdict::Refuse(reason) => {
650 let mut b = base;
651 b["rejectReason"] = json!({ reason: null });
652 Some(("gatekeeperReject", b))
653 }
654 }
655}
656
657fn reject_registration(
658 seq: u16,
659 gk: &str,
660 reason: &str,
661 duplicates: Option<Vec<Value>>,
662) -> (&'static str, Value) {
663 let reason_value = match duplicates {
664 Some(d) if reason == "duplicateAlias" => json!({"duplicateAlias": d}),
665 _ if reason == "duplicateAlias" => json!({"duplicateAlias": []}),
666 _ => json!({ reason: null }),
667 };
668 (
669 "registrationReject",
670 json!({
671 "requestSeqNum": seq,
672 "protocolIdentifier": h225::PROTOCOL_ID,
673 "rejectReason": reason_value,
674 "gatekeeperIdentifier": gk,
675 }),
676 )
677}
678
679async fn registration_request(
680 shared: &Shared,
681 id: ConnectionId,
682 peer: SocketAddr,
683 seq: u16,
684 body: &Value,
685) -> Option<(&'static str, Value)> {
686 let gk = shared.config.gatekeeper_id.clone();
687 let call_signal: Vec<Value> = body
688 .get("callSignalAddress")
689 .and_then(|a| a.as_array())
690 .cloned()
691 .unwrap_or_default();
692 let ras: Vec<Value> = body
693 .get("rasAddress")
694 .and_then(|a| a.as_array())
695 .cloned()
696 .unwrap_or_default();
697 let keep_alive = body
698 .get("keepAlive")
699 .and_then(|k| k.as_bool())
700 .unwrap_or(false);
701 let requested_ttl = body.get("timeToLive").and_then(|t| t.as_u64());
702
703 if keep_alive {
704 let endpoint_id = body
705 .get("endpointIdentifier")
706 .and_then(|e| e.as_str())
707 .unwrap_or("");
708 let mut st = shared.state();
709 let Some(reg) = st.registrations.get_mut(endpoint_id) else {
710 drop(st);
711 shared.log().info(format!(
712 "H.323 gatekeeper: keep-alive RRQ from {peer} for unknown endpoint {endpoint_id:?} decision=protocol_reject; RRJ fullRegistrationRequired"
713 ));
714 return Some(reject_registration(
715 seq,
716 &gk,
717 "fullRegistrationRequired",
718 None,
719 ));
720 };
721 reg.expires = Instant::now() + Duration::from_secs(reg.ttl);
722 let answer = json!({
723 "requestSeqNum": seq,
724 "protocolIdentifier": h225::PROTOCOL_ID,
725 "callSignalAddress": [],
726 "gatekeeperIdentifier": gk,
727 "endpointIdentifier": reg.endpoint_id,
728 "timeToLive": reg.ttl,
729 "willRespondToIRR": false,
730 "maintainConnection": false,
731 });
732 shared.log().debug(format!(
733 "H.323 gatekeeper: keep-alive RRQ from {peer} refreshed {} for {}s",
734 reg.endpoint_id, reg.ttl
735 ));
736 return Some(("registrationConfirm", answer));
737 }
738
739 if call_signal.is_empty() {
740 shared.log().info(format!("H.323 gatekeeper: RRQ from {peer} with no call signalling address decision=protocol_reject"));
741 return Some(reject_registration(
742 seq,
743 &gk,
744 "invalidCallSignalAddress",
745 None,
746 ));
747 }
748 if ras.is_empty() {
749 shared.log().info(format!(
750 "H.323 gatekeeper: RRQ from {peer} with no RAS address decision=protocol_reject"
751 ));
752 return Some(reject_registration(seq, &gk, "invalidRASAddress", None));
753 }
754 let requested: Vec<Value> = body
755 .get("terminalAlias")
756 .and_then(|a| a.as_array())
757 .cloned()
758 .unwrap_or_default();
759 let (existing, conflicts, full) = {
760 let mut st = shared.state();
761 st.expire();
762 let existing = st.by_call_signal(&call_signal);
763 let conflicts: Vec<String> = requested
764 .iter()
765 .filter(|a| {
766 st.holder_of(a)
767 .is_some_and(|r| Some(&r.endpoint_id) != existing.as_ref())
768 })
769 .map(h225::alias_text)
770 .collect();
771 let full = existing.is_none() && st.registrations.len() >= MAX_REGISTRATIONS;
772 (existing, conflicts, full)
773 };
774 if full {
775 shared.log().warn(format!(
776 "H.323 gatekeeper: RRQ from {peer} refused decision=fail_closed_registration_cap ({MAX_REGISTRATIONS} endpoints registered)"
777 ));
778 return Some(reject_registration(seq, &gk, "resourceUnavailable", None));
779 }
780 let event = Event::new(
781 &act::RRQ_EVENT,
782 json!({
783 "from": peer.to_string(),
784 "aliases": requested.iter().map(h225::alias_text).collect::<Vec<_>>(),
785 "call_signal_addresses": h225::addresses_text(Some(&Value::Array(call_signal.clone()))),
786 "ras_addresses": h225::addresses_text(Some(&Value::Array(ras.clone()))),
787 "endpoint": h225::endpoint_summary(body.get("terminalType")),
788 "vendor": h225::endpoint_summary(Some(&json!({"vendor": body.get("endpointVendor"), "terminal": {}}))),
789 "requested_time_to_live": requested_ttl,
790 "already_registered_as": existing,
791 "aliases_held_by_other_endpoints": conflicts,
792 "registered_endpoints": shared.state().registrations.len(),
793 }),
794 );
795 let consulted = consult(shared, id, (peer, seq), event).await;
796 let v = verdict(shared, "RRQ", peer, consulted, |c| match c {
797 GkCommand::ConfirmRegistration { aliases, ttl } => Some(Ok((aliases, ttl))),
798 GkCommand::RejectRegistration { reason, duplicates } => Some(Err((reason, duplicates))),
799 _ => None,
800 });
801 match v {
802 Verdict::Confirm((aliases, ttl)) => {
803 let aliases = aliases.unwrap_or(requested);
804 let ttl = ttl
805 .or(requested_ttl)
806 .unwrap_or(shared.config.default_ttl)
807 .clamp(1, MAX_TTL_SECS);
808 let mut st = shared.state();
809 let endpoint_id = match &existing {
810 Some(e) => e.clone(),
811 None => {
812 st.next_endpoint += 1;
813 format!("ep{}-{:04x}", st.next_endpoint, rand::random::<u16>())
814 }
815 };
816 let taken: Vec<Value> = aliases
817 .iter()
818 .filter(|a| {
819 st.holder_of(a)
820 .is_some_and(|r| r.endpoint_id != endpoint_id)
821 })
822 .cloned()
823 .collect();
824 if !taken.is_empty() {
825 drop(st);
826 shared.log().warn(format!(
827 "H.323 gatekeeper: RRQ from {peer}: the model confirmed aliases another endpoint holds ({}) decision=fail_closed_bad_action; RRJ duplicateAlias",
828 taken.iter().map(h225::alias_text).collect::<Vec<_>>().join(", ")
829 ));
830 return Some(reject_registration(seq, &gk, "duplicateAlias", Some(taken)));
831 }
832 if existing.is_none() && st.registrations.len() >= MAX_REGISTRATIONS {
833 drop(st);
834 return Some(reject_registration(seq, &gk, "resourceUnavailable", None));
835 }
836 st.registrations.insert(
837 endpoint_id.clone(),
838 Registration {
839 endpoint_id: endpoint_id.clone(),
840 aliases: aliases.clone(),
841 call_signal,
842 ras,
843 ttl,
844 expires: Instant::now() + Duration::from_secs(ttl),
845 endpoint: h225::endpoint_summary(body.get("terminalType")),
846 },
847 );
848 drop(st);
849 shared.log().info(format!(
850 "H.323 gatekeeper: RRQ from {peer} decision=model_answer; RCF {endpoint_id} as [{}] for {ttl}s",
851 aliases.iter().map(h225::alias_text).collect::<Vec<_>>().join(", ")
852 ));
853 let mut answer = json!({
854 "requestSeqNum": seq,
855 "protocolIdentifier": h225::PROTOCOL_ID,
856 "callSignalAddress": [],
857 "gatekeeperIdentifier": gk,
858 "endpointIdentifier": endpoint_id,
859 "timeToLive": ttl,
860 "willRespondToIRR": false,
861 "maintainConnection": false,
862 });
863 if !aliases.is_empty() {
864 answer["terminalAlias"] = Value::Array(aliases);
865 }
866 Some(("registrationConfirm", answer))
867 }
868 Verdict::Reject((reason, duplicates)) => {
869 shared.log().info(format!(
870 "H.323 gatekeeper: RRQ from {peer} decision=model_reject; RRJ {reason}"
871 ));
872 Some(reject_registration(seq, &gk, &reason, duplicates))
873 }
874 Verdict::Refuse(reason) => Some(reject_registration(seq, &gk, reason, None)),
875 }
876}
877
878fn unregistration_request(
879 shared: &Shared,
880 seq: u16,
881 body: &Value,
882) -> Option<(&'static str, Value)> {
883 let call_signal: Vec<Value> = body
884 .get("callSignalAddress")
885 .and_then(|a| a.as_array())
886 .cloned()
887 .unwrap_or_default();
888 let mut st = shared.state();
889 let endpoint_id = body
890 .get("endpointIdentifier")
891 .and_then(|e| e.as_str())
892 .map(str::to_string)
893 .filter(|e| st.registrations.contains_key(e))
894 .or_else(|| st.by_call_signal(&call_signal));
895 match endpoint_id {
896 Some(e) => {
897 st.registrations.remove(&e);
898 st.calls.retain(|_, c| c.endpoint_id != e);
899 drop(st);
900 shared
901 .log()
902 .info(format!("H.323 gatekeeper: URQ: {e} unregistered; UCF"));
903 Some(("unregistrationConfirm", json!({"requestSeqNum": seq})))
904 }
905 None => Some((
906 "unregistrationReject",
907 json!({"requestSeqNum": seq, "rejectReason": {"notCurrentlyRegistered": null}}),
908 )),
909 }
910}
911
912fn disengage_request(shared: &Shared, seq: u16, body: &Value) -> Option<(&'static str, Value)> {
913 let endpoint_id = body
914 .get("endpointIdentifier")
915 .and_then(|e| e.as_str())
916 .unwrap_or("");
917 let mut st = shared.state();
918 if !st.registrations.contains_key(endpoint_id) {
919 return Some((
920 "disengageReject",
921 json!({"requestSeqNum": seq, "rejectReason": {"notRegistered": null}}),
922 ));
923 }
924 if let Some(guid) = body
925 .get("callIdentifier")
926 .and_then(|c| c.get("guid"))
927 .and_then(|g| g.as_str())
928 {
929 if let Some(call) = st.calls.remove(&format!("{endpoint_id}/{guid}")) {
930 shared.log().debug(format!(
931 "H.323 gatekeeper: call {guid} of {} released {} × 100 bit/s",
932 call.endpoint_id, call.bandwidth
933 ));
934 }
935 }
936 drop(st);
937 shared
938 .log()
939 .info(format!("H.323 gatekeeper: DRQ from {endpoint_id}; DCF"));
940 Some(("disengageConfirm", json!({"requestSeqNum": seq})))
941}
942
943type Resolved = (SocketAddr, Vec<Value>, Option<Vec<Value>>);
948
949fn resolve_destination(st: &State, destination: &str) -> Result<Resolved, String> {
950 if let Ok(addr) = destination.parse::<SocketAddr>() {
951 return Ok((addr, Vec::new(), None));
952 }
953 let alias = h225::alias(destination).map_err(|e| e.to_string())?;
954 let reg = st
955 .holder_of(&alias)
956 .ok_or_else(|| format!("no registered endpoint holds {destination}"))?;
957 let addr = reg
958 .call_signal
959 .iter()
960 .find_map(h225::socket_addr)
961 .ok_or_else(|| {
962 format!(
963 "{} registered no IP call signalling address",
964 reg.endpoint_id
965 )
966 })?;
967 Ok((addr, reg.aliases.clone(), Some(reg.ras.clone())))
968}
969
970async fn admission_request(
971 shared: &Shared,
972 id: ConnectionId,
973 peer: SocketAddr,
974 seq: u16,
975 body: &Value,
976) -> Option<(&'static str, Value)> {
977 let endpoint_id = body
978 .get("endpointIdentifier")
979 .and_then(|e| e.as_str())
980 .unwrap_or("")
981 .to_string();
982 let reject = |reason: &str| {
983 Some((
984 "admissionReject",
985 json!({"requestSeqNum": seq, "rejectReason": { reason: null }}),
986 ))
987 };
988 let answer_call = body
989 .get("answerCall")
990 .and_then(|a| a.as_bool())
991 .unwrap_or(false);
992 let destinations: Vec<Value> = body
993 .get("destinationInfo")
994 .and_then(|d| d.as_array())
995 .cloned()
996 .unwrap_or_default();
997 let (caller, candidates, calls_open) = {
998 let mut st = shared.state();
999 st.expire();
1000 let caller = st.registrations.get(&endpoint_id).cloned();
1001 let candidates: Vec<Value> = destinations
1002 .iter()
1003 .filter_map(|a| {
1004 st.holder_of(a).map(|r| {
1005 json!({
1006 "alias": h225::alias_text(a),
1007 "endpoint_id": r.endpoint_id,
1008 "call_signal_address": r.call_signal.iter().find_map(h225::socket_addr).map(|s| s.to_string()),
1009 "endpoint": r.endpoint,
1010 })
1011 })
1012 })
1013 .collect();
1014 (caller, candidates, st.calls.len())
1015 };
1016 let Some(caller) = caller else {
1017 shared.log().info(format!(
1018 "H.323 gatekeeper: ARQ from {peer} for unregistered endpoint {endpoint_id:?} decision=protocol_reject; ARJ callerNotRegistered"
1019 ));
1020 return reject("callerNotRegistered");
1021 };
1022 if calls_open >= MAX_CALLS {
1023 shared.log().warn(format!(
1024 "H.323 gatekeeper: ARQ from {peer} refused decision=fail_closed_call_cap ({MAX_CALLS} calls admitted)"
1025 ));
1026 return reject("resourceUnavailable");
1027 }
1028 let requested_bw = body.get("bandWidth").and_then(|b| b.as_u64()).unwrap_or(0);
1029 let call_id = body
1030 .get("callIdentifier")
1031 .and_then(|c| c.get("guid"))
1032 .and_then(|g| g.as_str())
1033 .unwrap_or("")
1034 .to_string();
1035 let event = Event::new(
1036 &act::ARQ_EVENT,
1037 json!({
1038 "from": peer.to_string(),
1039 "endpoint_id": endpoint_id,
1040 "caller_aliases": caller.aliases.iter().map(h225::alias_text).collect::<Vec<_>>(),
1041 "source_info": h225::aliases_text(body.get("srcInfo")),
1042 "destination_aliases": h225::aliases_text(body.get("destinationInfo")),
1043 "destination_address": body.get("destCallSignalAddress").map(h225::transport_text),
1044 "registered_destinations": candidates,
1045 "answer_call": answer_call,
1046 "bandwidth": requested_bw,
1047 "call_reference": body.get("callReferenceValue"),
1048 "call_id": call_id,
1049 "conference_id": body.get("conferenceID"),
1050 }),
1051 );
1052 let consulted = consult(shared, id, (peer, seq), event).await;
1053 let v = verdict(shared, "ARQ", peer, consulted, |c| match c {
1054 GkCommand::ConfirmAdmission {
1055 destination,
1056 bandwidth,
1057 } => Some(Ok((destination, bandwidth))),
1058 GkCommand::RejectAdmission(r) => Some(Err(r)),
1059 _ => None,
1060 });
1061 match v {
1062 Verdict::Confirm((destination, bandwidth)) => {
1063 let resolved = {
1064 let st = shared.state();
1065 match destination.as_deref() {
1066 Some(d) => resolve_destination(&st, d),
1067 None if answer_call => caller
1068 .call_signal
1069 .iter()
1070 .find_map(h225::socket_addr)
1071 .map(|a| (a, caller.aliases.clone(), None))
1072 .ok_or_else(|| {
1073 "the answering endpoint registered no IP address".to_string()
1074 }),
1075 None => destinations
1076 .iter()
1077 .find_map(|a| {
1078 let r = st.holder_of(a)?;
1079 let addr = r.call_signal.iter().find_map(h225::socket_addr)?;
1080 Some((addr, r.aliases.clone(), None))
1081 })
1082 .or_else(|| {
1083 body.get("destCallSignalAddress")
1084 .and_then(h225::socket_addr)
1085 .map(|a| (a, Vec::new(), None))
1086 })
1087 .ok_or_else(|| {
1088 "no destination is registered and the ARQ named no address".to_string()
1089 }),
1090 }
1091 };
1092 let (addr, dest_aliases, _) = match resolved {
1093 Ok(r) => r,
1094 Err(why) => {
1095 shared.log().warn(format!(
1096 "H.323 gatekeeper: ARQ from {peer}: cannot admit ({why}) decision=fail_closed_bad_action; ARJ calledPartyNotRegistered"
1097 ));
1098 return reject("calledPartyNotRegistered");
1099 }
1100 };
1101 let bw = bandwidth
1102 .unwrap_or(requested_bw)
1103 .min(shared.config.max_bandwidth);
1104 {
1105 let mut st = shared.state();
1106 if st.calls.len() < MAX_CALLS && !call_id.is_empty() {
1107 st.calls.insert(
1108 format!("{endpoint_id}/{call_id}"),
1109 AdmittedCall {
1110 endpoint_id: endpoint_id.clone(),
1111 bandwidth: bw,
1112 },
1113 );
1114 }
1115 }
1116 shared.log().info(format!(
1117 "H.323 gatekeeper: ARQ from {endpoint_id} decision=model_answer; ACF to {addr}, {bw} × 100 bit/s"
1118 ));
1119 let mut answer = json!({
1120 "requestSeqNum": seq,
1121 "bandWidth": bw,
1122 "callModel": {"direct": null},
1123 "destCallSignalAddress": h225::transport(addr),
1124 "willRespondToIRR": false,
1125 "uuiesRequested": {
1126 "setup": false, "callProceeding": false, "connect": false, "alerting": false,
1127 "information": false, "releaseComplete": false, "facility": false,
1128 "progress": false, "empty": false,
1129 },
1130 });
1131 if !dest_aliases.is_empty() {
1132 answer["destinationInfo"] = Value::Array(dest_aliases);
1133 }
1134 Some(("admissionConfirm", answer))
1135 }
1136 Verdict::Reject(reason) => {
1137 shared.log().info(format!(
1138 "H.323 gatekeeper: ARQ from {endpoint_id} decision=model_reject; ARJ {reason}"
1139 ));
1140 reject(&reason)
1141 }
1142 Verdict::Refuse(reason) => reject(reason),
1143 }
1144}
1145
1146async fn location_request(
1147 shared: &Shared,
1148 id: ConnectionId,
1149 peer: SocketAddr,
1150 seq: u16,
1151 body: &Value,
1152) -> Option<(&'static str, Value)> {
1153 let reject = |reason: &str| {
1154 Some((
1155 "locationReject",
1156 json!({"requestSeqNum": seq, "rejectReason": { reason: null }}),
1157 ))
1158 };
1159 let destinations: Vec<Value> = body
1160 .get("destinationInfo")
1161 .and_then(|d| d.as_array())
1162 .cloned()
1163 .unwrap_or_default();
1164 let candidates: Vec<Value> = {
1165 let st = shared.state();
1166 destinations
1167 .iter()
1168 .filter_map(|a| {
1169 st.holder_of(a).map(|r| json!({
1170 "alias": h225::alias_text(a),
1171 "endpoint_id": r.endpoint_id,
1172 "call_signal_address": r.call_signal.iter().find_map(h225::socket_addr).map(|s| s.to_string()),
1173 }))
1174 })
1175 .collect()
1176 };
1177 let event = Event::new(
1178 &act::LRQ_EVENT,
1179 json!({
1180 "from": peer.to_string(),
1181 "destination_aliases": h225::aliases_text(body.get("destinationInfo")),
1182 "source_info": h225::aliases_text(body.get("sourceInfo")),
1183 "reply_address": body.get("replyAddress").map(h225::transport_text),
1184 "registered_destinations": candidates,
1185 }),
1186 );
1187 let consulted = consult(shared, id, (peer, seq), event).await;
1188 let v = verdict(shared, "LRQ", peer, consulted, |c| match c {
1189 GkCommand::ConfirmLocation { destination } => Some(Ok(destination)),
1190 GkCommand::RejectLocation(r) => Some(Err(r)),
1191 _ => None,
1192 });
1193 match v {
1194 Verdict::Confirm(destination) => {
1195 let resolved = {
1196 let st = shared.state();
1197 match destination.as_deref() {
1198 Some(d) => resolve_destination(&st, d),
1199 None => destinations
1200 .iter()
1201 .find_map(|a| {
1202 let r = st.holder_of(a)?;
1203 let addr = r.call_signal.iter().find_map(h225::socket_addr)?;
1204 Some((addr, r.aliases.clone(), Some(r.ras.clone())))
1205 })
1206 .ok_or_else(|| "no destination alias is registered".to_string()),
1207 }
1208 };
1209 match resolved {
1210 Ok((addr, aliases, ras)) => {
1211 let ras = ras
1212 .and_then(|r| r.into_iter().next())
1213 .unwrap_or_else(|| h225::transport(shared.ras_address_for(peer)));
1214 shared.log().info(format!(
1215 "H.323 gatekeeper: LRQ from {peer} decision=model_answer; LCF {addr}"
1216 ));
1217 let mut answer = json!({
1218 "requestSeqNum": seq,
1219 "callSignalAddress": h225::transport(addr),
1220 "rasAddress": ras,
1221 });
1222 if !aliases.is_empty() {
1223 answer["destinationInfo"] = Value::Array(aliases);
1224 }
1225 Some(("locationConfirm", answer))
1226 }
1227 Err(why) => {
1228 shared.log().warn(format!(
1229 "H.323 gatekeeper: LRQ from {peer}: cannot locate ({why}) decision=fail_closed_bad_action; LRJ notRegistered"
1230 ));
1231 reject("notRegistered")
1232 }
1233 }
1234 }
1235 Verdict::Reject(reason) => {
1236 shared.log().info(format!(
1237 "H.323 gatekeeper: LRQ from {peer} decision=model_reject; LRJ {reason}"
1238 ));
1239 reject(&reason)
1240 }
1241 Verdict::Refuse(reason) => {
1242 reject(if reason == "resourceUnavailable" {
1244 "resourceUnavailable"
1245 } else {
1246 "undefinedReason"
1247 })
1248 }
1249 }
1250}
1251
1252pub fn validate_gatekeeper_id(s: &str) -> Result<()> {
1253 anyhow::ensure!(
1254 gk_identifier_ok(s),
1255 "gatekeeper_id is 1 to 128 characters with no control characters"
1256 );
1257 Ok(())
1258}