Skip to main content

netget/protocol/
server_registry.rs

1//! Protocol registry
2//!
3//! This module provides a centralized registry that maps protocol names
4//! to their protocol implementations. It enables trait-based protocol lookup
5//! and keyword-based parsing.
6
7use super::metadata::ProtocolMetadataV2;
8use crate::llm::actions::Server;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12/// Global protocol registry mapping protocol names to protocol implementations
13pub struct ServerRegistry {
14    /// Maps protocol name (e.g., "TCP", "HTTP") to protocol implementation
15    protocols: HashMap<String, Arc<dyn Server>>,
16    /// Maps lowercase keywords to protocol name for fast parsing
17    keyword_map: HashMap<String, String>,
18}
19
20impl ServerRegistry {
21    /// Create a new protocol registry
22    fn new() -> Self {
23        let mut registry = Self {
24            protocols: HashMap::new(),
25            keyword_map: HashMap::new(),
26        };
27
28        // Register every protocol this platform builds
29        registry.register_protocols();
30        registry.build_keyword_map();
31
32        // Validate that no keywords overlap between protocols
33        registry.validate_keyword_uniqueness();
34
35        registry
36    }
37
38    /// Register every protocol this build includes (platform and system-library gates)
39    fn register_protocols(&mut self) {
40        // Create a shared dummy AppState for protocols that need it during registration
41        // This avoids creating multiple AppState instances which trigger expensive environment detection
42        let dummy_state = Arc::new(crate::state::app_state::AppState::new());
43
44        self.register(Arc::new(crate::server::TcpProtocol::new()));
45
46        #[cfg(unix)]
47        self.register(Arc::new(crate::server::SocketFileProtocol::new()));
48
49        #[cfg(unix)]
50        self.register(Arc::new(crate::server::NamedPipeProtocol::new()));
51
52        #[cfg(unix)]
53        self.register(Arc::new(crate::server::PtyProtocol::new()));
54
55        #[cfg(unix)]
56        self.register(Arc::new(crate::server::StdioProtocol::new()));
57
58        self.register(Arc::new(crate::server::HttpProtocol::new()));
59
60        self.register(Arc::new(crate::server::Http2Protocol::new()));
61
62        self.register(Arc::new(crate::server::PypiProtocol::new()));
63
64        self.register(Arc::new(crate::server::MavenProtocol::new()));
65
66        self.register(Arc::new(crate::server::UdpProtocol::new()));
67
68        #[cfg(all(unix, feature = "libpcap"))]
69        self.register(Arc::new(crate::server::DataLinkProtocol::new()));
70
71        #[cfg(all(unix, feature = "libpcap"))]
72        self.register(Arc::new(crate::server::ArpProtocol::new()));
73
74        #[cfg(unix)]
75        self.register(Arc::new(crate::server::IcmpProtocol::new()));
76
77        #[cfg(not(target_arch = "wasm32"))]
78        self.register(Arc::new(
79            crate::server::dc_peer::actions::DcPeerProtocol::new(),
80        ));
81
82        #[cfg(not(target_arch = "wasm32"))]
83        self.register(Arc::new(crate::server::adc::actions::AdcProtocol::new()));
84
85        #[cfg(not(target_arch = "wasm32"))]
86        self.register(Arc::new(
87            crate::server::adc_peer::actions::AdcPeerProtocol::new(),
88        ));
89
90        #[cfg(not(target_arch = "wasm32"))]
91        self.register(Arc::new(
92            crate::server::soulseek::actions::SoulseekProtocol::new(),
93        ));
94
95        #[cfg(not(target_arch = "wasm32"))]
96        self.register(Arc::new(
97            crate::server::soulseek_peer::actions::SoulseekPeerProtocol::new(),
98        ));
99
100        #[cfg(not(target_arch = "wasm32"))]
101        self.register(Arc::new(
102            crate::server::gnutella::actions::GnutellaProtocol::new(),
103        ));
104
105        self.register(Arc::new(crate::server::DcProtocol::new()));
106
107        #[cfg(not(target_arch = "wasm32"))]
108        self.register(Arc::new(crate::server::DnsProtocol::new()));
109
110        #[cfg(not(target_arch = "wasm32"))]
111        self.register(Arc::new(crate::server::doq::actions::DoqProtocol::new()));
112
113        #[cfg(not(target_arch = "wasm32"))]
114        self.register(Arc::new(crate::server::DotProtocol::new()));
115
116        #[cfg(not(target_arch = "wasm32"))]
117        self.register(Arc::new(crate::server::DohProtocol::new()));
118
119        self.register(Arc::new(crate::server::DhcpProtocol::new()));
120
121        self.register(Arc::new(crate::server::BootpProtocol::new()));
122
123        #[cfg(not(target_arch = "wasm32"))]
124        self.register(Arc::new(crate::server::NtpProtocol::new()));
125
126        self.register(Arc::new(crate::server::TftpProtocol::new()));
127
128        #[cfg(not(target_arch = "wasm32"))]
129        self.register(Arc::new(crate::server::nut::actions::NutProtocol::new()));
130
131        self.register(Arc::new(crate::server::WhoisProtocol::new()));
132
133        #[cfg(unix)]
134        self.register(Arc::new(crate::server::NdpProtocol::new()));
135
136        self.register(Arc::new(crate::server::Dhcpv6Protocol::new()));
137
138        #[cfg(unix)]
139        self.register(Arc::new(crate::server::TunTapProtocol::new()));
140
141        #[cfg(unix)]
142        self.register(Arc::new(crate::server::RawIpProtocol::new()));
143
144        self.register(Arc::new(crate::server::GtpProtocol::new()));
145
146        #[cfg(unix)]
147        self.register(Arc::new(crate::server::M3uaProtocol::new()));
148
149        #[cfg(unix)]
150        self.register(Arc::new(crate::server::ngap::actions::NgapProtocol::new()));
151
152        #[cfg(unix)]
153        self.register(Arc::new(crate::server::CanProtocol::new()));
154
155        #[cfg(all(unix, feature = "libpcap"))]
156        self.register(Arc::new(crate::server::LldpProtocol::new()));
157
158        #[cfg(all(unix, feature = "libpcap"))]
159        self.register(Arc::new(crate::server::CdpProtocol::new()));
160
161        #[cfg(all(unix, feature = "libpcap"))]
162        self.register(Arc::new(crate::server::StpProtocol::new()));
163
164        #[cfg(unix)]
165        self.register(Arc::new(crate::server::VrrpProtocol::new()));
166
167        self.register(Arc::new(crate::server::HsrpProtocol::new()));
168
169        #[cfg(all(unix, feature = "libpcap"))]
170        self.register(Arc::new(crate::server::EapolProtocol::new()));
171
172        self.register(Arc::new(crate::server::WolProtocol::new()));
173
174        self.register(Arc::new(crate::server::NatsProtocol::new()));
175
176        self.register(Arc::new(crate::server::StompProtocol::new()));
177
178        self.register(Arc::new(crate::server::IdentProtocol::new()));
179
180        #[cfg(not(target_arch = "wasm32"))]
181        self.register(Arc::new(crate::server::GeminiProtocol::new()));
182
183        #[cfg(not(target_arch = "wasm32"))]
184        self.register(Arc::new(crate::server::DictProtocol::new()));
185
186        #[cfg(not(target_arch = "wasm32"))]
187        self.register(Arc::new(crate::server::BeanstalkdProtocol::new()));
188
189        #[cfg(not(target_arch = "wasm32"))]
190        self.register(Arc::new(crate::server::ZabbixProtocol::new()));
191
192        #[cfg(not(target_arch = "wasm32"))]
193        self.register(Arc::new(crate::server::GearmanProtocol::new()));
194
195        #[cfg(not(target_arch = "wasm32"))]
196        self.register(Arc::new(crate::server::NsqProtocol::new()));
197
198        self.register(Arc::new(crate::server::GopherProtocol::new()));
199
200        self.register(Arc::new(crate::server::FingerProtocol::new()));
201
202        self.register(Arc::new(crate::server::SsdpProtocol::new()));
203
204        #[cfg(not(target_arch = "wasm32"))]
205        self.register(Arc::new(crate::server::LlmnrProtocol::new()));
206
207        self.register(Arc::new(crate::server::NetbiosNsProtocol::new()));
208
209        self.register(Arc::new(crate::server::SnmpProtocol::new()));
210
211        #[cfg(unix)]
212        self.register(Arc::new(crate::server::IgmpProtocol::new()));
213
214        #[cfg(not(target_arch = "wasm32"))]
215        self.register(Arc::new(
216            crate::server::diameter::actions::DiameterProtocol::new(),
217        ));
218
219        #[cfg(not(target_arch = "wasm32"))]
220        self.register(Arc::new(
221            crate::server::tacacs::actions::TacacsProtocol::new(),
222        ));
223
224        #[cfg(not(target_arch = "wasm32"))]
225        self.register(Arc::new(
226            crate::server::netconf::actions::NetconfProtocol::new(),
227        ));
228
229        #[cfg(not(target_arch = "wasm32"))]
230        self.register(Arc::new(
231            crate::server::rpki_rtr::actions::RpkiRtrProtocol::new(),
232        ));
233
234        #[cfg(not(target_arch = "wasm32"))]
235        self.register(Arc::new(crate::server::rdap::actions::RdapProtocol::new()));
236
237        #[cfg(not(target_arch = "wasm32"))]
238        self.register(Arc::new(crate::server::hl7::actions::Hl7Protocol::new()));
239
240        #[cfg(not(target_arch = "wasm32"))]
241        self.register(Arc::new(crate::server::icap::actions::IcapProtocol::new()));
242
243        #[cfg(not(target_arch = "wasm32"))]
244        self.register(Arc::new(
245            crate::server::s7comm::actions::S7commProtocol::new(),
246        ));
247
248        #[cfg(not(target_arch = "wasm32"))]
249        self.register(Arc::new(
250            crate::server::ethernet_ip::actions::EthernetIpProtocol::new(),
251        ));
252
253        #[cfg(not(target_arch = "wasm32"))]
254        self.register(Arc::new(crate::server::dnp3::actions::Dnp3Protocol::new()));
255
256        #[cfg(not(target_arch = "wasm32"))]
257        self.register(Arc::new(
258            crate::server::iec104::actions::Iec104Protocol::new(),
259        ));
260
261        #[cfg(not(target_arch = "wasm32"))]
262        self.register(Arc::new(
263            crate::server::bacnet::actions::BacnetProtocol::new(),
264        ));
265
266        #[cfg(not(target_arch = "wasm32"))]
267        self.register(Arc::new(crate::server::opcua::actions::OpcuaProtocol::new()));
268
269        #[cfg(not(target_arch = "wasm32"))]
270        self.register(Arc::new(crate::server::ocpp::actions::OcppProtocol::new()));
271
272        #[cfg(not(target_arch = "wasm32"))]
273        self.register(Arc::new(crate::server::a2a::actions::A2aProtocol::new()));
274
275        #[cfg(not(target_arch = "wasm32"))]
276        self.register(Arc::new(
277            crate::server::graphql::actions::GraphqlProtocol::new(),
278        ));
279
280        #[cfg(not(target_arch = "wasm32"))]
281        self.register(Arc::new(
282            crate::server::fastcgi::actions::FastcgiProtocol::new(),
283        ));
284
285        #[cfg(not(target_arch = "wasm32"))]
286        self.register(Arc::new(
287            crate::server::redfish::actions::RedfishProtocol::new(),
288        ));
289
290        #[cfg(not(target_arch = "wasm32"))]
291        self.register(Arc::new(crate::server::scim::actions::ScimProtocol::new()));
292
293        #[cfg(not(target_arch = "wasm32"))]
294        self.register(Arc::new(
295            crate::server::socketio::actions::SocketIoProtocol::new(),
296        ));
297
298        #[cfg(not(target_arch = "wasm32"))]
299        self.register(Arc::new(
300            crate::server::caldav::actions::CalDavProtocol::new(),
301        ));
302
303        #[cfg(not(target_arch = "wasm32"))]
304        self.register(Arc::new(
305            crate::server::carddav::actions::CardDavProtocol::new(),
306        ));
307
308        #[cfg(not(target_arch = "wasm32"))]
309        self.register(Arc::new(crate::server::dicom::actions::DicomProtocol::new()));
310
311        #[cfg(not(target_arch = "wasm32"))]
312        self.register(Arc::new(crate::server::acme::actions::AcmeProtocol::new()));
313
314        #[cfg(not(target_arch = "wasm32"))]
315        self.register(Arc::new(crate::server::fix::actions::FixProtocol::new()));
316
317        #[cfg(not(target_arch = "wasm32"))]
318        self.register(Arc::new(crate::server::wamp::actions::WampProtocol::new()));
319
320        #[cfg(not(target_arch = "wasm32"))]
321        self.register(Arc::new(crate::server::rtmp::actions::RtmpProtocol::new()));
322
323        #[cfg(not(target_arch = "wasm32"))]
324        self.register(Arc::new(crate::server::srt::actions::SrtProtocol::new()));
325
326        #[cfg(not(target_arch = "wasm32"))]
327        self.register(Arc::new(crate::server::amqp1::actions::Amqp1Protocol::new()));
328
329        #[cfg(not(target_arch = "wasm32"))]
330        self.register(Arc::new(
331            crate::server::thrift::actions::ThriftProtocol::new(),
332        ));
333        #[cfg(not(target_arch = "wasm32"))]
334        self.register(Arc::new(crate::server::bmp::actions::BmpProtocol::new()));
335        #[cfg(not(target_arch = "wasm32"))]
336        self.register(Arc::new(
337            crate::server::mqtt_sn::actions::MqttSnProtocol::new(),
338        ));
339        #[cfg(not(target_arch = "wasm32"))]
340        self.register(Arc::new(crate::server::nbd::actions::NbdProtocol::new()));
341        #[cfg(not(target_arch = "wasm32"))]
342        self.register(Arc::new(
343            crate::server::managesieve::actions::ManageSieveProtocol::new(),
344        ));
345        #[cfg(not(target_arch = "wasm32"))]
346        self.register(Arc::new(crate::server::zenoh::actions::ZenohProtocol::new()));
347        #[cfg(not(target_arch = "wasm32"))]
348        self.register(Arc::new(crate::server::lwm2m::actions::Lwm2mProtocol::new()));
349        #[cfg(not(target_arch = "wasm32"))]
350        self.register(Arc::new(
351            crate::server::restconf::actions::RestconfProtocol::new(),
352        ));
353        #[cfg(not(target_arch = "wasm32"))]
354        self.register(Arc::new(
355            crate::server::webtransport::actions::WebTransportProtocol::new(),
356        ));
357        #[cfg(not(target_arch = "wasm32"))]
358        self.register(Arc::new(crate::server::jmap::actions::JmapProtocol::new()));
359        #[cfg(not(target_arch = "wasm32"))]
360        self.register(Arc::new(crate::server::epp::actions::EppProtocol::new()));
361        #[cfg(not(target_arch = "wasm32"))]
362        self.register(Arc::new(crate::server::lmtp::actions::LmtpProtocol::new()));
363        #[cfg(not(target_arch = "wasm32"))]
364        self.register(Arc::new(crate::server::lpd::actions::LpdProtocol::new()));
365        #[cfg(not(target_arch = "wasm32"))]
366        self.register(Arc::new(crate::server::inetd::actions::EchoProtocol::new()));
367        #[cfg(not(target_arch = "wasm32"))]
368        self.register(Arc::new(
369            crate::server::inetd::actions::DiscardProtocol::new(),
370        ));
371        #[cfg(not(target_arch = "wasm32"))]
372        self.register(Arc::new(
373            crate::server::inetd::actions::DaytimeProtocol::new(),
374        ));
375        #[cfg(not(target_arch = "wasm32"))]
376        self.register(Arc::new(crate::server::inetd::actions::QotdProtocol::new()));
377        #[cfg(not(target_arch = "wasm32"))]
378        self.register(Arc::new(
379            crate::server::inetd::actions::ChargenProtocol::new(),
380        ));
381        #[cfg(not(target_arch = "wasm32"))]
382        self.register(Arc::new(crate::server::inetd::actions::TimeProtocol::new()));
383        #[cfg(not(target_arch = "wasm32"))]
384        self.register(Arc::new(crate::server::rcon::actions::RconProtocol::new()));
385        #[cfg(not(target_arch = "wasm32"))]
386        self.register(Arc::new(crate::server::a2s::actions::A2sProtocol::new()));
387        #[cfg(not(target_arch = "wasm32"))]
388        self.register(Arc::new(
389            crate::server::minecraft::actions::MinecraftProtocol::new(),
390        ));
391        #[cfg(not(target_arch = "wasm32"))]
392        self.register(Arc::new(crate::server::ninep::actions::NinepProtocol::new()));
393        #[cfg(not(target_arch = "wasm32"))]
394        self.register(Arc::new(
395            crate::server::zeromq::actions::ZeromqProtocol::new(),
396        ));
397        #[cfg(not(target_arch = "wasm32"))]
398        self.register(Arc::new(crate::server::smpp::actions::SmppProtocol::new()));
399        #[cfg(not(target_arch = "wasm32"))]
400        self.register(Arc::new(
401            crate::server::clickhouse::actions::ClickhouseProtocol::new(),
402        ));
403        #[cfg(not(target_arch = "wasm32"))]
404        self.register(Arc::new(
405            crate::server::msgpack_rpc::actions::MsgpackRpcProtocol::new(),
406        ));
407        #[cfg(not(target_arch = "wasm32"))]
408        self.register(Arc::new(
409            crate::server::zipkin::actions::ZipkinProtocol::new(),
410        ));
411        #[cfg(not(target_arch = "wasm32"))]
412        self.register(Arc::new(
413            crate::server::capnp_rpc::actions::CapnpRpcProtocol::new(),
414        ));
415        #[cfg(not(target_arch = "wasm32"))]
416        self.register(Arc::new(crate::server::knx::actions::KnxProtocol::new()));
417        #[cfg(not(target_arch = "wasm32"))]
418        self.register(Arc::new(
419            crate::server::consul::actions::ConsulProtocol::new(),
420        ));
421        #[cfg(not(target_arch = "wasm32"))]
422        self.register(Arc::new(
423            crate::server::milter::actions::MilterProtocol::new(),
424        ));
425        #[cfg(not(target_arch = "wasm32"))]
426        self.register(Arc::new(
427            crate::server::anthropic::actions::AnthropicProtocol::new(),
428        ));
429        #[cfg(not(target_arch = "wasm32"))]
430        self.register(Arc::new(
431            crate::server::radsec::actions::RadsecProtocol::new(),
432        ));
433        #[cfg(not(target_arch = "wasm32"))]
434        self.register(Arc::new(crate::server::dbus::actions::DbusProtocol::new()));
435        #[cfg(not(target_arch = "wasm32"))]
436        self.register(Arc::new(crate::server::bfd::actions::BfdProtocol::new()));
437        #[cfg(not(target_arch = "wasm32"))]
438        self.register(Arc::new(crate::server::vxlan::actions::VxlanProtocol::new()));
439        #[cfg(not(target_arch = "wasm32"))]
440        self.register(Arc::new(crate::server::rsync::actions::RsyncProtocol::new()));
441        #[cfg(not(target_arch = "wasm32"))]
442        self.register(Arc::new(crate::server::pfcp::actions::PfcpProtocol::new()));
443        #[cfg(not(target_arch = "wasm32"))]
444        self.register(Arc::new(
445            crate::server::matrix::actions::MatrixProtocol::new(),
446        ));
447        #[cfg(not(target_arch = "wasm32"))]
448        self.register(Arc::new(
449            crate::server::libp2p::actions::Libp2pProtocol::new(),
450        ));
451        #[cfg(not(target_arch = "wasm32"))]
452        self.register(Arc::new(
453            crate::server::sunrpc::actions::SunRpcProtocol::new(),
454        ));
455        #[cfg(not(target_arch = "wasm32"))]
456        self.register(Arc::new(
457            crate::server::guacamole::actions::GuacamoleProtocol::new(),
458        ));
459        #[cfg(not(target_arch = "wasm32"))]
460        self.register(Arc::new(
461            crate::server::activitypub::actions::ActivityPubProtocol::new(),
462        ));
463        #[cfg(not(target_arch = "wasm32"))]
464        self.register(Arc::new(
465            crate::server::stratum::actions::StratumProtocol::new(),
466        ));
467        #[cfg(not(target_arch = "wasm32"))]
468        self.register(Arc::new(
469            crate::server::stratum_v2::actions::StratumV2Protocol::new(),
470        ));
471        #[cfg(not(target_arch = "wasm32"))]
472        self.register(Arc::new(
473            crate::server::pulsar::actions::PulsarProtocol::new(),
474        ));
475        #[cfg(not(target_arch = "wasm32"))]
476        self.register(Arc::new(crate::server::tr069::actions::Tr069Protocol::new()));
477        #[cfg(not(target_arch = "wasm32"))]
478        self.register(Arc::new(crate::server::sse::actions::SseProtocol::new()));
479        #[cfg(not(target_arch = "wasm32"))]
480        self.register(Arc::new(
481            crate::server::wsdiscovery::actions::WsDiscoveryProtocol::new(),
482        ));
483        #[cfg(not(target_arch = "wasm32"))]
484        self.register(Arc::new(crate::server::onvif::actions::OnvifProtocol::new()));
485        #[cfg(not(target_arch = "wasm32"))]
486        self.register(Arc::new(
487            crate::server::kerberos::actions::KerberosProtocol::new(),
488        ));
489
490        #[cfg(not(target_arch = "wasm32"))]
491        self.register(Arc::new(
492            crate::server::netflow_v9::actions::NetflowV9Protocol::new(),
493        ));
494        #[cfg(not(target_arch = "wasm32"))]
495        self.register(Arc::new(crate::server::ipfix::actions::IpfixProtocol::new()));
496        #[cfg(not(target_arch = "wasm32"))]
497        self.register(Arc::new(
498            crate::server::prometheus_remote_write::actions::PrometheusRemoteWriteProtocol::new(),
499        ));
500        #[cfg(not(target_arch = "wasm32"))]
501        self.register(Arc::new(crate::server::sflow::actions::SflowProtocol::new()));
502        #[cfg(not(target_arch = "wasm32"))]
503        self.register(Arc::new(crate::server::loki::actions::LokiProtocol::new()));
504        #[cfg(not(target_arch = "wasm32"))]
505        self.register(Arc::new(
506            crate::server::influxdb::actions::InfluxDbProtocol::new(),
507        ));
508
509        #[cfg(not(target_arch = "wasm32"))]
510        self.register(Arc::new(
511            crate::server::fluent_forward::actions::FluentForwardProtocol::new(),
512        ));
513
514        #[cfg(not(target_arch = "wasm32"))]
515        self.register(Arc::new(crate::server::gelf::actions::GelfProtocol::new()));
516
517        #[cfg(not(target_arch = "wasm32"))]
518        self.register(Arc::new(
519            crate::server::graphite::actions::GraphiteProtocol::new(),
520        ));
521
522        #[cfg(not(target_arch = "wasm32"))]
523        self.register(Arc::new(
524            crate::server::statsd::actions::StatsdProtocol::new(),
525        ));
526
527        self.register(Arc::new(crate::server::SyslogProtocol::new()));
528
529        #[cfg(not(target_arch = "wasm32"))]
530        self.register(Arc::new(crate::server::SshProtocol::new()));
531
532        #[cfg(unix)]
533        self.register(Arc::new(crate::server::SshAgentProtocol::new()));
534
535        self.register(Arc::new(crate::server::SvnProtocol::new()));
536
537        #[cfg(not(target_arch = "wasm32"))]
538        self.register(Arc::new(crate::server::IrcProtocol::new()));
539
540        #[cfg(unix)]
541        self.register(Arc::new(crate::server::XmppProtocol::new()));
542
543        self.register(Arc::new(crate::server::TelnetProtocol::new()));
544
545        #[cfg(not(target_arch = "wasm32"))]
546        self.register(Arc::new(crate::server::SmtpProtocol::new()));
547
548        self.register(Arc::new(crate::server::FtpProtocol::new()));
549
550        #[cfg(not(target_arch = "wasm32"))]
551        self.register(Arc::new(crate::server::ImapProtocol::new()));
552
553        self.register(Arc::new(crate::server::Pop3Protocol::new()));
554
555        self.register(Arc::new(crate::server::NntpProtocol::new()));
556
557        #[cfg(not(target_arch = "wasm32"))]
558        self.register(Arc::new(crate::server::MqttProtocol::new()));
559
560        self.register(Arc::new(crate::server::ModbusProtocol::new()));
561
562        self.register(Arc::new(crate::server::CoapProtocol::new()));
563
564        #[cfg(not(target_arch = "wasm32"))]
565        self.register(Arc::new(crate::server::AmqpProtocol::new()));
566
567        #[cfg(not(target_arch = "wasm32"))]
568        self.register(Arc::new(crate::server::MdnsProtocol::new()));
569
570        #[cfg(not(target_arch = "wasm32"))]
571        self.register(Arc::new(crate::server::LdapProtocol::new()));
572
573        #[cfg(not(target_arch = "wasm32"))]
574        {
575            use crate::server::connection::ConnectionId;
576            use tokio::sync::mpsc;
577            let (tx, _rx) = mpsc::unbounded_channel();
578            self.register(Arc::new(crate::server::MysqlProtocol::new(
579                ConnectionId::new(0), // Placeholder for protocol registry
580                dummy_state.clone(),
581                tx,
582            )));
583        }
584
585        {
586            use crate::server::connection::ConnectionId;
587            use tokio::sync::mpsc;
588            let (tx, _rx) = mpsc::unbounded_channel();
589            self.register(Arc::new(crate::server::SnowflakeProtocol::new(
590                ConnectionId::new(0), // Placeholder for protocol registry
591                dummy_state.clone(),
592                tx,
593            )));
594        }
595
596        {
597            use crate::server::connection::ConnectionId;
598            use tokio::sync::mpsc;
599            let (tx, _rx) = mpsc::unbounded_channel();
600            self.register(Arc::new(crate::server::Db2Protocol::new(
601                ConnectionId::new(0), // Placeholder for protocol registry
602                dummy_state.clone(),
603                tx,
604            )));
605        }
606
607        #[cfg(not(target_arch = "wasm32"))]
608        {
609            use crate::server::connection::ConnectionId;
610            use tokio::sync::mpsc;
611            let (tx, _rx) = mpsc::unbounded_channel();
612            self.register(Arc::new(crate::server::MssqlProtocol::new(
613                ConnectionId::new(0), // Placeholder for protocol registry
614                dummy_state.clone(),
615                tx,
616            )));
617        }
618
619        #[cfg(not(target_arch = "wasm32"))]
620        {
621            use crate::server::connection::ConnectionId;
622            use tokio::sync::mpsc;
623            let (tx, _rx) = mpsc::unbounded_channel();
624            self.register(Arc::new(crate::server::PostgresqlProtocol::new(
625                ConnectionId::new(0), // Placeholder for protocol registry
626                dummy_state.clone(),
627                tx,
628            )));
629        }
630
631        #[cfg(not(target_arch = "wasm32"))]
632        {
633            use crate::server::connection::ConnectionId;
634            use tokio::sync::mpsc;
635            let (tx, _rx) = mpsc::unbounded_channel();
636            self.register(Arc::new(crate::server::RedisProtocol::new(
637                ConnectionId::new(0), // Placeholder for protocol registry
638                dummy_state.clone(),
639                tx,
640            )));
641        }
642
643        self.register(Arc::new(crate::server::MemcachedProtocol::new()));
644
645        self.register(Arc::new(crate::server::RadiusProtocol::new()));
646
647        self.register(Arc::new(crate::server::RssProtocol::new()));
648
649        #[cfg(not(target_arch = "wasm32"))]
650        {
651            use crate::server::connection::ConnectionId;
652            use tokio::sync::mpsc;
653            let (tx, _rx) = mpsc::unbounded_channel();
654            self.register(Arc::new(crate::server::CassandraProtocol::new(
655                ConnectionId::new(0), // Placeholder for protocol registry
656                dummy_state.clone(),
657                tx,
658            )));
659        }
660
661        {
662            use crate::server::connection::ConnectionId;
663            use tokio::sync::mpsc;
664            let (tx, _rx) = mpsc::unbounded_channel();
665            self.register(Arc::new(crate::server::MongodbProtocol::new(
666                ConnectionId::new(0), // Placeholder for protocol registry
667                dummy_state.clone(),
668                tx,
669            )));
670        }
671
672        #[cfg(not(target_arch = "wasm32"))]
673        self.register(Arc::new(crate::server::DynamoProtocol::new()));
674
675        #[cfg(not(target_arch = "wasm32"))]
676        self.register(Arc::new(crate::server::S3Protocol::new()));
677
678        #[cfg(not(target_arch = "wasm32"))]
679        self.register(Arc::new(crate::server::SqsProtocol::new()));
680
681        self.register(Arc::new(crate::server::ElasticsearchProtocol::new()));
682
683        #[cfg(not(target_arch = "wasm32"))]
684        self.register(Arc::new(crate::server::CouchDbProtocol::new()));
685
686        self.register(Arc::new(crate::server::YarnProtocol::new()));
687
688        self.register(Arc::new(crate::server::SparkProtocol::new()));
689
690        self.register(Arc::new(crate::server::NpmProtocol::new()));
691
692        self.register(Arc::new(crate::server::OciRegistryProtocol::new()));
693
694        self.register(Arc::new(crate::server::KubernetesProtocol::new()));
695
696        #[cfg(not(target_arch = "wasm32"))]
697        self.register(Arc::new(crate::server::PrometheusProtocol::new()));
698
699        #[cfg(not(target_arch = "wasm32"))]
700        self.register(Arc::new(crate::server::OtlpProtocol::new()));
701
702        #[cfg(not(target_arch = "wasm32"))]
703        self.register(Arc::new(crate::server::DockerProtocol::new()));
704
705        #[cfg(not(target_arch = "wasm32"))]
706        self.register(Arc::new(crate::server::VaultProtocol::new()));
707
708        #[cfg(not(target_arch = "wasm32"))]
709        self.register(Arc::new(crate::server::BoltProtocol::new()));
710
711        #[cfg(not(target_arch = "wasm32"))]
712        self.register(Arc::new(crate::server::IppProtocol::new()));
713
714        #[cfg(not(target_arch = "wasm32"))]
715        self.register(Arc::new(crate::server::WebDavProtocol::new()));
716
717        #[cfg(unix)]
718        self.register(Arc::new(crate::server::NfsProtocol::new()));
719
720        #[cfg(unix)]
721        self.register(Arc::new(crate::server::NfcServerProtocol));
722
723        self.register(Arc::new(crate::server::SmbProtocol::new()));
724
725        #[cfg(unix)]
726        self.register(Arc::new(crate::server::ProxyProtocol::new()));
727
728        #[cfg(not(target_arch = "wasm32"))]
729        self.register(Arc::new(crate::server::Socks5Protocol::new()));
730
731        #[cfg(unix)]
732        self.register(Arc::new(crate::server::WireguardProtocol::new()));
733
734        #[cfg(unix)]
735        self.register(Arc::new(crate::server::OpenvpnProtocol::new()));
736
737        #[cfg(not(target_arch = "wasm32"))]
738        self.register(Arc::new(crate::server::IpsecProtocol::new()));
739
740        #[cfg(unix)]
741        self.register(Arc::new(crate::server::StunProtocol::new()));
742
743        #[cfg(unix)]
744        self.register(Arc::new(crate::server::TurnProtocol::new()));
745
746        #[cfg(not(target_arch = "wasm32"))]
747        self.register(Arc::new(crate::server::WebRtcProtocol::new()));
748
749        #[cfg(not(target_arch = "wasm32"))]
750        self.register(Arc::new(crate::server::WebRtcSignalingProtocol::new()));
751
752        #[cfg(not(target_arch = "wasm32"))]
753        self.register(Arc::new(crate::server::WebSocketProtocol::new()));
754
755        self.register(Arc::new(crate::server::SipProtocol::new()));
756        #[cfg(not(target_arch = "wasm32"))]
757        self.register(Arc::new(
758            crate::server::h323::actions::H323GatekeeperProtocol::new(),
759        ));
760        #[cfg(not(target_arch = "wasm32"))]
761        self.register(Arc::new(
762            crate::server::h323::actions::H323EndpointProtocol::new(),
763        ));
764
765        self.register(Arc::new(crate::server::RtpProtocol::new()));
766
767        self.register(Arc::new(crate::server::RtspProtocol::new()));
768
769        self.register(Arc::new(crate::server::HlsProtocol::new()));
770
771        self.register(Arc::new(crate::server::BgpProtocol::new()));
772
773        #[cfg(unix)]
774        self.register(Arc::new(crate::server::OspfProtocol::new()));
775
776        #[cfg(all(unix, feature = "libpcap"))]
777        self.register(Arc::new(crate::server::IsisProtocol::new()));
778
779        #[cfg(not(target_arch = "wasm32"))]
780        self.register(Arc::new(crate::server::RipProtocol::new()));
781
782        self.register(Arc::new(crate::server::BitcoinProtocol::new()));
783        #[cfg(not(target_arch = "wasm32"))]
784        self.register(Arc::new(
785            crate::server::devp2p::actions::Devp2pProtocol::new(),
786        ));
787        #[cfg(not(target_arch = "wasm32"))]
788        self.register(Arc::new(crate::server::NostrProtocol::new()));
789
790        #[cfg(not(target_arch = "wasm32"))]
791        self.register(Arc::new(crate::server::McpProtocol::new()));
792
793        #[cfg(not(target_arch = "wasm32"))]
794        self.register(Arc::new(crate::server::OpenAiProtocol::new()));
795
796        self.register(Arc::new(crate::server::OllamaProtocol::new()));
797
798        self.register(Arc::new(crate::server::OAuth2Protocol::new()));
799
800        self.register(Arc::new(crate::server::JsonRpcProtocol::new()));
801
802        #[cfg(not(target_arch = "wasm32"))]
803        self.register(Arc::new(crate::server::XmlRpcProtocol::new()));
804
805        #[cfg(not(target_arch = "wasm32"))]
806        self.register(Arc::new(crate::server::GrpcProtocol::new()));
807        #[cfg(not(target_arch = "wasm32"))]
808        self.register(Arc::new(
809            crate::server::grpc_web::actions::GrpcWebProtocol::new(),
810        ));
811        #[cfg(not(target_arch = "wasm32"))]
812        self.register(Arc::new(crate::server::gnmi::actions::GnmiProtocol::new()));
813        #[cfg(not(target_arch = "wasm32"))]
814        self.register(Arc::new(
815            crate::server::connect_rpc::actions::ConnectRpcProtocol::new(),
816        ));
817
818        #[cfg(not(target_arch = "wasm32"))]
819        self.register(Arc::new(crate::server::EtcdProtocol::new()));
820
821        #[cfg(not(target_arch = "wasm32"))]
822        self.register(Arc::new(crate::server::ZookeeperProtocol::new()));
823
824        #[cfg(unix)]
825        self.register(Arc::new(crate::server::TorRelayProtocol::new()));
826
827        self.register(Arc::new(crate::server::VncProtocol::new()));
828
829        self.register(Arc::new(crate::server::ReverseShellProtocol::new()));
830
831        self.register(Arc::new(crate::server::RdpProtocol::new()));
832        #[cfg(not(target_arch = "wasm32"))]
833        self.register(Arc::new(crate::server::spice::actions::SpiceProtocol::new()));
834
835        self.register(Arc::new(crate::server::OpenApiProtocol::new()));
836
837        self.register(Arc::new(crate::server::OpenIdProtocol::new()));
838
839        #[cfg(not(target_arch = "wasm32"))]
840        self.register(Arc::new(crate::server::GitProtocol::new()));
841
842        self.register(Arc::new(crate::server::MercurialProtocol::new()));
843
844        self.register(Arc::new(crate::server::KafkaProtocol::new()));
845
846        #[cfg(not(target_arch = "wasm32"))]
847        self.register(Arc::new(crate::server::Http3Protocol::new()));
848
849        #[cfg(not(target_arch = "wasm32"))]
850        self.register(Arc::new(crate::server::QuicProtocol::new()));
851
852        self.register(Arc::new(crate::server::TorrentTrackerProtocol::new()));
853
854        self.register(Arc::new(crate::server::TorrentDhtProtocol::new()));
855
856        self.register(Arc::new(crate::server::TorrentPeerProtocol::new()));
857
858        self.register(Arc::new(crate::server::TlsProtocol::new()));
859
860        self.register(Arc::new(crate::server::SamlIdpProtocol::new()));
861
862        self.register(Arc::new(crate::server::SamlSpProtocol::new()));
863
864        #[cfg(all(unix, feature = "libusb"))]
865        self.register(Arc::new(crate::server::UsbKeyboardProtocol::new()));
866
867        #[cfg(all(unix, feature = "libusb"))]
868        self.register(Arc::new(crate::server::UsbMouseProtocol::new()));
869
870        #[cfg(all(unix, feature = "libusb"))]
871        self.register(Arc::new(crate::server::UsbSerialProtocol::new()));
872
873        #[cfg(all(unix, feature = "libusb"))]
874        self.register(Arc::new(crate::server::UsbMscProtocol::new()));
875
876        #[cfg(all(unix, feature = "libusb"))]
877        self.register(Arc::new(crate::server::UsbFido2Protocol::new()));
878
879        #[cfg(all(unix, feature = "libusb"))]
880        self.register(Arc::new(crate::server::UsbSmartCardProtocol::new()));
881
882        #[cfg(all(unix, feature = "bluetooth"))]
883        self.register(Arc::new(crate::server::BluetoothBleProtocol::new()));
884
885        #[cfg(all(unix, feature = "bluetooth"))]
886        self.register(Arc::new(crate::server::BluetoothBleKeyboardProtocol::new()));
887
888        #[cfg(all(unix, feature = "bluetooth"))]
889        self.register(Arc::new(crate::server::BluetoothBleMouseProtocol::new()));
890
891        #[cfg(all(unix, feature = "bluetooth"))]
892        self.register(Arc::new(crate::server::BluetoothBleBeaconProtocol::new()));
893
894        #[cfg(all(unix, feature = "bluetooth"))]
895        self.register(Arc::new(crate::server::BluetoothBleRemoteProtocol::new()));
896
897        #[cfg(all(unix, feature = "bluetooth"))]
898        self.register(Arc::new(crate::server::BluetoothBleBatteryProtocol::new()));
899
900        #[cfg(all(unix, feature = "bluetooth"))]
901        self.register(Arc::new(crate::server::BluetoothBleHeartRateProtocol::new()));
902
903        #[cfg(all(unix, feature = "bluetooth"))]
904        self.register(Arc::new(
905            crate::server::BluetoothBleThermometerProtocol::new(),
906        ));
907
908        #[cfg(all(unix, feature = "bluetooth"))]
909        self.register(Arc::new(
910            crate::server::BluetoothBleEnvironmentalProtocol::new(),
911        ));
912
913        #[cfg(all(unix, feature = "bluetooth"))]
914        self.register(Arc::new(crate::server::BluetoothBleProximityProtocol::new()));
915
916        #[cfg(all(unix, feature = "bluetooth"))]
917        self.register(Arc::new(crate::server::BluetoothBleGamepadProtocol::new()));
918
919        #[cfg(all(unix, feature = "bluetooth"))]
920        self.register(Arc::new(crate::server::BluetoothBlePresenterProtocol::new()));
921
922        #[cfg(all(unix, feature = "bluetooth"))]
923        self.register(Arc::new(
924            crate::server::BluetoothBleFileTransferProtocol::new(),
925        ));
926
927        #[cfg(all(unix, feature = "bluetooth"))]
928        self.register(Arc::new(
929            crate::server::BluetoothBleDataStreamProtocol::new(),
930        ));
931
932        #[cfg(all(unix, feature = "bluetooth"))]
933        self.register(Arc::new(crate::server::BluetoothBleCyclingProtocol::new()));
934
935        #[cfg(all(unix, feature = "bluetooth"))]
936        self.register(Arc::new(crate::server::BluetoothBleRunningProtocol::new()));
937
938        #[cfg(all(unix, feature = "bluetooth"))]
939        self.register(Arc::new(
940            crate::server::BluetoothBleWeightScaleProtocol::new(),
941        ));
942    }
943
944    /// Build keyword map for fast protocol parsing
945    fn build_keyword_map(&mut self) {
946        for (protocol_name, protocol) in &self.protocols {
947            // Add all protocol keywords
948            for keyword in protocol.keywords() {
949                let lower = keyword.to_lowercase();
950                // Store the hyphen/space-normalized form too; input is normalized
951                // before lookup, so a keyword declared as "ssh-agent" was otherwise
952                // unreachable from the input "ssh_agent".
953                let normalized = lower.replace(['-', ' '], "_");
954                self.keyword_map.insert(lower, protocol_name.clone());
955                self.keyword_map.insert(normalized, protocol_name.clone());
956            }
957
958            // Also add the full stack name as a keyword
959            // This allows parsing inputs like "eth>ip>tcp>http" or "ETH>IP>UDP>DNS"
960            let stack_name = protocol.stack_name().to_lowercase();
961            self.keyword_map.insert(stack_name, protocol_name.clone());
962        }
963    }
964
965    /// Get keyword overlaps between protocols
966    ///
967    /// Returns a vector of (keyword, protocols) tuples for keywords
968    /// that are claimed by multiple protocols.
969    pub fn get_keyword_overlaps(&self) -> Vec<(String, Vec<(String, String)>)> {
970        use std::collections::HashMap;
971
972        // Build a map: keyword (lowercase) -> Vec<(protocol_name, keyword_source)>
973        // keyword_source is either "keyword" or "stack_name"
974        let mut keyword_to_protocols: HashMap<String, Vec<(String, String)>> = HashMap::new();
975
976        for (protocol_name, protocol) in &self.protocols {
977            // Collect all keywords from keywords()
978            for keyword in protocol.keywords() {
979                let key = keyword.to_lowercase();
980                keyword_to_protocols
981                    .entry(key)
982                    .or_default()
983                    .push((protocol_name.clone(), format!("keyword '{}'", keyword)));
984            }
985
986            // Also collect the stack name as a keyword
987            let stack_name = protocol.stack_name();
988            let key = stack_name.to_lowercase();
989            keyword_to_protocols.entry(key).or_default().push((
990                protocol_name.clone(),
991                format!("stack_name '{}'", stack_name),
992            ));
993        }
994
995        // Find all keywords claimed by more than one DISTINCT protocol. A single
996        // protocol whose keyword equals its own stack_name contributes two entries
997        // for the same keyword — that is not an overlap and must not be reported.
998        let mut overlaps = Vec::new();
999        for (keyword, protocols) in &keyword_to_protocols {
1000            let distinct: std::collections::HashSet<&String> =
1001                protocols.iter().map(|(name, _)| name).collect();
1002            if distinct.len() > 1 {
1003                overlaps.push((keyword.clone(), protocols.clone()));
1004            }
1005        }
1006
1007        overlaps
1008    }
1009
1010    /// Validate that no two protocols share the same keyword
1011    ///
1012    /// This logs warnings for any overlapping keywords detected,
1013    /// but does not panic to allow the application to continue running.
1014    fn validate_keyword_uniqueness(&self) {
1015        let overlaps = self.get_keyword_overlaps();
1016
1017        // Overlaps between distinct protocols are a development-time advisory, not a
1018        // runtime problem (many are intentional family keywords like "bluetooth"/"usb"
1019        // that then narrow by a more specific keyword). Log at DEBUG so it lands in
1020        // netget.log for whoever cares without spamming the TUI on every startup; the
1021        // `test_keyword_overlaps` test is the enforcement point.
1022        if !overlaps.is_empty() {
1023            use tracing::debug;
1024            debug!("Keyword overlaps detected between protocols:");
1025
1026            for (keyword, protocols) in &overlaps {
1027                debug!("  Keyword '{}' is used by:", keyword);
1028                for (protocol_name, source) in protocols {
1029                    debug!("    - {} ({})", protocol_name, source);
1030                }
1031            }
1032
1033            debug!("Note: Each keyword should ideally be unique to a single protocol.");
1034            debug!(
1035                "      Run 'cargo test test_keyword_overlaps -- --ignored' to see all overlaps."
1036            );
1037        }
1038    }
1039
1040    /// Register a protocol implementation
1041    #[allow(dead_code)]
1042    fn register(&mut self, protocol: Arc<dyn Server>) {
1043        let protocol_name = protocol.protocol_name().to_string();
1044        self.protocols.insert(protocol_name, protocol);
1045    }
1046
1047    /// Get protocol implementation by protocol name
1048    pub fn get(&self, protocol_name: &str) -> Option<Arc<dyn Server>> {
1049        self.protocols.get(protocol_name).cloned()
1050    }
1051
1052    /// Resolve user/LLM-supplied input (a protocol name or keyword) to a
1053    /// registered protocol implementation.
1054    ///
1055    /// This is the recommended entry point for turning arbitrary protocol input
1056    /// (CLI args, `/load` scripts, MCP `start_server` calls, or an already
1057    /// canonical name such as a stored `ServerInfo::protocol_name`) into a
1058    /// protocol implementation. Unlike calling `get()` / `parse_from_str()`
1059    /// directly, a failure here distinguishes "this protocol exists but wasn't
1060    /// compiled into this build" from "this is not a real NetGet protocol at
1061    /// all", and offers a "did you mean" suggestion for likely typos — see
1062    /// [`ProtocolLookupError`].
1063    pub fn resolve(&self, input: &str) -> Result<Arc<dyn Server>, ProtocolLookupError> {
1064        // Exact canonical name first — handles a name already resolved by a
1065        // previous call (e.g. a stored `ServerInfo::protocol_name`).
1066        if let Some(p) = self.get(input) {
1067            return Ok(p);
1068        }
1069
1070        // Fall back to keyword / stack-name parsing (case-insensitive,
1071        // hyphen/space tolerant) against currently-compiled protocols.
1072        if let Some(name) = self.parse_from_str(input) {
1073            if let Some(p) = self.get(&name) {
1074                return Ok(p);
1075            }
1076        }
1077
1078        // Not found among compiled-in protocols. Check whether `input` names a
1079        // real NetGet protocol that simply wasn't compiled into this build.
1080        let input_norm = input.to_lowercase().replace(['-', ' '], "_");
1081        for (name, requires) in ALL_KNOWN_PROTOCOLS {
1082            let name_norm = name.to_lowercase().replace(['-', ' '], "_");
1083            if name_norm == input_norm {
1084                return Err(ProtocolLookupError::NotCompiled { name, requires });
1085            }
1086        }
1087
1088        // Truly unknown. Offer a "did you mean" suggestion via edit distance
1089        // against every known protocol name, compiled in or not.
1090        Err(ProtocolLookupError::Unknown {
1091            input: input.to_string(),
1092            suggestion: suggest_protocol_name(input, ALL_KNOWN_PROTOCOLS),
1093        })
1094    }
1095
1096    /// Parse protocol from user input string
1097    ///
1098    /// Attempts to match keywords from registered protocols.
1099    /// Returns protocol name if match found, None otherwise.
1100    pub fn parse_from_str(&self, input: &str) -> Option<String> {
1101        let input_lower = input.to_lowercase();
1102        // Normalize hyphens and spaces to underscores for consistent matching
1103        // e.g., "bluetooth-ble" -> "bluetooth_ble", "WebRTC Signaling" -> "webrtc_signaling"
1104        let input_normalized = input_lower.replace(['-', ' '], "_");
1105
1106        // First, try exact match with stack names (for LLM-generated responses)
1107        for (protocol_name, protocol) in &self.protocols {
1108            let stack_lower = protocol.stack_name().to_lowercase();
1109            let stack_normalized = stack_lower.replace(['-', ' '], "_");
1110            if input_lower == stack_lower
1111                || input_normalized == stack_lower
1112                || input_normalized == stack_normalized
1113            {
1114                return Some(protocol_name.clone());
1115            }
1116        }
1117
1118        // Second, try exact match with protocol names (for startup messages)
1119        for (protocol_name, protocol) in &self.protocols {
1120            let proto_lower = protocol.protocol_name().to_lowercase();
1121            let proto_normalized = proto_lower.replace(['-', ' '], "_");
1122            if input_lower == proto_lower
1123                || input_normalized == proto_lower
1124                || input_normalized == proto_normalized
1125            {
1126                return Some(protocol_name.clone());
1127            }
1128        }
1129
1130        // Try keyword matching with priority ordering
1131        // More specific protocols checked first to avoid substring collisions
1132
1133        // Priority 1: Check mDNS before DNS (avoid substring match)
1134        if let Some(stack) =
1135            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "mDNS")
1136        {
1137            return Some(stack);
1138        }
1139
1140        // Priority 2: Check IMAP before SMTP (more specific for mail/email)
1141        if let Some(stack) =
1142            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "IMAP")
1143        {
1144            return Some(stack);
1145        }
1146
1147        // Priority 2.5: Check SMTP before general loop (avoid hash order collisions)
1148        if let Some(stack) =
1149            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "SMTP")
1150        {
1151            return Some(stack);
1152        }
1153
1154        // Priority 2.7: Check SNMP before SSH-Agent (avoid "agent" substring match)
1155        if let Some(stack) =
1156            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "SNMP")
1157        {
1158            return Some(stack);
1159        }
1160
1161        // Priority 3: Check PostgreSQL before MySQL (avoid "sql" substring)
1162        if let Some(stack) =
1163            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "PostgreSQL")
1164        {
1165            return Some(stack);
1166        }
1167
1168        // Priority 4: Check XML-RPC and JSON-RPC before HTTP (avoid "http" substring in stack names)
1169        if let Some(stack) =
1170            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "XmlRPC")
1171        {
1172            return Some(stack);
1173        }
1174        if let Some(stack) =
1175            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "JsonRPC")
1176        {
1177            return Some(stack);
1178        }
1179
1180        // Priority 5: Check Proxy before HTTP (avoid "http" substring in "http proxy")
1181        if let Some(stack) =
1182            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "Proxy")
1183        {
1184            return Some(stack);
1185        }
1186
1187        // Priority 6: Check Tor protocols before TCP fallback
1188        if let Some(stack) =
1189            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "TorDirectory")
1190        {
1191            return Some(stack);
1192        }
1193        if let Some(stack) =
1194            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "TorRelay")
1195        {
1196            return Some(stack);
1197        }
1198
1199        // Priority 7: Check WebRTC Signaling before the generic WebSocket protocol.
1200        // "websocket signaling" is a strict superset of WebSocket's "websocket" keyword, and
1201        // the loop below iterates a HashMap, so without this the winner is whichever the hash
1202        // order happens to yield. Nothing is registered under this name unless the `webrtc`
1203        // feature is on, in which case the lookup simply misses.
1204        if let Some(stack) =
1205            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "WebRTC Signaling")
1206        {
1207            return Some(stack);
1208        }
1209
1210        // Priority 8: AWS cloud services before the generic loop and the HTTP fallback.
1211        // S3, SQS and DynamoDB are all HTTP/REST underneath, so their distinctive names
1212        // ("s3 bucket", "sqs queue", "dynamodb", …) must win over the plain "http" keyword
1213        // and over each other deterministically — the general loop below iterates a
1214        // HashMap and would otherwise let hash order decide "act as an S3 bucket over
1215        // http" between S3 and HTTP. Each check is a no-op if the feature is not compiled.
1216        // MQTT before the AWS block. MQTT stands for "Message Queuing Telemetry Transport"
1217        // and advertises "message queue telemetry"; SQS advertises the plain two-word
1218        // "message queue" deliberately. SQS is checked below, so without this the shorter and
1219        // *less* specific keyword won and "message queue telemetry transport" started an SQS
1220        // server. Note the general loop further down already resolves this correctly - it
1221        // takes the longest matching keyword - so it is these priority blocks, not the
1222        // matching rule, that create the collision they exist to prevent.
1223        if let Some(stack) =
1224            self.match_protocol_by_any_keyword_with_boundaries(&input_lower, "MQTT")
1225        {
1226            return Some(stack);
1227        }
1228
1229        for aws_service in ["S3", "SQS", "DynamoDB"] {
1230            if let Some(stack) =
1231                self.match_protocol_by_any_keyword_with_boundaries(&input_lower, aws_service)
1232            {
1233                return Some(stack);
1234            }
1235        }
1236
1237        // For all other protocols, check ALL keywords from each protocol with word
1238        // boundaries -- longest matching keyword first.
1239        //
1240        // This used to return the first hit while iterating a HashMap, whose order is
1241        // unspecified, so a input matching two protocols' keywords resolved to
1242        // whichever the hash order yielded and could differ between runs or feature
1243        // sets. That is what the hand-maintained priority ladder above exists to work
1244        // around, one collision at a time. Preferring the longest keyword makes the
1245        // more specific protocol win on its own, and the name tie-break makes the
1246        // answer identical on every run.
1247        let mut best: Option<(usize, &String)> = None;
1248        for (protocol_name, protocol) in &self.protocols {
1249            for keyword in protocol.keywords() {
1250                let keyword = keyword.to_lowercase();
1251                if !self.matches_with_word_boundary(&input_lower, &keyword) {
1252                    continue;
1253                }
1254                let better = match best {
1255                    None => true,
1256                    Some((best_len, best_name)) => {
1257                        keyword.len() > best_len
1258                            || (keyword.len() == best_len && protocol_name < best_name)
1259                    }
1260                };
1261                if better {
1262                    best = Some((keyword.len(), protocol_name));
1263                }
1264            }
1265        }
1266        if let Some((_, protocol_name)) = best {
1267            return Some(protocol_name.clone());
1268        }
1269
1270        // Default to TCP if "tcp", "raw", "ftp", "custom" found
1271        if input_lower.contains("tcp")
1272            || input_lower.contains("raw")
1273            || input_lower.contains("ftp")
1274            || input_lower.contains("custom")
1275        {
1276            return Some("TCP".to_string());
1277        }
1278
1279        None
1280    }
1281
1282    /// Check if input matches keyword with word boundaries
1283    ///
1284    /// Matches if keyword appears as a complete word or phrase (not substring).
1285    /// Examples:
1286    ///   - "mail" matches "mail server" ✓
1287    ///   - "mail" does NOT match "email" ✗
1288    ///   - "ai" does NOT match "mail" ✗
1289    fn matches_with_word_boundary(&self, input: &str, keyword: &str) -> bool {
1290        // Handle multi-word keywords (e.g., "mail server", "ssh keys")
1291        if keyword.contains(' ') {
1292            return input.contains(keyword);
1293        }
1294
1295        // For single-word keywords, check word boundaries
1296        let input_bytes = input.as_bytes();
1297
1298        if let Some(pos) = input.find(keyword) {
1299            // Check character before keyword
1300            let before_ok = if pos == 0 {
1301                true
1302            } else {
1303                let c = input_bytes[pos - 1];
1304                !c.is_ascii_alphanumeric() && c != b'_' && c != b'-'
1305            };
1306
1307            // Check character after keyword
1308            let end_pos = pos + keyword.len();
1309            let after_ok = if end_pos >= input.len() {
1310                true
1311            } else {
1312                let c = input_bytes[end_pos];
1313                !c.is_ascii_alphanumeric() && c != b'_' && c != b'-'
1314            };
1315
1316            return before_ok && after_ok;
1317        }
1318
1319        false
1320    }
1321
1322    /// Match a specific protocol by checking if input contains ANY of its keywords (with word boundaries)
1323    ///
1324    /// This method checks ALL keywords defined by the protocol, not just a hardcoded subset.
1325    /// Returns the protocol name if any keyword matches.
1326    fn match_protocol_by_any_keyword_with_boundaries(
1327        &self,
1328        input_lower: &str,
1329        protocol_name: &str,
1330    ) -> Option<String> {
1331        if let Some(protocol) = self.protocols.get(protocol_name) {
1332            for keyword in protocol.keywords() {
1333                if self.matches_with_word_boundary(input_lower, &keyword.to_lowercase()) {
1334                    return Some(protocol_name.to_string());
1335                }
1336            }
1337        }
1338        None
1339    }
1340
1341    /// Get list of available protocol names
1342    pub fn available_protocols(&self) -> Vec<&'static str> {
1343        let mut protocols: Vec<&'static str> =
1344            self.protocols.values().map(|p| p.protocol_name()).collect();
1345        // Sort alphabetically for deterministic output
1346        protocols.sort();
1347        protocols
1348    }
1349
1350    /// Get stack name by protocol name (e.g., "HTTP" -> "ETH>IP>TCP>HTTP")
1351    pub fn stack_name_by_protocol(&self, protocol_name: &str) -> Option<&'static str> {
1352        self.get(protocol_name).map(|p| p.stack_name())
1353    }
1354
1355    /// Get metadata for a protocol by name
1356    pub fn metadata(&self, protocol_name: &str) -> Option<ProtocolMetadataV2> {
1357        self.get(protocol_name).map(|p| p.metadata())
1358    }
1359
1360    /// Why a server of this protocol can only answer its peers, when the protocol declares
1361    /// that — see [`ProtocolMetadataV2::request_only`]. `None` for anything else, including a
1362    /// protocol this build does not compile.
1363    pub fn request_only_reason(&self, protocol_name: &str) -> Option<&'static str> {
1364        self.resolve(protocol_name)
1365            .ok()
1366            .and_then(|p| p.metadata().request_only)
1367    }
1368
1369    /// Get all registered protocols with their metadata
1370    pub fn all_protocols(&self) -> Vec<(String, Arc<dyn Server>)> {
1371        self.protocols
1372            .iter()
1373            .map(|(name, protocol)| (name.clone(), Arc::clone(protocol)))
1374            .collect()
1375    }
1376
1377    /// Get protocols that are excluded due to missing dependencies
1378    ///
1379    /// Returns a map of protocol name -> list of missing dependencies
1380    pub fn get_excluded_protocols(
1381        &self,
1382        caps: &crate::privilege::SystemCapabilities,
1383    ) -> HashMap<String, Vec<super::dependencies::ProtocolDependency>> {
1384        let mut excluded = HashMap::new();
1385
1386        for (protocol_name, protocol) in &self.protocols {
1387            let dependencies = protocol.get_dependencies();
1388            let mut missing = Vec::new();
1389
1390            for dep in dependencies {
1391                if !dep.is_available(caps) {
1392                    missing.push(dep);
1393                }
1394            }
1395
1396            if !missing.is_empty() {
1397                excluded.insert(protocol_name.clone(), missing);
1398            }
1399        }
1400
1401        excluded
1402    }
1403
1404    /// Protocols whose *default* port needs privilege this process does not have.
1405    ///
1406    /// These are **not** excluded — they run on any unprivileged port, and that is
1407    /// the whole point of listing them separately. Returns protocol name -> default
1408    /// port, so a caller can say "DNS defaults to 53; use 5353" instead of dropping
1409    /// DNS from the catalogue, which is what used to happen.
1410    ///
1411    /// Empty when the process can bind privileged ports (root, or CAP_NET_BIND_SERVICE).
1412    pub fn privileged_default_ports(
1413        &self,
1414        caps: &crate::privilege::SystemCapabilities,
1415    ) -> Vec<(String, u16)> {
1416        if caps.can_bind_privileged_ports {
1417            return Vec::new();
1418        }
1419        let mut out: Vec<(String, u16)> = self
1420            .protocols
1421            .iter()
1422            .filter_map(
1423                |(name, protocol)| match protocol.metadata().privilege_requirement {
1424                    crate::protocol::metadata::PrivilegeRequirement::PrivilegedPort(port) => {
1425                        Some((name.clone(), port))
1426                    }
1427                    _ => None,
1428                },
1429            )
1430            .collect();
1431        out.sort();
1432        out
1433    }
1434
1435    /// Get protocols that are available (have all dependencies met)
1436    ///
1437    /// Returns a list of protocol names that can be used
1438    pub fn get_available_protocols(
1439        &self,
1440        caps: &crate::privilege::SystemCapabilities,
1441    ) -> Vec<String> {
1442        let excluded = self.get_excluded_protocols(caps);
1443
1444        self.protocols
1445            .keys()
1446            .filter(|name| !excluded.contains_key(*name))
1447            .cloned()
1448            .collect()
1449    }
1450
1451    /// Check if a specific protocol is available (has all dependencies met)
1452    pub fn is_protocol_available(
1453        &self,
1454        protocol_name: &str,
1455        caps: &crate::privilege::SystemCapabilities,
1456    ) -> bool {
1457        if let Some(protocol) = self.get(protocol_name) {
1458            let dependencies = protocol.get_dependencies();
1459            dependencies.iter().all(|dep| dep.is_available(caps))
1460        } else {
1461            false
1462        }
1463    }
1464}
1465
1466/// All protocol names known to the netget codebase, paired with what a build needs to
1467/// include each one ("a native build (not the browser)", "the `libpcap` feature, on Unix",
1468/// …) — independent of what *this* build includes. This lets [`ServerRegistry::resolve`] tell
1469/// "protocol exists but wasn't compiled in" apart from "no such protocol", and say why.
1470///
1471/// Kept manually in sync with the platform gates on the registrations in
1472/// `register_protocols()` above. This is the one acceptable central touchpoint
1473/// per CLAUDE.md's decentralization policy (it plays the same role as the
1474/// `register()` calls themselves) — when adding a new protocol, add its
1475/// (canonical name, requirement) pair here too.
1476pub(crate) const ALL_KNOWN_PROTOCOLS: &[(&str, &str)] = &[
1477    ("TCP", "nothing (every build has it)"),
1478    ("SOCKET_FILE", "a Unix build (macOS or Linux)"),
1479    // The other three local-descriptor protocols. Without these, a build that does not compile
1480    // them answers `Unknown protocol: 'pty'. Did you mean 'NTP'?` instead of saying the protocol
1481    // exists but is not compiled in - the table is what tells those two cases apart.
1482    ("PTY", "a Unix build (macOS or Linux)"),
1483    ("STDIO", "a Unix build (macOS or Linux)"),
1484    ("NAMED_PIPE", "a Unix build (macOS or Linux)"),
1485    ("HTTP", "nothing (every build has it)"),
1486    ("HTTP2", "nothing (every build has it)"),
1487    ("PyPI", "nothing (every build has it)"),
1488    ("Maven", "nothing (every build has it)"),
1489    ("UDP", "nothing (every build has it)"),
1490    ("DataLink", "the `libpcap` feature, on Unix"),
1491    ("ARP", "the `libpcap` feature, on Unix"),
1492    ("ICMP", "a Unix build (macOS or Linux)"),
1493    ("NMDC Peer", "a native build (not the browser)"),
1494    ("ADC", "a native build (not the browser)"),
1495    ("ADC Peer", "a native build (not the browser)"),
1496    ("Soulseek", "a native build (not the browser)"),
1497    ("Soulseek Peer", "a native build (not the browser)"),
1498    ("Gnutella", "a native build (not the browser)"),
1499    ("DC", "nothing (every build has it)"),
1500    ("DNS", "a native build (not the browser)"),
1501    ("DoT", "a native build (not the browser)"),
1502    ("DoQ", "a native build (not the browser)"),
1503    ("DoH", "a native build (not the browser)"),
1504    ("DHCP", "nothing (every build has it)"),
1505    ("BOOTP", "nothing (every build has it)"),
1506    ("NTP", "a native build (not the browser)"),
1507    ("TFTP", "nothing (every build has it)"),
1508    ("NUT", "a native build (not the browser)"),
1509    ("WHOIS", "nothing (every build has it)"),
1510    ("NDP", "a Unix build (macOS or Linux)"),
1511    ("DHCPv6", "nothing (every build has it)"),
1512    ("TUN/TAP", "a Unix build (macOS or Linux)"),
1513    ("Raw IP", "a Unix build (macOS or Linux)"),
1514    ("GTP", "nothing (every build has it)"),
1515    ("M3UA", "a Unix build (macOS or Linux)"),
1516    ("NGAP", "a Unix build (macOS or Linux)"),
1517    ("CAN", "a Unix build (macOS or Linux)"),
1518    ("LLDP", "the `libpcap` feature, on Unix"),
1519    ("CDP", "the `libpcap` feature, on Unix"),
1520    ("STP", "the `libpcap` feature, on Unix"),
1521    ("VRRP", "a Unix build (macOS or Linux)"),
1522    ("HSRP", "nothing (every build has it)"),
1523    ("EAPOL", "the `libpcap` feature, on Unix"),
1524    ("Wake-on-LAN", "nothing (every build has it)"),
1525    ("NATS", "nothing (every build has it)"),
1526    ("STOMP", "nothing (every build has it)"),
1527    ("Ident", "nothing (every build has it)"),
1528    ("Gopher", "nothing (every build has it)"),
1529    ("DICT", "a native build (not the browser)"),
1530    ("Beanstalkd", "a native build (not the browser)"),
1531    ("Zabbix", "a native build (not the browser)"),
1532    ("Gearman", "a native build (not the browser)"),
1533    ("NSQ", "a native build (not the browser)"),
1534    ("Gemini", "a native build (not the browser)"),
1535    ("Finger", "nothing (every build has it)"),
1536    ("SSDP", "nothing (every build has it)"),
1537    ("LLMNR", "a native build (not the browser)"),
1538    ("NetBIOS-NS", "nothing (every build has it)"),
1539    ("SNMP", "nothing (every build has it)"),
1540    ("IGMP", "a Unix build (macOS or Linux)"),
1541    ("Syslog", "nothing (every build has it)"),
1542    ("FluentForward", "a native build (not the browser)"),
1543    ("NETFLOWV9", "a native build (not the browser)"),
1544    ("NETFLOW-V9", "a native build (not the browser)"),
1545    ("NETFLOW_V9", "a native build (not the browser)"),
1546    ("IPFIX", "a native build (not the browser)"),
1547    ("IPFIX-UDP", "a native build (not the browser)"),
1548    ("PrometheusRemoteWrite", "a native build (not the browser)"),
1549    ("RemoteWrite", "a native build (not the browser)"),
1550    ("sFlow", "a native build (not the browser)"),
1551    ("sFlow-v5", "a native build (not the browser)"),
1552    ("Loki", "a native build (not the browser)"),
1553    ("LokiPush", "a native build (not the browser)"),
1554    ("InfluxDB", "a native build (not the browser)"),
1555    ("InfluxDB2", "a native build (not the browser)"),
1556    ("GELF", "a native build (not the browser)"),
1557    ("Graylog", "a native build (not the browser)"),
1558    ("Graphite", "a native build (not the browser)"),
1559    ("StatsD", "a native build (not the browser)"),
1560    ("DogStatsD", "a native build (not the browser)"),
1561    ("SSH", "a native build (not the browser)"),
1562    ("SSH Agent", "a Unix build (macOS or Linux)"),
1563    ("SVN", "nothing (every build has it)"),
1564    ("IRC", "a native build (not the browser)"),
1565    ("XMPP", "a Unix build (macOS or Linux)"),
1566    ("Telnet", "nothing (every build has it)"),
1567    ("SMTP", "a native build (not the browser)"),
1568    ("FTP", "nothing (every build has it)"),
1569    ("IMAP", "a native build (not the browser)"),
1570    ("NNTP", "nothing (every build has it)"),
1571    ("MQTT", "a native build (not the browser)"),
1572    ("Modbus", "nothing (every build has it)"),
1573    ("CoAP", "nothing (every build has it)"),
1574    ("AMQP", "a native build (not the browser)"),
1575    ("mDNS", "a native build (not the browser)"),
1576    ("LDAP", "a native build (not the browser)"),
1577    ("MySQL", "a native build (not the browser)"),
1578    ("MSSQL", "a native build (not the browser)"),
1579    ("Snowflake", "nothing (every build has it)"),
1580    ("Db2", "nothing (every build has it)"),
1581    ("PostgreSQL", "a native build (not the browser)"),
1582    ("Memcached", "nothing (every build has it)"),
1583    ("RADIUS", "nothing (every build has it)"),
1584    ("DIAMETER", "a native build (not the browser)"),
1585    ("Redis", "a native build (not the browser)"),
1586    ("RSS", "nothing (every build has it)"),
1587    ("Cassandra", "a native build (not the browser)"),
1588    ("MongoDB", "nothing (every build has it)"),
1589    ("DynamoDB", "a native build (not the browser)"),
1590    ("SQS", "a native build (not the browser)"),
1591    ("Elasticsearch", "nothing (every build has it)"),
1592    ("CouchDB", "a native build (not the browser)"),
1593    ("YARN", "nothing (every build has it)"),
1594    ("Spark", "nothing (every build has it)"),
1595    ("NPM", "nothing (every build has it)"),
1596    ("OCI-Registry", "nothing (every build has it)"),
1597    ("Kubernetes", "nothing (every build has it)"),
1598    ("Prometheus", "a native build (not the browser)"),
1599    ("OTLP", "a native build (not the browser)"),
1600    ("Docker", "a native build (not the browser)"),
1601    ("Vault", "a native build (not the browser)"),
1602    ("Bolt", "a native build (not the browser)"),
1603    ("IPP", "a native build (not the browser)"),
1604    ("WebDAV", "a native build (not the browser)"),
1605    ("NFS", "a Unix build (macOS or Linux)"),
1606    ("nfc", "a Unix build (macOS or Linux)"),
1607    ("SMB", "nothing (every build has it)"),
1608    ("Proxy", "a Unix build (macOS or Linux)"),
1609    ("SOCKS5", "a native build (not the browser)"),
1610    ("WireGuard", "a Unix build (macOS or Linux)"),
1611    ("OpenVPN", "a Unix build (macOS or Linux)"),
1612    ("IPSec/IKEv2", "a native build (not the browser)"),
1613    ("TURN", "a Unix build (macOS or Linux)"),
1614    ("WebRTC Signaling", "a native build (not the browser)"),
1615    ("WebSocket", "a native build (not the browser)"),
1616    ("SIP", "nothing (every build has it)"),
1617    ("H323", "a native build (not the browser)"),
1618    ("H323-Gatekeeper", "a native build (not the browser)"),
1619    ("RTP", "nothing (every build has it)"),
1620    ("RTSP", "nothing (every build has it)"),
1621    ("HLS", "nothing (every build has it)"),
1622    ("ISIS", "the `libpcap` feature, on Unix"),
1623    ("RIP", "a native build (not the browser)"),
1624    ("Bitcoin P2P", "nothing (every build has it)"),
1625    ("devp2p", "a native build (not the browser)"),
1626    ("Nostr", "a native build (not the browser)"),
1627    ("MCP", "a native build (not the browser)"),
1628    ("OpenAI", "a native build (not the browser)"),
1629    ("Ollama", "nothing (every build has it)"),
1630    ("OAuth2", "nothing (every build has it)"),
1631    ("JSON-RPC", "nothing (every build has it)"),
1632    ("XML-RPC", "a native build (not the browser)"),
1633    ("gRPC", "a native build (not the browser)"),
1634    ("gRPC-Web", "a native build (not the browser)"),
1635    ("gNMI", "a native build (not the browser)"),
1636    ("ConnectRPC", "a native build (not the browser)"),
1637    ("TACACS", "a native build (not the browser)"),
1638    ("NETCONF", "a native build (not the browser)"),
1639    ("RPKI-RTR", "a native build (not the browser)"),
1640    ("RDAP", "a native build (not the browser)"),
1641    ("HL7", "a native build (not the browser)"),
1642    ("ICAP", "a native build (not the browser)"),
1643    ("OCPP", "a native build (not the browser)"),
1644    ("A2A", "a native build (not the browser)"),
1645    ("GraphQL", "a native build (not the browser)"),
1646    ("FastCGI", "a native build (not the browser)"),
1647    ("Redfish", "a native build (not the browser)"),
1648    ("SCIM", "a native build (not the browser)"),
1649    ("Socket.IO", "a native build (not the browser)"),
1650    ("CalDAV", "a native build (not the browser)"),
1651    ("CardDAV", "a native build (not the browser)"),
1652    ("DICOM", "a native build (not the browser)"),
1653    ("ACME", "a native build (not the browser)"),
1654    ("FIX", "a native build (not the browser)"),
1655    ("WAMP", "a native build (not the browser)"),
1656    ("RTMP", "a native build (not the browser)"),
1657    ("SRT", "a native build (not the browser)"),
1658    ("AMQP1", "a native build (not the browser)"),
1659    ("Thrift", "a native build (not the browser)"),
1660    ("BMP", "a native build (not the browser)"),
1661    ("MQTT-SN", "a native build (not the browser)"),
1662    ("NBD", "a native build (not the browser)"),
1663    ("ManageSieve", "a native build (not the browser)"),
1664    ("Zenoh", "a native build (not the browser)"),
1665    ("LwM2M", "a native build (not the browser)"),
1666    ("RESTCONF", "a native build (not the browser)"),
1667    ("WebTransport", "a native build (not the browser)"),
1668    ("JMAP", "a native build (not the browser)"),
1669    ("EPP", "a native build (not the browser)"),
1670    ("LMTP", "a native build (not the browser)"),
1671    ("LPD", "a native build (not the browser)"),
1672    ("Echo", "a native build (not the browser)"),
1673    ("Discard", "a native build (not the browser)"),
1674    ("Daytime", "a native build (not the browser)"),
1675    ("QOTD", "a native build (not the browser)"),
1676    ("Chargen", "a native build (not the browser)"),
1677    ("Time", "a native build (not the browser)"),
1678    ("RCON", "a native build (not the browser)"),
1679    ("A2S", "a native build (not the browser)"),
1680    ("Minecraft", "a native build (not the browser)"),
1681    ("9P", "a native build (not the browser)"),
1682    ("ZeroMQ", "a native build (not the browser)"),
1683    ("SMPP", "a native build (not the browser)"),
1684    ("ClickHouse", "a native build (not the browser)"),
1685    ("MessagePack-RPC", "a native build (not the browser)"),
1686    ("Zipkin", "a native build (not the browser)"),
1687    ("Cap'n Proto RPC", "a native build (not the browser)"),
1688    ("KNX/IP", "a native build (not the browser)"),
1689    ("Consul", "a native build (not the browser)"),
1690    ("Milter", "a native build (not the browser)"),
1691    ("Anthropic", "a native build (not the browser)"),
1692    ("RadSec", "a native build (not the browser)"),
1693    ("D-Bus", "a native build (not the browser)"),
1694    ("BFD", "a native build (not the browser)"),
1695    ("VXLAN", "a native build (not the browser)"),
1696    ("rsync", "a native build (not the browser)"),
1697    ("PFCP", "a native build (not the browser)"),
1698    ("Matrix", "a native build (not the browser)"),
1699    ("libp2p", "a native build (not the browser)"),
1700    ("SunRPC", "a native build (not the browser)"),
1701    ("Guacamole", "a native build (not the browser)"),
1702    ("ActivityPub", "a native build (not the browser)"),
1703    ("Stratum", "a native build (not the browser)"),
1704    ("Stratum V2", "a native build (not the browser)"),
1705    ("Apache Pulsar", "a native build (not the browser)"),
1706    ("TR-069 (CWMP)", "a native build (not the browser)"),
1707    ("SSE", "a native build (not the browser)"),
1708    ("WS-Discovery", "a native build (not the browser)"),
1709    ("ONVIF", "a native build (not the browser)"),
1710    ("Kerberos", "a native build (not the browser)"),
1711    ("etcd", "a native build (not the browser)"),
1712    ("ZooKeeper", "a native build (not the browser)"),
1713    ("Tor Relay", "a Unix build (macOS or Linux)"),
1714    ("VNC", "nothing (every build has it)"),
1715    ("Reverse Shell", "nothing (every build has it)"),
1716    ("RDP", "nothing (every build has it)"),
1717    ("SPICE", "a native build (not the browser)"),
1718    ("OpenAPI", "nothing (every build has it)"),
1719    ("OpenID", "nothing (every build has it)"),
1720    ("KAFKA", "nothing (every build has it)"),
1721    // Separate raw QUIC and RFC 9114 HTTP/3 transports.
1722    ("HTTP3", "a native build (not the browser)"),
1723    ("QUIC", "a native build (not the browser)"),
1724    ("Torrent-Tracker", "nothing (every build has it)"),
1725    ("Torrent-DHT", "nothing (every build has it)"),
1726    ("Torrent-Peer", "nothing (every build has it)"),
1727    ("TLS", "nothing (every build has it)"),
1728    ("SamlIdp", "nothing (every build has it)"),
1729    ("SamlSp", "nothing (every build has it)"),
1730    ("USB-Keyboard", "the `libusb` feature, on Unix"),
1731    ("USB-Mouse", "the `libusb` feature, on Unix"),
1732    ("USB-Serial", "the `libusb` feature, on Unix"),
1733    ("USB-MassStorage", "the `libusb` feature, on Unix"),
1734    ("usb-fido2", "the `libusb` feature, on Unix"),
1735    ("usb-smartcard", "the `libusb` feature, on Unix"),
1736    ("BLUETOOTH_BLE", "the `bluetooth` feature, on Unix"),
1737    ("BLUETOOTH_BLE_KEYBOARD", "the `bluetooth` feature, on Unix"),
1738    ("BLUETOOTH_BLE_MOUSE", "the `bluetooth` feature, on Unix"),
1739    ("BLUETOOTH_BLE_BEACON", "the `bluetooth` feature, on Unix"),
1740    ("BLUETOOTH_BLE_REMOTE", "the `bluetooth` feature, on Unix"),
1741    ("BLUETOOTH_BLE_BATTERY", "the `bluetooth` feature, on Unix"),
1742    (
1743        "BLUETOOTH_BLE_HEART_RATE",
1744        "the `bluetooth` feature, on Unix",
1745    ),
1746    (
1747        "BLUETOOTH_BLE_THERMOMETER",
1748        "the `bluetooth` feature, on Unix",
1749    ),
1750    (
1751        "BLUETOOTH_BLE_ENVIRONMENTAL",
1752        "the `bluetooth` feature, on Unix",
1753    ),
1754    (
1755        "BLUETOOTH_BLE_PROXIMITY",
1756        "the `bluetooth` feature, on Unix",
1757    ),
1758    ("BLUETOOTH_BLE_GAMEPAD", "the `bluetooth` feature, on Unix"),
1759    (
1760        "BLUETOOTH_BLE_PRESENTER",
1761        "the `bluetooth` feature, on Unix",
1762    ),
1763    (
1764        "BLUETOOTH_BLE_FILE_TRANSFER",
1765        "the `bluetooth` feature, on Unix",
1766    ),
1767    (
1768        "BLUETOOTH_BLE_DATA_STREAM",
1769        "the `bluetooth` feature, on Unix",
1770    ),
1771    ("BLUETOOTH_BLE_CYCLING", "the `bluetooth` feature, on Unix"),
1772    ("BLUETOOTH_BLE_RUNNING", "the `bluetooth` feature, on Unix"),
1773    (
1774        "BLUETOOTH_BLE_WEIGHT_SCALE",
1775        "the `bluetooth` feature, on Unix",
1776    ),
1777    ("POP3", "nothing (every build has it)"),
1778    ("S3", "a native build (not the browser)"),
1779    ("STUN", "a Unix build (macOS or Linux)"),
1780    ("WebRTC", "a native build (not the browser)"),
1781    ("BGP", "nothing (every build has it)"),
1782    ("OSPF", "a Unix build (macOS or Linux)"),
1783    ("Git", "a native build (not the browser)"),
1784    ("Mercurial", "nothing (every build has it)"),
1785];
1786
1787/// Why [`ServerRegistry::resolve`] could not find a protocol for the given input.
1788#[derive(Debug, Clone, PartialEq, Eq)]
1789pub enum ProtocolLookupError {
1790    /// A real NetGet protocol by this name exists, but this build does not include it
1791    /// (`requires` says what does).
1792    NotCompiled {
1793        name: &'static str,
1794        requires: &'static str,
1795    },
1796    /// No protocol by this name/keyword is known to NetGet at all, in any build.
1797    Unknown {
1798        input: String,
1799        suggestion: Option<&'static str>,
1800    },
1801}
1802
1803impl std::fmt::Display for ProtocolLookupError {
1804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1805        match self {
1806            ProtocolLookupError::NotCompiled { name, requires } => write!(
1807                f,
1808                "Protocol '{name}' exists but is not compiled into this build: it needs {requires}"
1809            ),
1810            ProtocolLookupError::Unknown {
1811                input,
1812                suggestion: Some(s),
1813            } => write!(f, "Unknown protocol: '{input}'. Did you mean '{s}'?"),
1814            ProtocolLookupError::Unknown {
1815                input,
1816                suggestion: None,
1817            } => write!(f, "Unknown protocol: '{input}'"),
1818        }
1819    }
1820}
1821
1822impl std::error::Error for ProtocolLookupError {}
1823
1824/// Suggest the closest known protocol name to `input` via Levenshtein edit
1825/// distance, if it's plausibly a typo rather than something unrelated.
1826fn suggest_protocol_name(
1827    input: &str,
1828    known: &[(&'static str, &'static str)],
1829) -> Option<&'static str> {
1830    let input_norm = input.to_lowercase();
1831    let mut best: Option<(&'static str, usize)> = None;
1832
1833    for (name, _feature) in known {
1834        let distance = levenshtein(&input_norm, &name.to_lowercase());
1835        best = match best {
1836            Some((_, best_dist)) if best_dist <= distance => best,
1837            _ => Some((name, distance)),
1838        };
1839    }
1840
1841    best.and_then(|(name, distance)| {
1842        let threshold = (input_norm.chars().count() / 3).max(2);
1843        if distance <= threshold {
1844            Some(name)
1845        } else {
1846            None
1847        }
1848    })
1849}
1850
1851/// Minimal Levenshtein edit distance between two strings. Implemented by hand
1852/// (no external dependency) since a "did you mean" suggestion doesn't justify
1853/// adding a crate.
1854fn levenshtein(a: &str, b: &str) -> usize {
1855    let a: Vec<char> = a.chars().collect();
1856    let b: Vec<char> = b.chars().collect();
1857    let mut dp: Vec<usize> = (0..=b.len()).collect();
1858
1859    for i in 1..=a.len() {
1860        let mut prev = dp[0];
1861        dp[0] = i;
1862        for j in 1..=b.len() {
1863            let tmp = dp[j];
1864            dp[j] = if a[i - 1] == b[j - 1] {
1865                prev
1866            } else {
1867                1 + prev.min(dp[j]).min(dp[j - 1])
1868            };
1869            prev = tmp;
1870        }
1871    }
1872
1873    dp[b.len()]
1874}
1875
1876/// Global singleton registry instance
1877static REGISTRY: once_cell::sync::Lazy<ServerRegistry> =
1878    once_cell::sync::Lazy::new(ServerRegistry::new);
1879
1880/// Get the global protocol registry
1881pub fn registry() -> &'static ServerRegistry {
1882    &REGISTRY
1883}