1use crate::llm::actions::Client;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11pub struct ClientRegistry {
13 protocols: HashMap<String, Arc<dyn Client>>,
15 keyword_map: HashMap<String, String>,
17}
18
19impl ClientRegistry {
20 fn new() -> Self {
22 tracing::debug!("ClientRegistry::new() - Creating new client registry");
23 let mut registry = Self {
24 protocols: HashMap::new(),
25 keyword_map: HashMap::new(),
26 };
27
28 tracing::debug!("ClientRegistry::new() - Registering client protocols");
30 registry.register_protocols();
31 tracing::debug!("ClientRegistry::new() - Client protocols registered");
32
33 tracing::debug!("ClientRegistry::new() - Building keyword map");
34 registry.build_keyword_map();
35 tracing::debug!("ClientRegistry::new() - Keyword map built");
36
37 tracing::debug!("ClientRegistry::new() - Validating keyword uniqueness");
39 registry.validate_keyword_uniqueness();
40 tracing::debug!("ClientRegistry::new() - Keyword uniqueness validated");
41
42 tracing::debug!("ClientRegistry::new() - Client registry created");
43 registry
44 }
45
46 fn register_protocols(&mut self) {
48 #[cfg(all(unix, feature = "libpcap"))]
49 self.register(Arc::new(crate::client::arp::ArpClientProtocol::new()));
50
51 self.register(Arc::new(crate::client::bgp::BgpClientProtocol::new()));
52
53 self.register(Arc::new(
54 crate::client::bitcoin::BitcoinClientProtocol::new(),
55 ));
56 #[cfg(not(target_arch = "wasm32"))]
57 self.register(Arc::new(
58 crate::client::devp2p::actions::Devp2pClientProtocol::new(),
59 ));
60
61 #[cfg(all(unix, feature = "bluetooth"))]
62 self.register(Arc::new(
63 crate::client::bluetooth::BluetoothClientProtocol::new(),
64 ));
65
66 self.register(Arc::new(crate::client::bootp::BootpClientProtocol::new()));
67
68 #[cfg(not(target_arch = "wasm32"))]
69 self.register(Arc::new(
70 crate::client::cassandra::CassandraClientProtocol::new(),
71 ));
72
73 #[cfg(all(unix, feature = "libpcap"))]
74 self.register(Arc::new(
75 crate::client::datalink::DataLinkClientProtocol::new(),
76 ));
77
78 #[cfg(not(target_arch = "wasm32"))]
79 self.register(Arc::new(
80 crate::client::dc_peer::actions::DcPeerClientProtocol::new(),
81 ));
82
83 #[cfg(not(target_arch = "wasm32"))]
84 self.register(Arc::new(
85 crate::client::adc::actions::AdcClientProtocol::new(),
86 ));
87
88 #[cfg(not(target_arch = "wasm32"))]
89 self.register(Arc::new(
90 crate::client::adc_peer::actions::AdcPeerClientProtocol::new(),
91 ));
92
93 #[cfg(not(target_arch = "wasm32"))]
94 self.register(Arc::new(
95 crate::client::soulseek::actions::SoulseekClientProtocol::new(),
96 ));
97
98 #[cfg(not(target_arch = "wasm32"))]
99 self.register(Arc::new(
100 crate::client::soulseek_peer::actions::SoulseekPeerClientProtocol::new(),
101 ));
102
103 #[cfg(not(target_arch = "wasm32"))]
104 self.register(Arc::new(
105 crate::client::gnutella::actions::GnutellaClientProtocol::new(),
106 ));
107
108 self.register(Arc::new(crate::client::dc::DcClientProtocol::new()));
109
110 self.register(Arc::new(crate::client::dhcp::DhcpClientProtocol::new()));
111
112 #[cfg(not(target_arch = "wasm32"))]
113 self.register(Arc::new(crate::client::dns::DnsClientProtocol::new()));
114
115 #[cfg(not(target_arch = "wasm32"))]
116 self.register(Arc::new(crate::client::doh::DohClientProtocol::new()));
117
118 #[cfg(not(target_arch = "wasm32"))]
119 self.register(Arc::new(
120 crate::client::doq::actions::DoqClientProtocol::new(),
121 ));
122
123 #[cfg(not(target_arch = "wasm32"))]
124 self.register(Arc::new(crate::client::dot::DotClientProtocol::new()));
125
126 #[cfg(unix)]
127 self.register(Arc::new(
128 crate::client::dynamodb::DynamoDbClientProtocol::new(),
129 ));
130
131 self.register(Arc::new(
132 crate::client::elasticsearch::ElasticsearchClientProtocol::new(),
133 ));
134
135 #[cfg(not(target_arch = "wasm32"))]
136 self.register(Arc::new(crate::client::etcd::EtcdClientProtocol::new()));
137
138 #[cfg(not(target_arch = "wasm32"))]
139 self.register(Arc::new(crate::client::git::GitClientProtocol::new()));
140
141 #[cfg(not(target_arch = "wasm32"))]
142 self.register(Arc::new(
143 crate::client::zookeeper::ZookeeperClientProtocol::new(),
144 ));
145
146 #[cfg(not(target_arch = "wasm32"))]
147 self.register(Arc::new(crate::client::grpc::GrpcClientProtocol::new()));
148 #[cfg(not(target_arch = "wasm32"))]
149 self.register(Arc::new(
150 crate::client::gnmi::actions::GnmiClientProtocol::new(),
151 ));
152 #[cfg(not(target_arch = "wasm32"))]
153 self.register(Arc::new(
154 crate::client::grpc_web::actions::GrpcWebClientProtocol::new(),
155 ));
156 #[cfg(not(target_arch = "wasm32"))]
157 self.register(Arc::new(
158 crate::client::connect_rpc::actions::ConnectRpcClientProtocol::new(),
159 ));
160
161 {
162 tracing::debug!("ClientRegistry: Registering HTTP client protocol");
163 self.register(Arc::new(crate::client::http::HttpClientProtocol::new()));
164 tracing::debug!("ClientRegistry: HTTP client protocol registered");
165 }
166
167 self.register(Arc::new(crate::client::http2::Http2ClientProtocol::new()));
168
169 #[cfg(not(target_arch = "wasm32"))]
171 self.register(Arc::new(
172 crate::client::quic::actions::QuicClientProtocol::new(),
173 ));
174
175 #[cfg(not(target_arch = "wasm32"))]
176 self.register(Arc::new(crate::client::http3::Http3ClientProtocol::new()));
177
178 #[cfg(unix)]
179 self.register(Arc::new(
180 crate::client::http_proxy::HttpProxyClientProtocol::new(),
181 ));
182
183 #[cfg(unix)]
184 self.register(Arc::new(crate::client::igmp::IgmpClientProtocol::new()));
185
186 #[cfg(not(target_arch = "wasm32"))]
187 self.register(Arc::new(crate::client::imap::ImapClientProtocol::new()));
188
189 #[cfg(not(target_arch = "wasm32"))]
190 self.register(Arc::new(crate::client::ipp::IppClientProtocol::new()));
191
192 #[cfg(not(target_arch = "wasm32"))]
193 self.register(Arc::new(crate::client::irc::IrcClientProtocol::new()));
194
195 #[cfg(all(unix, feature = "libpcap"))]
196 self.register(Arc::new(crate::client::isis::IsisClientProtocol::new()));
197
198 self.register(Arc::new(
199 crate::client::jsonrpc::JsonRpcClientProtocol::new(),
200 ));
201
202 self.register(Arc::new(crate::client::kafka::KafkaClientProtocol::new()));
203
204 #[cfg(not(target_arch = "wasm32"))]
205 self.register(Arc::new(
206 crate::client::kubernetes::KubernetesClientProtocol::new(),
207 ));
208
209 #[cfg(not(target_arch = "wasm32"))]
210 self.register(Arc::new(crate::client::ldap::LdapClientProtocol::new()));
211
212 self.register(Arc::new(crate::client::maven::MavenClientProtocol::new()));
213
214 #[cfg(not(target_arch = "wasm32"))]
215 self.register(Arc::new(crate::client::mcp::McpClientProtocol::new()));
216
217 #[cfg(not(target_arch = "wasm32"))]
218 self.register(Arc::new(crate::client::mdns::MdnsClientProtocol::new()));
219
220 #[cfg(not(target_arch = "wasm32"))]
221 self.register(Arc::new(crate::client::mqtt::MqttClientProtocol::new()));
222
223 #[cfg(not(target_arch = "wasm32"))]
224 self.register(Arc::new(crate::client::amqp::AmqpClientProtocol::new()));
225
226 #[cfg(not(target_arch = "wasm32"))]
227 self.register(Arc::new(crate::client::mysql::MysqlClientProtocol::new()));
228
229 #[cfg(not(target_arch = "wasm32"))]
230 self.register(Arc::new(
231 crate::client::mongodb::MongodbClientProtocol::new(),
232 ));
233
234 #[cfg(unix)]
235 self.register(Arc::new(crate::client::nfs::NfsClientProtocol::new()));
236
237 #[cfg(all(unix, feature = "pcsc"))]
238 self.register(Arc::new(crate::client::nfc::NfcClientProtocol));
239
240 self.register(Arc::new(crate::client::nntp::NntpClientProtocol::new()));
241
242 self.register(Arc::new(crate::client::npm::NpmClientProtocol::new()));
243
244 #[cfg(not(target_arch = "wasm32"))]
245 self.register(Arc::new(crate::client::ntp::NtpClientProtocol::new()));
246
247 self.register(Arc::new(crate::client::oauth2::OAuth2ClientProtocol::new()));
248
249 #[cfg(not(target_arch = "wasm32"))]
250 self.register(Arc::new(crate::client::openai::OpenAiClientProtocol::new()));
251
252 self.register(Arc::new(crate::client::ollama::OllamaClientProtocol::new()));
253
254 self.register(Arc::new(
255 crate::client::openapi::OpenApiClientProtocol::new(),
256 ));
257
258 self.register(Arc::new(
259 crate::client::openidconnect::OpenIdConnectClientProtocol::new(),
260 ));
261
262 #[cfg(unix)]
263 self.register(Arc::new(crate::client::ospf::OspfClientProtocol::new()));
264
265 #[cfg(not(target_arch = "wasm32"))]
266 self.register(Arc::new(
267 crate::client::otlp::actions::OtlpClientProtocol::new(),
268 ));
269
270 #[cfg(not(target_arch = "wasm32"))]
271 self.register(Arc::new(
272 crate::client::postgresql::PostgresqlClientProtocol::new(),
273 ));
274
275 self.register(Arc::new(crate::client::pypi::PypiClientProtocol::new()));
276
277 #[cfg(not(target_arch = "wasm32"))]
278 self.register(Arc::new(crate::client::mssql::MssqlClientProtocol::new()));
279
280 #[cfg(not(target_arch = "wasm32"))]
281 self.register(Arc::new(crate::client::redis::RedisClientProtocol::new()));
282
283 #[cfg(unix)]
284 self.register(Arc::new(crate::client::icmp::IcmpClientProtocol::new()));
285
286 #[cfg(not(target_arch = "wasm32"))]
287 self.register(Arc::new(
288 crate::client::couchdb::CouchDbClientProtocol::new(),
289 ));
290
291 self.register(Arc::new(crate::client::tftp::TftpClientProtocol::new()));
292
293 self.register(Arc::new(crate::client::rss::RssClientProtocol::new()));
294
295 #[cfg(not(target_arch = "wasm32"))]
296 self.register(Arc::new(crate::client::rip::RipClientProtocol::new()));
297
298 #[cfg(not(target_arch = "wasm32"))]
299 self.register(Arc::new(crate::client::s3::S3ClientProtocol::new()));
300
301 self.register(Arc::new(crate::client::saml::SamlClientProtocol::new()));
302
303 self.register(Arc::new(crate::client::sip::SipClientProtocol::new()));
304 #[cfg(not(target_arch = "wasm32"))]
305 self.register(Arc::new(
306 crate::client::h323::actions::H323ClientProtocol::new(),
307 ));
308
309 #[cfg(all(unix, feature = "libsmbclient"))]
310 self.register(Arc::new(crate::client::smb::SmbClientProtocol::new()));
311
312 #[cfg(not(target_arch = "wasm32"))]
313 self.register(Arc::new(crate::client::smtp::SmtpClientProtocol::new()));
314
315 self.register(Arc::new(crate::client::ftp::FtpClientProtocol::new()));
316
317 self.register(Arc::new(crate::client::pop3::Pop3ClientProtocol::new()));
318
319 self.register(Arc::new(crate::client::snmp::SnmpClientProtocol::new()));
320
321 #[cfg(not(target_arch = "wasm32"))]
322 self.register(Arc::new(crate::client::socks5::Socks5ClientProtocol::new()));
323
324 #[cfg(unix)]
325 self.register(Arc::new(
326 crate::client::socket_file::SocketFileClientProtocol::new(),
327 ));
328
329 #[cfg(not(target_arch = "wasm32"))]
330 self.register(Arc::new(crate::client::sqs::SqsClientProtocol::new()));
331
332 #[cfg(not(target_arch = "wasm32"))]
333 self.register(Arc::new(crate::client::ssh::SshClientProtocol::new()));
334
335 #[cfg(unix)]
336 self.register(Arc::new(
337 crate::client::ssh_agent::SshAgentClientProtocol::new(),
338 ));
339
340 #[cfg(unix)]
341 self.register(Arc::new(crate::client::stun::StunClientProtocol::new()));
342
343 #[cfg(not(target_arch = "wasm32"))]
344 self.register(Arc::new(
345 crate::client::diameter::actions::DiameterClientProtocol::new(),
346 ));
347
348 #[cfg(not(target_arch = "wasm32"))]
349 self.register(Arc::new(
350 crate::client::tacacs::actions::TacacsClientProtocol::new(),
351 ));
352
353 #[cfg(not(target_arch = "wasm32"))]
354 self.register(Arc::new(
355 crate::client::netconf::actions::NetconfClientProtocol::new(),
356 ));
357
358 #[cfg(not(target_arch = "wasm32"))]
359 self.register(Arc::new(
360 crate::client::rpki_rtr::actions::RpkiRtrClientProtocol::new(),
361 ));
362
363 #[cfg(not(target_arch = "wasm32"))]
364 self.register(Arc::new(
365 crate::client::rdap::actions::RdapClientProtocol::new(),
366 ));
367
368 #[cfg(not(target_arch = "wasm32"))]
369 self.register(Arc::new(
370 crate::client::hl7::actions::Hl7ClientProtocol::new(),
371 ));
372
373 #[cfg(not(target_arch = "wasm32"))]
374 self.register(Arc::new(
375 crate::client::icap::actions::IcapClientProtocol::new(),
376 ));
377
378 #[cfg(not(target_arch = "wasm32"))]
379 self.register(Arc::new(
380 crate::client::s7comm::actions::S7commClientProtocol::new(),
381 ));
382
383 #[cfg(not(target_arch = "wasm32"))]
384 self.register(Arc::new(
385 crate::client::ethernet_ip::actions::EthernetIpClientProtocol::new(),
386 ));
387
388 #[cfg(not(target_arch = "wasm32"))]
389 self.register(Arc::new(
390 crate::client::dnp3::actions::Dnp3ClientProtocol::new(),
391 ));
392
393 #[cfg(not(target_arch = "wasm32"))]
394 self.register(Arc::new(
395 crate::client::iec104::actions::Iec104ClientProtocol::new(),
396 ));
397
398 #[cfg(not(target_arch = "wasm32"))]
399 self.register(Arc::new(
400 crate::client::bacnet::actions::BacnetClientProtocol::new(),
401 ));
402
403 #[cfg(not(target_arch = "wasm32"))]
404 self.register(Arc::new(
405 crate::client::opcua::actions::OpcuaClientProtocol::new(),
406 ));
407
408 #[cfg(not(target_arch = "wasm32"))]
409 self.register(Arc::new(
410 crate::client::ocpp::actions::OcppClientProtocol::new(),
411 ));
412
413 #[cfg(not(target_arch = "wasm32"))]
414 self.register(Arc::new(
415 crate::client::a2a::actions::A2aClientProtocol::new(),
416 ));
417
418 #[cfg(not(target_arch = "wasm32"))]
419 self.register(Arc::new(
420 crate::client::graphql::actions::GraphqlClientProtocol::new(),
421 ));
422
423 #[cfg(not(target_arch = "wasm32"))]
424 self.register(Arc::new(
425 crate::client::fastcgi::actions::FastcgiClientProtocol::new(),
426 ));
427
428 #[cfg(not(target_arch = "wasm32"))]
429 self.register(Arc::new(
430 crate::client::redfish::actions::RedfishClientProtocol::new(),
431 ));
432
433 #[cfg(not(target_arch = "wasm32"))]
434 self.register(Arc::new(
435 crate::client::scim::actions::ScimClientProtocol::new(),
436 ));
437
438 #[cfg(not(target_arch = "wasm32"))]
439 self.register(Arc::new(
440 crate::client::socketio::actions::SocketIoClientProtocol::new(),
441 ));
442
443 #[cfg(not(target_arch = "wasm32"))]
444 self.register(Arc::new(
445 crate::client::caldav::actions::CalDavClientProtocol::new(),
446 ));
447
448 #[cfg(not(target_arch = "wasm32"))]
449 self.register(Arc::new(
450 crate::client::carddav::actions::CardDavClientProtocol::new(),
451 ));
452
453 #[cfg(not(target_arch = "wasm32"))]
454 self.register(Arc::new(
455 crate::client::dicom::actions::DicomClientProtocol::new(),
456 ));
457
458 #[cfg(not(target_arch = "wasm32"))]
459 self.register(Arc::new(
460 crate::client::acme::actions::AcmeClientProtocol::new(),
461 ));
462
463 #[cfg(not(target_arch = "wasm32"))]
464 self.register(Arc::new(
465 crate::client::fix::actions::FixClientProtocol::new(),
466 ));
467
468 #[cfg(not(target_arch = "wasm32"))]
469 self.register(Arc::new(
470 crate::client::wamp::actions::WampClientProtocol::new(),
471 ));
472
473 #[cfg(not(target_arch = "wasm32"))]
474 self.register(Arc::new(
475 crate::client::rtmp::actions::RtmpClientProtocol::new(),
476 ));
477
478 #[cfg(not(target_arch = "wasm32"))]
479 self.register(Arc::new(
480 crate::client::srt::actions::SrtClientProtocol::new(),
481 ));
482
483 #[cfg(not(target_arch = "wasm32"))]
484 self.register(Arc::new(
485 crate::client::amqp1::actions::Amqp1ClientProtocol::new(),
486 ));
487
488 #[cfg(not(target_arch = "wasm32"))]
489 self.register(Arc::new(
490 crate::client::thrift::actions::ThriftClientProtocol::new(),
491 ));
492 #[cfg(not(target_arch = "wasm32"))]
493 self.register(Arc::new(
494 crate::client::bmp::actions::BmpClientProtocol::new(),
495 ));
496 #[cfg(not(target_arch = "wasm32"))]
497 self.register(Arc::new(
498 crate::client::mqtt_sn::actions::MqttSnClientProtocol::new(),
499 ));
500 #[cfg(not(target_arch = "wasm32"))]
501 self.register(Arc::new(
502 crate::client::nbd::actions::NbdClientProtocol::new(),
503 ));
504 #[cfg(not(target_arch = "wasm32"))]
505 self.register(Arc::new(
506 crate::client::managesieve::actions::ManageSieveClientProtocol::new(),
507 ));
508 #[cfg(not(target_arch = "wasm32"))]
509 self.register(Arc::new(
510 crate::client::zenoh::actions::ZenohClientProtocol::new(),
511 ));
512 #[cfg(not(target_arch = "wasm32"))]
513 self.register(Arc::new(
514 crate::client::lwm2m::actions::Lwm2mClientProtocol::new(),
515 ));
516 #[cfg(not(target_arch = "wasm32"))]
517 self.register(Arc::new(
518 crate::client::restconf::actions::RestconfClientProtocol::new(),
519 ));
520 #[cfg(not(target_arch = "wasm32"))]
521 self.register(Arc::new(
522 crate::client::webtransport::actions::WebTransportClientProtocol::new(),
523 ));
524 #[cfg(not(target_arch = "wasm32"))]
525 self.register(Arc::new(
526 crate::client::jmap::actions::JmapClientProtocol::new(),
527 ));
528 #[cfg(not(target_arch = "wasm32"))]
529 self.register(Arc::new(
530 crate::client::epp::actions::EppClientProtocol::new(),
531 ));
532 #[cfg(not(target_arch = "wasm32"))]
533 self.register(Arc::new(
534 crate::client::lmtp::actions::LmtpClientProtocol::new(),
535 ));
536 #[cfg(not(target_arch = "wasm32"))]
537 self.register(Arc::new(
538 crate::client::lpd::actions::LpdClientProtocol::new(),
539 ));
540 #[cfg(not(target_arch = "wasm32"))]
541 self.register(Arc::new(
542 crate::client::inetd::actions::EchoClientProtocol::new(),
543 ));
544 #[cfg(not(target_arch = "wasm32"))]
545 self.register(Arc::new(
546 crate::client::inetd::actions::DiscardClientProtocol::new(),
547 ));
548 #[cfg(not(target_arch = "wasm32"))]
549 self.register(Arc::new(
550 crate::client::inetd::actions::DaytimeClientProtocol::new(),
551 ));
552 #[cfg(not(target_arch = "wasm32"))]
553 self.register(Arc::new(
554 crate::client::inetd::actions::QotdClientProtocol::new(),
555 ));
556 #[cfg(not(target_arch = "wasm32"))]
557 self.register(Arc::new(
558 crate::client::inetd::actions::ChargenClientProtocol::new(),
559 ));
560 #[cfg(not(target_arch = "wasm32"))]
561 self.register(Arc::new(
562 crate::client::inetd::actions::TimeClientProtocol::new(),
563 ));
564 #[cfg(not(target_arch = "wasm32"))]
565 self.register(Arc::new(
566 crate::client::rcon::actions::RconClientProtocol::new(),
567 ));
568 #[cfg(not(target_arch = "wasm32"))]
569 self.register(Arc::new(
570 crate::client::a2s::actions::A2sClientProtocol::new(),
571 ));
572 #[cfg(not(target_arch = "wasm32"))]
573 self.register(Arc::new(
574 crate::client::minecraft::actions::MinecraftClientProtocol::new(),
575 ));
576 #[cfg(not(target_arch = "wasm32"))]
577 self.register(Arc::new(
578 crate::client::ninep::actions::NinepClientProtocol::new(),
579 ));
580 #[cfg(not(target_arch = "wasm32"))]
581 self.register(Arc::new(
582 crate::client::zeromq::actions::ZeromqClientProtocol::new(),
583 ));
584 #[cfg(not(target_arch = "wasm32"))]
585 self.register(Arc::new(
586 crate::client::smpp::actions::SmppClientProtocol::new(),
587 ));
588 #[cfg(not(target_arch = "wasm32"))]
589 self.register(Arc::new(
590 crate::client::clickhouse::actions::ClickhouseClientProtocol::new(),
591 ));
592 #[cfg(not(target_arch = "wasm32"))]
593 self.register(Arc::new(
594 crate::client::msgpack_rpc::actions::MsgpackRpcClientProtocol::new(),
595 ));
596 #[cfg(not(target_arch = "wasm32"))]
597 self.register(Arc::new(
598 crate::client::zipkin::actions::ZipkinClientProtocol::new(),
599 ));
600 #[cfg(not(target_arch = "wasm32"))]
601 self.register(Arc::new(
602 crate::client::capnp_rpc::actions::CapnpRpcClientProtocol::new(),
603 ));
604 #[cfg(not(target_arch = "wasm32"))]
605 self.register(Arc::new(
606 crate::client::knx::actions::KnxClientProtocol::new(),
607 ));
608 #[cfg(not(target_arch = "wasm32"))]
609 self.register(Arc::new(
610 crate::client::consul::actions::ConsulClientProtocol::new(),
611 ));
612 #[cfg(not(target_arch = "wasm32"))]
613 self.register(Arc::new(
614 crate::client::milter::actions::MilterClientProtocol::new(),
615 ));
616 #[cfg(not(target_arch = "wasm32"))]
617 self.register(Arc::new(
618 crate::client::anthropic::actions::AnthropicClientProtocol::new(),
619 ));
620 #[cfg(not(target_arch = "wasm32"))]
621 self.register(Arc::new(
622 crate::client::radsec::actions::RadsecClientProtocol::new(),
623 ));
624 #[cfg(not(target_arch = "wasm32"))]
625 self.register(Arc::new(
626 crate::client::dbus::actions::DbusClientProtocol::new(),
627 ));
628 #[cfg(not(target_arch = "wasm32"))]
629 self.register(Arc::new(
630 crate::client::bfd::actions::BfdClientProtocol::new(),
631 ));
632 #[cfg(not(target_arch = "wasm32"))]
633 self.register(Arc::new(
634 crate::client::vxlan::actions::VxlanClientProtocol::new(),
635 ));
636 #[cfg(not(target_arch = "wasm32"))]
637 self.register(Arc::new(
638 crate::client::rsync::actions::RsyncClientProtocol::new(),
639 ));
640 #[cfg(unix)]
641 self.register(Arc::new(
642 crate::client::rdp::actions::RdpClientProtocol::new(),
643 ));
644 #[cfg(not(target_arch = "wasm32"))]
645 self.register(Arc::new(
646 crate::client::spice::actions::SpiceClientProtocol::new(),
647 ));
648 #[cfg(unix)]
649 self.register(Arc::new(
650 crate::client::m3ua::actions::M3uaClientProtocol::new(),
651 ));
652 #[cfg(unix)]
653 self.register(Arc::new(
654 crate::client::ngap::actions::NgapClientProtocol::new(),
655 ));
656 #[cfg(not(target_arch = "wasm32"))]
657 self.register(Arc::new(
658 crate::client::pfcp::actions::PfcpClientProtocol::new(),
659 ));
660 self.register(Arc::new(
661 crate::client::gtp::actions::GtpClientProtocol::new(),
662 ));
663 self.register(Arc::new(
664 crate::client::db2::actions::Db2ClientProtocol::new(),
665 ));
666 self.register(Arc::new(
667 crate::client::snowflake::actions::SnowflakeClientProtocol::new(),
668 ));
669 self.register(Arc::new(
670 crate::client::spark::actions::SparkClientProtocol::new(),
671 ));
672 self.register(Arc::new(
673 crate::client::yarn::actions::YarnClientProtocol::new(),
674 ));
675 #[cfg(not(target_arch = "wasm32"))]
676 self.register(Arc::new(
677 crate::client::matrix::actions::MatrixClientProtocol::new(),
678 ));
679 #[cfg(not(target_arch = "wasm32"))]
680 self.register(Arc::new(
681 crate::client::libp2p::actions::Libp2pClientProtocol::new(),
682 ));
683 #[cfg(not(target_arch = "wasm32"))]
684 self.register(Arc::new(
685 crate::client::sunrpc::actions::SunRpcClientProtocol::new(),
686 ));
687 #[cfg(not(target_arch = "wasm32"))]
688 self.register(Arc::new(
689 crate::client::guacamole::actions::GuacamoleClientProtocol::new(),
690 ));
691 #[cfg(not(target_arch = "wasm32"))]
692 self.register(Arc::new(
693 crate::client::activitypub::actions::ActivityPubClientProtocol::new(),
694 ));
695 #[cfg(not(target_arch = "wasm32"))]
696 self.register(Arc::new(
697 crate::client::stratum::actions::StratumClientProtocol::new(),
698 ));
699 #[cfg(not(target_arch = "wasm32"))]
700 self.register(Arc::new(
701 crate::client::stratum_v2::actions::StratumV2ClientProtocol::new(),
702 ));
703 #[cfg(not(target_arch = "wasm32"))]
704 self.register(Arc::new(
705 crate::client::pulsar::actions::PulsarClientProtocol::new(),
706 ));
707 #[cfg(not(target_arch = "wasm32"))]
708 self.register(Arc::new(
709 crate::client::tr069::actions::Tr069ClientProtocol::new(),
710 ));
711 #[cfg(not(target_arch = "wasm32"))]
712 self.register(Arc::new(
713 crate::client::sse::actions::SseClientProtocol::new(),
714 ));
715 #[cfg(not(target_arch = "wasm32"))]
716 self.register(Arc::new(
717 crate::client::zabbix::actions::ZabbixClientProtocol::new(),
718 ));
719 self.register(Arc::new(
720 crate::client::rtp::actions::RtpClientProtocol::new(),
721 ));
722 self.register(Arc::new(
723 crate::client::rtsp::actions::RtspClientProtocol::new(),
724 ));
725 self.register(Arc::new(
726 crate::client::hls::actions::HlsClientProtocol::new(),
727 ));
728 #[cfg(not(target_arch = "wasm32"))]
729 self.register(Arc::new(
730 crate::client::wsdiscovery::actions::WsDiscoveryClientProtocol::new(),
731 ));
732 #[cfg(not(target_arch = "wasm32"))]
733 self.register(Arc::new(
734 crate::client::onvif::actions::OnvifClientProtocol::new(),
735 ));
736 #[cfg(not(target_arch = "wasm32"))]
737 self.register(Arc::new(
738 crate::client::kerberos::actions::KerberosClientProtocol::new(),
739 ));
740 #[cfg(not(target_arch = "wasm32"))]
741 self.register(Arc::new(
742 crate::client::x11::actions::X11ClientProtocol::new(),
743 ));
744
745 #[cfg(not(target_arch = "wasm32"))]
746 self.register(Arc::new(
747 crate::client::netflow_v9::actions::NetflowV9ClientProtocol::new(),
748 ));
749 #[cfg(not(target_arch = "wasm32"))]
750 self.register(Arc::new(
751 crate::client::ipfix::actions::IpfixClientProtocol::new(),
752 ));
753 #[cfg(not(target_arch = "wasm32"))]
754 self.register(Arc::new(
755 crate::client::loki::actions::LokiClientProtocol::new(),
756 ));
757 #[cfg(not(target_arch = "wasm32"))]
758 self.register(Arc::new(
759 crate::client::influxdb::actions::InfluxDbClientProtocol::new(),
760 ));
761 #[cfg(not(target_arch = "wasm32"))]
762 self.register(Arc::new(crate::client::prometheus_remote_write::actions::PrometheusRemoteWriteClientProtocol::new()));
763 #[cfg(not(target_arch = "wasm32"))]
764 self.register(Arc::new(
765 crate::client::sflow::actions::SflowClientProtocol::new(),
766 ));
767
768 #[cfg(not(target_arch = "wasm32"))]
769 self.register(Arc::new(
770 crate::client::fluent_forward::actions::FluentForwardClientProtocol::new(),
771 ));
772
773 #[cfg(not(target_arch = "wasm32"))]
774 self.register(Arc::new(
775 crate::client::gelf::actions::GelfClientProtocol::new(),
776 ));
777
778 #[cfg(not(target_arch = "wasm32"))]
779 self.register(Arc::new(
780 crate::client::graphite::actions::GraphiteClientProtocol::new(),
781 ));
782
783 #[cfg(not(target_arch = "wasm32"))]
784 self.register(Arc::new(
785 crate::client::statsd::actions::StatsdClientProtocol::new(),
786 ));
787
788 self.register(Arc::new(crate::client::syslog::SyslogClientProtocol::new()));
789
790 self.register(Arc::new(crate::client::tcp::TcpClientProtocol::new()));
791
792 self.register(Arc::new(crate::client::telnet::TelnetClientProtocol::new()));
793
794 self.register(Arc::new(crate::client::tls::TlsClientProtocol::new()));
795
796 #[cfg(unix)]
797 self.register(Arc::new(crate::client::tor::TorClientProtocol::new()));
798
799 self.register(Arc::new(
800 crate::client::torrent_dht::TorrentDhtClientProtocol::new(),
801 ));
802
803 self.register(Arc::new(
804 crate::client::torrent_peer::TorrentPeerClientProtocol::new(),
805 ));
806
807 self.register(Arc::new(
808 crate::client::torrent_tracker::TorrentTrackerClientProtocol::new(),
809 ));
810
811 #[cfg(unix)]
812 self.register(Arc::new(crate::client::turn::TurnClientProtocol::new()));
813
814 self.register(Arc::new(crate::client::udp::UdpClientProtocol::new()));
815
816 #[cfg(not(target_arch = "wasm32"))]
817 self.register(Arc::new(crate::client::usb::UsbClientProtocol::new()));
818
819 self.register(Arc::new(crate::client::vnc::VncClientProtocol::new()));
820
821 #[cfg(not(target_arch = "wasm32"))]
822 self.register(Arc::new(crate::client::webdav::WebdavClientProtocol::new()));
823
824 #[cfg(not(target_arch = "wasm32"))]
825 self.register(Arc::new(crate::client::webrtc::WebRtcClientProtocol::new()));
826
827 #[cfg(not(target_arch = "wasm32"))]
828 self.register(Arc::new(
829 crate::client::websocket::WebSocketClientProtocol::new(),
830 ));
831
832 #[cfg(not(target_arch = "wasm32"))]
833 self.register(Arc::new(crate::client::gemini::GeminiClientProtocol::new()));
834
835 #[cfg(not(target_arch = "wasm32"))]
836 self.register(Arc::new(crate::client::dict::DictClientProtocol::new()));
837
838 #[cfg(not(target_arch = "wasm32"))]
839 self.register(Arc::new(
840 crate::client::beanstalkd::BeanstalkdClientProtocol::new(),
841 ));
842
843 #[cfg(not(target_arch = "wasm32"))]
844 self.register(Arc::new(crate::client::vault::VaultClientProtocol::new()));
845
846 #[cfg(not(target_arch = "wasm32"))]
847 self.register(Arc::new(crate::client::bolt::BoltClientProtocol::new()));
848
849 self.register(Arc::new(
850 crate::client::oci_registry::OciRegistryClientProtocol::new(),
851 ));
852
853 #[cfg(not(target_arch = "wasm32"))]
854 self.register(Arc::new(crate::client::nostr::NostrClientProtocol::new()));
855
856 #[cfg(not(target_arch = "wasm32"))]
857 self.register(Arc::new(
858 crate::client::prometheus::PrometheusClientProtocol::new(),
859 ));
860 #[cfg(not(target_arch = "wasm32"))]
861 self.register(Arc::new(crate::client::docker::DockerClientProtocol::new()));
862
863 #[cfg(not(target_arch = "wasm32"))]
864 self.register(Arc::new(
865 crate::client::gearman::GearmanClientProtocol::new(),
866 ));
867
868 #[cfg(not(target_arch = "wasm32"))]
869 self.register(Arc::new(crate::client::nsq::NsqClientProtocol::new()));
870
871 #[cfg(not(target_arch = "wasm32"))]
872 self.register(Arc::new(
873 crate::client::nut::actions::NutClientProtocol::new(),
874 ));
875
876 self.register(Arc::new(crate::client::whois::WhoisClientProtocol::new()));
877 self.register(Arc::new(crate::client::stomp::StompClientProtocol::new()));
878 self.register(Arc::new(
879 crate::client::netbios_ns::NetbiosNsClientProtocol::new(),
880 ));
881 self.register(Arc::new(crate::client::nats::NatsClientProtocol::new()));
882 self.register(Arc::new(
883 crate::client::memcached::MemcachedClientProtocol::new(),
884 ));
885 self.register(Arc::new(crate::client::modbus::ModbusClientProtocol::new()));
886 self.register(Arc::new(crate::client::coap::CoapClientProtocol::new()));
887 self.register(Arc::new(crate::client::radius::RadiusClientProtocol::new()));
888 self.register(Arc::new(crate::client::ssdp::SsdpClientProtocol::new()));
889 self.register(Arc::new(crate::client::gopher::GopherClientProtocol::new()));
890 self.register(Arc::new(crate::client::finger::FingerClientProtocol::new()));
891 self.register(Arc::new(crate::client::ident::IdentClientProtocol::new()));
892 #[cfg(not(target_arch = "wasm32"))]
893 self.register(Arc::new(crate::client::llmnr::LlmnrClientProtocol::new()));
894
895 #[cfg(unix)]
896 self.register(Arc::new(
897 crate::client::wireguard::WireguardClientProtocol::new(),
898 ));
899
900 #[cfg(not(target_arch = "wasm32"))]
901 self.register(Arc::new(crate::client::xmlrpc::XmlRpcClientProtocol::new()));
902
903 #[cfg(unix)]
904 self.register(Arc::new(crate::client::xmpp::XmppClientProtocol::new()));
905 }
906
907 fn build_keyword_map(&mut self) {
909 for (protocol_name, protocol) in &self.protocols {
910 for keyword in protocol.keywords() {
912 let lower = keyword.to_lowercase();
913 let normalized = lower.replace(['-', ' '], "_");
917 self.keyword_map.insert(lower, protocol_name.clone());
918 self.keyword_map.insert(normalized, protocol_name.clone());
919 }
920
921 let stack_name = protocol.stack_name().to_lowercase();
924 self.keyword_map.insert(stack_name, protocol_name.clone());
925 }
926 }
927
928 fn validate_keyword_uniqueness(&self) {
933 use std::collections::HashMap;
934
935 let mut keyword_to_protocols: HashMap<String, Vec<(String, String)>> = HashMap::new();
938
939 for (protocol_name, protocol) in &self.protocols {
940 for keyword in protocol.keywords() {
942 let key = keyword.to_lowercase();
943 keyword_to_protocols
944 .entry(key)
945 .or_default()
946 .push((protocol_name.clone(), format!("keyword '{}'", keyword)));
947 }
948
949 let stack_name = protocol.stack_name();
951 let key = stack_name.to_lowercase();
952 keyword_to_protocols.entry(key).or_default().push((
953 protocol_name.clone(),
954 format!("stack_name '{}'", stack_name),
955 ));
956 }
957
958 let mut overlaps = Vec::new();
962 for (keyword, protocols) in &keyword_to_protocols {
963 let distinct: std::collections::HashSet<&String> =
964 protocols.iter().map(|(name, _)| name).collect();
965 if distinct.len() > 1 {
966 overlaps.push((keyword.clone(), protocols.clone()));
967 }
968 }
969
970 if !overlaps.is_empty() {
973 use tracing::debug;
974 debug!("Client keyword overlaps detected between protocols:");
975
976 for (keyword, protocols) in &overlaps {
977 debug!(" Keyword '{}' is used by:", keyword);
978 for (protocol_name, source) in protocols {
979 debug!(" - {} ({})", protocol_name, source);
980 }
981 }
982
983 debug!("Note: Each keyword should ideally be unique to a single protocol.");
984 debug!(
985 " Run 'cargo test test_keyword_overlaps -- --ignored' to see all overlaps."
986 );
987 }
988 }
989
990 #[allow(dead_code)]
992 fn register(&mut self, protocol: Arc<dyn Client>) {
993 let protocol_name = protocol.protocol_name().to_string();
994 self.protocols.insert(protocol_name, protocol);
995 }
996
997 pub fn get(&self, protocol_name: &str) -> Option<Arc<dyn Client>> {
999 self.protocols.get(protocol_name).cloned()
1000 }
1001
1002 pub fn resolve(&self, input: &str) -> Result<Arc<dyn Client>, ClientProtocolLookupError> {
1011 if let Some(p) = self.get(input) {
1014 return Ok(p);
1015 }
1016
1017 if let Some(name) = self.parse_from_str(input) {
1020 if let Some(p) = self.get(&name) {
1021 return Ok(p);
1022 }
1023 }
1024
1025 let input_norm = input.to_lowercase().replace(['-', ' '], "_");
1029 for (name, requires) in ALL_KNOWN_CLIENT_PROTOCOLS {
1030 let name_norm = name.to_lowercase().replace(['-', ' '], "_");
1031 if name_norm == input_norm {
1032 return Err(ClientProtocolLookupError::NotCompiled { name, requires });
1033 }
1034 }
1035
1036 Err(ClientProtocolLookupError::Unknown {
1039 input: input.to_string(),
1040 suggestion: suggest_client_protocol_name(input, ALL_KNOWN_CLIENT_PROTOCOLS),
1041 })
1042 }
1043
1044 pub fn parse_from_str(&self, input: &str) -> Option<String> {
1049 let input_lower = input.to_lowercase();
1050 let input_normalized = input_lower.replace(['-', ' '], "_");
1053
1054 for (protocol_name, protocol) in &self.protocols {
1056 let stack_lower = protocol.stack_name().to_lowercase();
1057 let stack_normalized = stack_lower.replace(['-', ' '], "_");
1058 if input_lower == stack_lower
1059 || input_normalized == stack_lower
1060 || input_normalized == stack_normalized
1061 {
1062 return Some(protocol_name.clone());
1063 }
1064 }
1065
1066 if let Some(protocol_name) = self
1068 .keyword_map
1069 .get(&input_normalized)
1070 .or_else(|| self.keyword_map.get(&input_lower))
1071 {
1072 return Some(protocol_name.clone());
1073 }
1074
1075 let mut best: Option<(usize, &String)> = None;
1085 for (keyword, protocol_name) in &self.keyword_map {
1086 if !(input_lower.contains(keyword) || input_normalized.contains(keyword)) {
1087 continue;
1088 }
1089 let better = match best {
1090 None => true,
1091 Some((best_len, best_name)) => {
1092 keyword.len() > best_len
1093 || (keyword.len() == best_len && protocol_name < best_name)
1094 }
1095 };
1096 if better {
1097 best = Some((keyword.len(), protocol_name));
1098 }
1099 }
1100 best.map(|(_, name)| name.clone())
1101 }
1102
1103 pub fn list_protocols(&self) -> Vec<String> {
1105 self.protocols.keys().cloned().collect()
1106 }
1107
1108 pub fn has_protocol(&self, protocol_name: &str) -> bool {
1110 self.protocols.contains_key(protocol_name)
1111 }
1112
1113 pub fn get_all(&self) -> Vec<Arc<dyn Client>> {
1115 self.protocols.values().cloned().collect()
1116 }
1117
1118 pub fn get_excluded_protocols(
1122 &self,
1123 caps: &crate::privilege::SystemCapabilities,
1124 ) -> std::collections::HashMap<String, Vec<super::dependencies::ProtocolDependency>> {
1125 let mut excluded = std::collections::HashMap::new();
1126
1127 for (protocol_name, protocol) in &self.protocols {
1128 let dependencies = protocol.get_dependencies();
1129 let mut missing = Vec::new();
1130
1131 for dep in dependencies {
1132 if !dep.is_available(caps) {
1133 missing.push(dep);
1134 }
1135 }
1136
1137 if !missing.is_empty() {
1138 excluded.insert(protocol_name.clone(), missing);
1139 }
1140 }
1141
1142 excluded
1143 }
1144
1145 pub fn get_available_protocols(
1149 &self,
1150 caps: &crate::privilege::SystemCapabilities,
1151 ) -> Vec<String> {
1152 let excluded = self.get_excluded_protocols(caps);
1153
1154 self.protocols
1155 .keys()
1156 .filter(|name| !excluded.contains_key(*name))
1157 .cloned()
1158 .collect()
1159 }
1160
1161 pub fn is_protocol_available(
1163 &self,
1164 protocol_name: &str,
1165 caps: &crate::privilege::SystemCapabilities,
1166 ) -> bool {
1167 if let Some(protocol) = self.get(protocol_name) {
1168 let dependencies = protocol.get_dependencies();
1169 dependencies.iter().all(|dep| dep.is_available(caps))
1170 } else {
1171 false
1172 }
1173 }
1174}
1175
1176pub(crate) const ALL_KNOWN_CLIENT_PROTOCOLS: &[(&str, &str)] = &[
1187 ("BGP", "nothing (every build has it)"),
1188 ("BOOTP", "nothing (every build has it)"),
1189 ("Cassandra", "a native build (not the browser)"),
1190 ("DataLink", "the `libpcap` feature, on Unix"),
1191 ("NMDC Peer", "a native build (not the browser)"),
1192 ("ADC", "a native build (not the browser)"),
1193 ("ADC Peer", "a native build (not the browser)"),
1194 ("Soulseek", "a native build (not the browser)"),
1195 ("Soulseek Peer", "a native build (not the browser)"),
1196 ("Gnutella", "a native build (not the browser)"),
1197 ("DC", "nothing (every build has it)"),
1198 ("DHCP", "nothing (every build has it)"),
1199 ("DNS", "a native build (not the browser)"),
1200 ("DNS-over-HTTPS", "a native build (not the browser)"),
1201 ("DoT", "a native build (not the browser)"),
1202 ("DoQ", "a native build (not the browser)"),
1203 ("etcd", "a native build (not the browser)"),
1204 ("ZooKeeper", "a native build (not the browser)"),
1205 ("gRPC", "a native build (not the browser)"),
1206 ("gRPC-Web", "a native build (not the browser)"),
1207 ("gNMI", "a native build (not the browser)"),
1208 ("ConnectRPC", "a native build (not the browser)"),
1209 ("TACACS", "a native build (not the browser)"),
1210 ("NETCONF", "a native build (not the browser)"),
1211 ("RPKI-RTR", "a native build (not the browser)"),
1212 ("RDAP", "a native build (not the browser)"),
1213 ("HL7", "a native build (not the browser)"),
1214 ("ICAP", "a native build (not the browser)"),
1215 ("OCPP", "a native build (not the browser)"),
1216 ("A2A", "a native build (not the browser)"),
1217 ("GraphQL", "a native build (not the browser)"),
1218 ("FastCGI", "a native build (not the browser)"),
1219 ("Redfish", "a native build (not the browser)"),
1220 ("SCIM", "a native build (not the browser)"),
1221 ("Socket.IO", "a native build (not the browser)"),
1222 ("CalDAV", "a native build (not the browser)"),
1223 ("CardDAV", "a native build (not the browser)"),
1224 ("DICOM", "a native build (not the browser)"),
1225 ("ACME", "a native build (not the browser)"),
1226 ("FIX", "a native build (not the browser)"),
1227 ("WAMP", "a native build (not the browser)"),
1228 ("RTMP", "a native build (not the browser)"),
1229 ("SRT", "a native build (not the browser)"),
1230 ("AMQP1", "a native build (not the browser)"),
1231 ("Thrift", "a native build (not the browser)"),
1232 ("BMP", "a native build (not the browser)"),
1233 ("MQTT-SN", "a native build (not the browser)"),
1234 ("NBD", "a native build (not the browser)"),
1235 ("ManageSieve", "a native build (not the browser)"),
1236 ("Zenoh", "a native build (not the browser)"),
1237 ("LwM2M", "a native build (not the browser)"),
1238 ("RESTCONF", "a native build (not the browser)"),
1239 ("WebTransport", "a native build (not the browser)"),
1240 ("JMAP", "a native build (not the browser)"),
1241 ("EPP", "a native build (not the browser)"),
1242 ("LMTP", "a native build (not the browser)"),
1243 ("LPD", "a native build (not the browser)"),
1244 ("Echo", "a native build (not the browser)"),
1245 ("Discard", "a native build (not the browser)"),
1246 ("Daytime", "a native build (not the browser)"),
1247 ("QOTD", "a native build (not the browser)"),
1248 ("Chargen", "a native build (not the browser)"),
1249 ("Time", "a native build (not the browser)"),
1250 ("RCON", "a native build (not the browser)"),
1251 ("A2S", "a native build (not the browser)"),
1252 ("Minecraft", "a native build (not the browser)"),
1253 ("9P", "a native build (not the browser)"),
1254 ("ZeroMQ", "a native build (not the browser)"),
1255 ("SMPP", "a native build (not the browser)"),
1256 ("ClickHouse", "a native build (not the browser)"),
1257 ("MessagePack-RPC", "a native build (not the browser)"),
1258 ("Zipkin", "a native build (not the browser)"),
1259 ("Cap'n Proto RPC", "a native build (not the browser)"),
1260 ("KNX/IP", "a native build (not the browser)"),
1261 ("Consul", "a native build (not the browser)"),
1262 ("Milter", "a native build (not the browser)"),
1263 ("Anthropic", "a native build (not the browser)"),
1264 ("RadSec", "a native build (not the browser)"),
1265 ("D-Bus", "a native build (not the browser)"),
1266 ("BFD", "a native build (not the browser)"),
1267 ("VXLAN", "a native build (not the browser)"),
1268 ("rsync", "a native build (not the browser)"),
1269 ("RDP", "a Unix build (macOS or Linux)"),
1270 ("SPICE", "a native build (not the browser)"),
1271 ("PFCP", "a native build (not the browser)"),
1272 ("GTP", "nothing (every build has it)"),
1273 ("Db2", "nothing (every build has it)"),
1274 ("Snowflake", "nothing (every build has it)"),
1275 ("Spark", "nothing (every build has it)"),
1276 ("YARN", "nothing (every build has it)"),
1277 ("M3UA", "a Unix build (macOS or Linux)"),
1278 ("NGAP", "a Unix build (macOS or Linux)"),
1279 ("Matrix", "a native build (not the browser)"),
1280 ("libp2p", "a native build (not the browser)"),
1281 ("SunRPC", "a native build (not the browser)"),
1282 ("Guacamole", "a native build (not the browser)"),
1283 ("ActivityPub", "a native build (not the browser)"),
1284 ("Stratum", "a native build (not the browser)"),
1285 ("Stratum V2", "a native build (not the browser)"),
1286 ("Apache Pulsar", "a native build (not the browser)"),
1287 ("TR-069 (CWMP)", "a native build (not the browser)"),
1288 ("SSE", "a native build (not the browser)"),
1289 ("Zabbix", "a native build (not the browser)"),
1290 ("RTP", "nothing (every build has it)"),
1291 ("RTSP", "nothing (every build has it)"),
1292 ("HLS", "nothing (every build has it)"),
1293 ("WS-Discovery", "a native build (not the browser)"),
1294 ("ONVIF", "a native build (not the browser)"),
1295 ("Kerberos", "a native build (not the browser)"),
1296 ("X11", "a native build (not the browser)"),
1297 ("HTTP", "nothing (every build has it)"),
1298 ("HTTP2", "nothing (every build has it)"),
1299 ("HTTP3", "a native build (not the browser)"),
1300 ("QUIC", "a native build (not the browser)"),
1301 ("igmp", "a Unix build (macOS or Linux)"),
1302 ("IPP", "a native build (not the browser)"),
1303 ("IS-IS", "the `libpcap` feature, on Unix"),
1304 ("JSON-RPC", "nothing (every build has it)"),
1305 ("Kafka", "nothing (every build has it)"),
1306 ("mDNS", "a native build (not the browser)"),
1307 ("AMQP", "a native build (not the browser)"),
1308 ("MySQL", "a native build (not the browser)"),
1309 ("nfc", "the `pcsc` feature, on Unix"),
1310 ("NTP", "a native build (not the browser)"),
1311 ("OpenAI", "a native build (not the browser)"),
1312 ("OTLP", "a native build (not the browser)"),
1313 ("PostgreSQL", "a native build (not the browser)"),
1314 ("PyPI", "nothing (every build has it)"),
1315 ("MSSQL", "a native build (not the browser)"),
1316 ("Redis", "a native build (not the browser)"),
1317 ("ICMP", "a Unix build (macOS or Linux)"),
1318 ("RIP", "a native build (not the browser)"),
1319 ("SAML", "nothing (every build has it)"),
1320 ("SIP", "nothing (every build has it)"),
1321 ("H323", "a native build (not the browser)"),
1322 ("SMTP", "a native build (not the browser)"),
1323 ("FTP", "nothing (every build has it)"),
1324 ("POP3", "nothing (every build has it)"),
1325 ("SNMP", "nothing (every build has it)"),
1326 ("SOCKS5", "a native build (not the browser)"),
1327 ("SocketFile", "a Unix build (macOS or Linux)"),
1328 ("SSH", "a native build (not the browser)"),
1329 ("SSH Agent", "a Unix build (macOS or Linux)"),
1330 ("Syslog", "nothing (every build has it)"),
1331 ("FluentForward", "a native build (not the browser)"),
1332 ("NETFLOWV9", "a native build (not the browser)"),
1333 ("NETFLOW-V9", "a native build (not the browser)"),
1334 ("NETFLOW_V9", "a native build (not the browser)"),
1335 ("IPFIX", "a native build (not the browser)"),
1336 ("IPFIX-UDP", "a native build (not the browser)"),
1337 ("PrometheusRemoteWrite", "a native build (not the browser)"),
1338 ("RemoteWrite", "a native build (not the browser)"),
1339 ("sFlow", "a native build (not the browser)"),
1340 ("sFlow-v5", "a native build (not the browser)"),
1341 ("Loki", "a native build (not the browser)"),
1342 ("LokiPush", "a native build (not the browser)"),
1343 ("InfluxDB", "a native build (not the browser)"),
1344 ("InfluxDB2", "a native build (not the browser)"),
1345 ("GELF", "a native build (not the browser)"),
1346 ("Graylog", "a native build (not the browser)"),
1347 ("Graphite", "a native build (not the browser)"),
1348 ("StatsD", "a native build (not the browser)"),
1349 ("DogStatsD", "a native build (not the browser)"),
1350 ("TCP", "nothing (every build has it)"),
1351 ("Telnet", "nothing (every build has it)"),
1352 ("TLS", "nothing (every build has it)"),
1353 ("Tor", "a Unix build (macOS or Linux)"),
1354 ("BitTorrent DHT", "nothing (every build has it)"),
1355 ("BitTorrent Peer Wire", "nothing (every build has it)"),
1356 ("BitTorrent Tracker", "nothing (every build has it)"),
1357 ("TURN", "a Unix build (macOS or Linux)"),
1358 ("UDP", "nothing (every build has it)"),
1359 ("Gemini", "a native build (not the browser)"),
1360 ("DICT", "a native build (not the browser)"),
1361 ("Beanstalkd", "a native build (not the browser)"),
1362 ("Gearman", "a native build (not the browser)"),
1363 ("Prometheus", "a native build (not the browser)"),
1364 ("Docker", "a native build (not the browser)"),
1365 ("Vault", "a native build (not the browser)"),
1366 ("Nostr", "a native build (not the browser)"),
1367 ("Bolt", "a native build (not the browser)"),
1368 ("OCI-Registry", "nothing (every build has it)"),
1369 ("NSQ", "a native build (not the browser)"),
1370 ("NUT", "a native build (not the browser)"),
1371 ("WHOIS", "nothing (every build has it)"),
1372 ("STOMP", "nothing (every build has it)"),
1373 ("NetBIOS-NS", "nothing (every build has it)"),
1374 ("NATS", "nothing (every build has it)"),
1375 ("Memcached", "nothing (every build has it)"),
1376 ("Modbus", "nothing (every build has it)"),
1377 ("CoAP", "nothing (every build has it)"),
1378 ("RADIUS", "nothing (every build has it)"),
1379 ("DIAMETER", "a native build (not the browser)"),
1380 ("SSDP", "nothing (every build has it)"),
1381 ("Gopher", "nothing (every build has it)"),
1382 ("Finger", "nothing (every build has it)"),
1383 ("Ident", "nothing (every build has it)"),
1384 ("LLMNR", "a native build (not the browser)"),
1385 ("wireguard", "a Unix build (macOS or Linux)"),
1386 ("XML-RPC", "a native build (not the browser)"),
1387 ("XMPP", "a Unix build (macOS or Linux)"),
1388 ("ARP", "the `libpcap` feature, on Unix"),
1389 ("HTTP Proxy", "a Unix build (macOS or Linux)"),
1390 ("IMAP", "a native build (not the browser)"),
1391 ("IRC", "a native build (not the browser)"),
1392 ("MCP", "a native build (not the browser)"),
1393 ("MQTT", "a native build (not the browser)"),
1394 ("MongoDB", "a native build (not the browser)"),
1395 ("NNTP", "nothing (every build has it)"),
1396 ("NPM", "nothing (every build has it)"),
1397 ("Ollama", "nothing (every build has it)"),
1398 ("OpenAPI", "nothing (every build has it)"),
1399 ("ospf", "a Unix build (macOS or Linux)"),
1400 ("SMB", "the `libsmbclient` feature, on Unix"),
1401 ("SQS", "a native build (not the browser)"),
1402 ("STUN", "a Unix build (macOS or Linux)"),
1403 ("USB", "a native build (not the browser)"),
1404 ("VNC", "nothing (every build has it)"),
1405 ("WebRTC", "a native build (not the browser)"),
1406 ("WebSocket", "a native build (not the browser)"),
1407 ("Bitcoin", "nothing (every build has it)"),
1408 ("devp2p", "a native build (not the browser)"),
1409 ("Bluetooth (BLE)", "the `bluetooth` feature, on Unix"),
1410 ("DynamoDB", "a Unix build (macOS or Linux)"),
1411 ("Elasticsearch", "nothing (every build has it)"),
1412 ("Git", "a native build (not the browser)"),
1413 ("Kubernetes", "a native build (not the browser)"),
1414 ("LDAP", "a native build (not the browser)"),
1415 ("Maven", "nothing (every build has it)"),
1416 ("NFS", "a Unix build (macOS or Linux)"),
1417 ("OAuth2", "nothing (every build has it)"),
1418 ("OpenIDConnect", "nothing (every build has it)"),
1419 ("CouchDB", "a native build (not the browser)"),
1420 ("S3", "a native build (not the browser)"),
1421 ("WebDAV", "a native build (not the browser)"),
1422];
1423
1424#[derive(Debug, Clone, PartialEq, Eq)]
1427pub enum ClientProtocolLookupError {
1428 NotCompiled {
1431 name: &'static str,
1432 requires: &'static str,
1433 },
1434 Unknown {
1437 input: String,
1438 suggestion: Option<&'static str>,
1439 },
1440}
1441
1442impl std::fmt::Display for ClientProtocolLookupError {
1443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1444 match self {
1445 ClientProtocolLookupError::NotCompiled { name, requires } => write!(
1446 f,
1447 "Client protocol '{name}' exists but is not compiled into this build: it needs {requires}"
1448 ),
1449 ClientProtocolLookupError::Unknown {
1450 input,
1451 suggestion: Some(s),
1452 } => write!(f, "Unknown client protocol: '{input}'. Did you mean '{s}'?"),
1453 ClientProtocolLookupError::Unknown {
1454 input,
1455 suggestion: None,
1456 } => write!(f, "Unknown client protocol: '{input}'"),
1457 }
1458 }
1459}
1460
1461impl std::error::Error for ClientProtocolLookupError {}
1462
1463fn suggest_client_protocol_name(
1466 input: &str,
1467 known: &[(&'static str, &'static str)],
1468) -> Option<&'static str> {
1469 let input_norm = input.to_lowercase();
1470 let mut best: Option<(&'static str, usize)> = None;
1471
1472 for (name, _feature) in known {
1473 let distance = levenshtein(&input_norm, &name.to_lowercase());
1474 best = match best {
1475 Some((_, best_dist)) if best_dist <= distance => best,
1476 _ => Some((name, distance)),
1477 };
1478 }
1479
1480 best.and_then(|(name, distance)| {
1481 let threshold = (input_norm.chars().count() / 3).max(2);
1482 if distance <= threshold {
1483 Some(name)
1484 } else {
1485 None
1486 }
1487 })
1488}
1489
1490fn levenshtein(a: &str, b: &str) -> usize {
1494 let a: Vec<char> = a.chars().collect();
1495 let b: Vec<char> = b.chars().collect();
1496 let mut dp: Vec<usize> = (0..=b.len()).collect();
1497
1498 for i in 1..=a.len() {
1499 let mut prev = dp[0];
1500 dp[0] = i;
1501 for j in 1..=b.len() {
1502 let tmp = dp[j];
1503 dp[j] = if a[i - 1] == b[j - 1] {
1504 prev
1505 } else {
1506 1 + prev.min(dp[j]).min(dp[j - 1])
1507 };
1508 prev = tmp;
1509 }
1510 }
1511
1512 dp[b.len()]
1513}
1514
1515pub static CLIENT_REGISTRY: std::sync::LazyLock<ClientRegistry> =
1521 std::sync::LazyLock::new(ClientRegistry::new);