1use std::{
7 future::Future,
8 io,
9 net::SocketAddr,
10 sync::{
11 Arc, OnceLock,
12 atomic::{AtomicI32, AtomicU64, Ordering},
13 },
14 time::{self, Duration},
15};
16
17use bytes::{BufMut, Bytes, BytesMut};
18use dashmap::DashMap;
19use krabka_ids::{ApiKey, ApiVersion};
20use krabka_protocol::owned::{api_versions_request, api_versions_response};
21use krabka_units::{
22 ByteSize, Time,
23 convert::{ByteSizeExt as _, TimeExt as _},
24 kibibytes, mebibytes, millis, minutes, secs,
25};
26use refined_type::rule::{GreaterI64, GreaterUsize};
27use tokio::{
28 io::{AsyncRead, AsyncWrite, split},
29 net::TcpStream,
30 sync::{RwLockReadGuard, mpsc, oneshot},
31 task::JoinHandle,
32 time::{Instant, sleep_until, timeout},
33};
34use tokio_util::sync::CancellationToken;
35use tracing::field;
36
37#[cfg(test)]
38use crate::mock;
39use crate::{
40 error::{AuthenticationError, ClientError},
41 framing, reauth,
42 request::ProtocolRequest,
43 sasl, security, telemetry,
44 transport::{SocketOptions, dial_address},
45 version::ApiVersionTable,
46};
47
48pub trait ClientDuplex: AsyncRead + AsyncWrite + Send + Unpin {}
54impl<T: AsyncRead + AsyncWrite + Send + Unpin + ?Sized> ClientDuplex for T {}
55
56type Pending = Arc<DashMap<i32, oneshot::Sender<Result<Bytes, ClientError>>>>;
57
58const API_VERSIONS_KEY: i16 = 18;
64
65pub const DEFAULT_CLIENT_DNS_TIMEOUT: Time = secs(10);
67pub const DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT: Time = secs(10);
70pub const DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MAX: Time = secs(30);
73pub const DEFAULT_RECONNECT_BACKOFF: Time = millis(50);
75pub const DEFAULT_RECONNECT_BACKOFF_MAX: Time = secs(1);
77pub const DEFAULT_CONNECTIONS_MAX_IDLE: Time = minutes(9);
80pub const DEFAULT_SEND_BUFFER: ByteSize = kibibytes(128);
82pub const DEFAULT_RECEIVE_BUFFER: ByteSize = kibibytes(64);
85pub const DEFAULT_CLIENT_REQUEST_TIMEOUT: Time = secs(30);
87pub const DEFAULT_CONNECTION_DISPATCH_QUEUE_CAPACITY: usize = 64;
89pub const MAX_CLIENT_FRAME_BYTES: ByteSize = mebibytes(100);
91pub const DEFAULT_CLIENT_FRAME_MAX: ByteSize = MAX_CLIENT_FRAME_BYTES;
93const DEFAULT_CLIENT_FRAME_MAX_BYTES: usize = 100 * 1024 * 1024;
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq, derive_more::Into)]
101pub struct ClientDnsTimeout(i64);
102
103impl ClientDnsTimeout {
104 pub fn new(value: Time) -> Result<Self, String> {
112 let milliseconds = GreaterI64::<0>::new(value.millis_i64())
113 .map_err(|error| format!("client DNS timeout: {error}"))?
114 .into_value();
115 if !value.secs_f64().is_finite() || Time::from_millis(milliseconds) != value {
116 return Err("client DNS timeout must be a whole number of milliseconds".to_owned());
117 }
118 Ok(Self(milliseconds))
119 }
120
121 #[must_use]
123 pub fn time(self) -> Time {
124 Time::from_millis(self.0)
125 }
126
127 #[must_use]
129 pub const fn milliseconds(self) -> i64 {
130 self.0
131 }
132}
133
134impl Default for ClientDnsTimeout {
135 fn default() -> Self {
136 Self(DEFAULT_CLIENT_DNS_TIMEOUT.millis_i64())
137 }
138}
139
140#[derive(Clone, Copy, Debug, Eq, PartialEq, derive_more::Into)]
142pub struct ConnectionDispatchQueueCapacity(usize);
143
144impl ConnectionDispatchQueueCapacity {
145 pub fn new(value: usize) -> Result<Self, String> {
151 GreaterUsize::<0>::new(value)
152 .map(|value| Self(value.into_value()))
153 .map_err(|error| format!("client dispatch queue capacity: {error}"))
154 }
155
156 #[must_use]
158 pub const fn get(self) -> usize {
159 self.0
160 }
161}
162
163impl Default for ConnectionDispatchQueueCapacity {
164 fn default() -> Self {
165 Self(DEFAULT_CONNECTION_DISPATCH_QUEUE_CAPACITY)
166 }
167}
168
169#[derive(Clone, Copy, Debug, Eq, PartialEq, derive_more::Into)]
171pub struct ClientFrameMax(usize);
172
173impl ClientFrameMax {
174 #[must_use]
176 pub const fn bytes(self) -> usize {
177 self.0
178 }
179
180 #[must_use]
182 pub fn size(self) -> ByteSize {
183 ByteSize::from_bytes(u64::try_from(self.0).unwrap_or(u64::MAX))
184 }
185}
186
187impl TryFrom<ByteSize> for ClientFrameMax {
188 type Error = String;
189
190 fn try_from(value: ByteSize) -> Result<Self, Self::Error> {
191 let bytes = value.bytes_f64();
192 if !bytes.is_finite()
193 || bytes.fract() != 0.0
194 || !(1.0..=MAX_CLIENT_FRAME_BYTES.bytes_f64()).contains(&bytes)
195 {
196 return Err(
197 "client frame max must be a positive whole-byte value no greater than 100MiB"
198 .to_owned(),
199 );
200 }
201 usize::try_from(value.bytes_u64())
202 .map(Self)
203 .map_err(|_| "client frame max does not fit usize".to_owned())
204 }
205}
206
207impl Default for ClientFrameMax {
208 fn default() -> Self {
209 Self(DEFAULT_CLIENT_FRAME_MAX_BYTES)
210 }
211}
212
213#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
215pub enum ClientDnsLookup {
216 #[default]
219 UseAllDnsIps,
220 ResolveCanonicalBootstrapServersOnly,
224}
225
226#[derive(Debug, Clone)]
228pub struct ConnectionOptions {
229 pub client_id: String,
230 pub dns_timeout: ClientDnsTimeout,
231 pub socket_connection_setup_timeout: Time,
236 pub socket_connection_setup_timeout_max: Time,
238 pub reconnect_backoff: Time,
242 pub reconnect_backoff_max: Time,
244 pub connections_max_idle: Time,
247 pub client_dns_lookup: ClientDnsLookup,
249 pub send_buffer: Option<ByteSize>,
252 pub receive_buffer: Option<ByteSize>,
255 pub request_timeout: Time,
256 pub dispatch_queue_capacity: ConnectionDispatchQueueCapacity,
257 pub frame_max: ClientFrameMax,
258 pub security: Option<Box<security::ClientSecurity>>,
265 pub network_metrics: Option<telemetry::NetworkMetrics>,
269}
270
271impl ConnectionOptions {
272 pub fn validate(&self) -> Result<(), String> {
279 for (name, value, allow_zero) in [
280 ("request_timeout", self.request_timeout, false),
281 (
282 "socket_connection_setup_timeout",
283 self.socket_connection_setup_timeout,
284 false,
285 ),
286 (
287 "socket_connection_setup_timeout_max",
288 self.socket_connection_setup_timeout_max,
289 false,
290 ),
291 ("reconnect_backoff", self.reconnect_backoff, true),
292 ("reconnect_backoff_max", self.reconnect_backoff_max, true),
293 ("connections_max_idle", self.connections_max_idle, true),
294 ] {
295 let seconds = value.secs_f64();
296 let duration = Duration::try_from_secs_f64(seconds);
297 if !seconds.is_finite()
298 || seconds < 0.0
299 || duration.as_ref().is_err()
300 || (!allow_zero && duration.as_ref().is_ok_and(Duration::is_zero))
301 || duration
302 .as_ref()
303 .is_ok_and(|duration| time::Instant::now().checked_add(*duration).is_none())
304 {
305 return Err(format!(
306 "{name} must be {} and representable as a transport deadline",
307 if allow_zero {
308 "nonnegative"
309 } else {
310 "positive"
311 }
312 ));
313 }
314 }
315 if self.socket_connection_setup_timeout_max < self.socket_connection_setup_timeout {
316 return Err("socket_connection_setup_timeout_max must not be below socket_connection_setup_timeout".into());
317 }
318 if self.reconnect_backoff_max < self.reconnect_backoff {
319 return Err("reconnect_backoff_max must not be below reconnect_backoff".into());
320 }
321 for (name, value) in [
322 ("send_buffer", self.send_buffer),
323 ("receive_buffer", self.receive_buffer),
324 ] {
325 if let Some(value) = value {
326 let bytes = value.bytes_f64();
327 if !bytes.is_finite()
328 || bytes < 1.0
329 || bytes > f64::from(i32::MAX)
330 || bytes.fract().abs() > f64::EPSILON
331 {
332 return Err(format!(
333 "{name} must contain positive whole bytes fitting i32"
334 ));
335 }
336 }
337 }
338 if let Some(mechanism) = self
339 .security
340 .as_ref()
341 .and_then(|security| security.sasl.as_ref())
342 .map(sasl::SaslCredentials::mechanism)
343 && !sasl::mechanism_supported(mechanism)
344 {
345 return Err(format!(
346 "SASL/{}: {}",
347 mechanism.wire_name(),
348 sasl::GSSAPI_UNAVAILABLE
349 ));
350 }
351 Ok(())
352 }
353}
354
355impl Default for ConnectionOptions {
356 fn default() -> Self {
357 Self {
358 client_id: "krabka".into(),
359 dns_timeout: ClientDnsTimeout::default(),
360 socket_connection_setup_timeout: DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT,
361 socket_connection_setup_timeout_max: DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MAX,
362 reconnect_backoff: DEFAULT_RECONNECT_BACKOFF,
363 reconnect_backoff_max: DEFAULT_RECONNECT_BACKOFF_MAX,
364 connections_max_idle: DEFAULT_CONNECTIONS_MAX_IDLE,
365 client_dns_lookup: ClientDnsLookup::default(),
366 send_buffer: Some(DEFAULT_SEND_BUFFER),
367 receive_buffer: Some(DEFAULT_RECEIVE_BUFFER),
368 request_timeout: DEFAULT_CLIENT_REQUEST_TIMEOUT,
369 dispatch_queue_capacity: ConnectionDispatchQueueCapacity::default(),
370 frame_max: ClientFrameMax::default(),
371 security: None,
372 network_metrics: None,
373 }
374 }
375}
376
377#[derive(Clone)]
379pub struct Connection {
380 inner: Arc<ConnectionInner>,
381}
382
383struct ConnectionInner {
384 versions: ApiVersionTable,
385 options: ConnectionOptions,
386 next_corr_id: AtomicI32,
387 pending: Pending,
388 writer_tx: mpsc::Sender<DispatchItem>,
389 shutdown: CancellationToken,
390 reauth: Option<reauth::Reauth>,
393 metrics: OnceLock<telemetry::ConnectionMetrics>,
396 _reader: JoinHandle<()>,
397 _writer: JoinHandle<()>,
398}
399
400impl ConnectionInner {
401 fn next_correlation_id(&self) -> i32 {
405 let last_normal = sasl::MIN_RESERVED_CORRELATION_ID - 1;
406 self.next_corr_id
407 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
408 Some(if current >= last_normal {
409 0
410 } else {
411 current + 1
412 })
413 })
414 .unwrap_or(0)
415 }
416}
417
418struct DispatchItem {
419 bytes: Bytes,
420}
421
422fn tls_handshake_error(addr: SocketAddr, source: io::Error) -> ClientError {
430 let rejected = source
431 .get_ref()
432 .and_then(|inner| inner.downcast_ref::<rustls::Error>())
433 .is_some_and(|error| {
434 !matches!(
435 error,
436 rustls::Error::AlertReceived(rustls::AlertDescription::CloseNotify)
437 )
438 });
439 if rejected {
440 ClientError::Authentication {
441 addr,
442 source: AuthenticationError::Tls(source),
443 }
444 } else {
445 ClientError::Tls { addr, source }
446 }
447}
448
449pub fn sasl_error(addr: SocketAddr, source: sasl::OutboundSaslError) -> ClientError {
452 match source {
453 sasl::OutboundSaslError::Authentication(error) => ClientError::Authentication {
454 addr,
455 source: error.into(),
456 },
457 source => ClientError::Sasl { addr, source },
458 }
459}
460
461struct Activity {
464 started: Instant,
465 last_ms: AtomicU64,
466}
467
468impl Activity {
469 fn new() -> Self {
470 Self {
471 started: Instant::now(),
472 last_ms: AtomicU64::new(0),
473 }
474 }
475
476 fn touch(&self) {
477 let elapsed = u64::try_from(self.started.elapsed().as_millis()).unwrap_or(u64::MAX);
478 self.last_ms.fetch_max(elapsed, Ordering::Relaxed);
479 }
480
481 fn last(&self) -> Instant {
482 self.started + Duration::from_millis(self.last_ms.load(Ordering::Relaxed))
483 }
484}
485
486async fn tcp_connect(
489 addr: SocketAddr,
490 options: &ConnectionOptions,
491) -> Result<TcpStream, ClientError> {
492 let socket = SocketOptions {
493 send_buffer: options.send_buffer,
494 receive_buffer: options.receive_buffer,
495 nodelay: true,
496 };
497 dial_address(addr, socket)
498 .await
499 .map_err(|source| ClientError::Connect { addr, source })
500}
501
502async fn within_setup_timeout<T>(
508 options: &ConnectionOptions,
509 setup: impl Future<Output = Result<T, ClientError>>,
510) -> Result<T, ClientError> {
511 options.validate().map_err(ClientError::InvalidConfig)?;
512 timeout(options.socket_connection_setup_timeout.to_std(), setup)
513 .await
514 .map_err(|_| ClientError::Timeout(options.socket_connection_setup_timeout))?
515}
516
517impl Connection {
518 #[tracing::instrument(level = "debug", skip_all, fields(addr = %addr), err(level = "debug"))]
520 pub async fn connect(
523 addr: SocketAddr,
524 options: ConnectionOptions,
525 ) -> Result<Self, ClientError> {
526 let stream = within_setup_timeout(&options, tcp_connect(addr, &options)).await?;
527 Self::from_stream(Box::new(stream), options).await
528 }
529
530 #[tracing::instrument(
541 level = "debug",
542 skip_all,
543 fields(addr = %addr, secured = options.security.is_some()),
544 err(level = "debug"),
545 )]
546 pub async fn connect_with_options(
547 addr: SocketAddr,
548 options: ConnectionOptions,
549 ) -> Result<Self, ClientError> {
550 let metrics = options.network_metrics.clone();
551 let connection = match options.security.clone() {
555 Some(sec) => Box::pin(Self::connect_secured(addr, options, sec.as_ref())).await,
556 None => Box::pin(Self::connect(addr, options)).await,
557 }?;
558 if let Some(metrics) = &metrics {
559 connection.attach_metrics(metrics);
560 }
561 Ok(connection)
562 }
563
564 #[tracing::instrument(
579 level = "debug",
580 skip_all,
581 fields(addr = %addr, protocol = ?security.protocol),
582 err(level = "debug"),
583 )]
584 pub async fn connect_secured(
585 addr: SocketAddr,
586 options: ConnectionOptions,
587 security: &security::ClientSecurity,
588 ) -> Result<Self, ClientError> {
589 let (stream, reauth) = within_setup_timeout(&options, async {
590 let tcp = tcp_connect(addr, &options).await?;
591 Self::secure_stream(addr, tcp, &options, security).await
592 })
593 .await?;
594 Self::from_stream_with_reauth(stream, options, reauth).await
595 }
596
597 async fn secure_stream(
599 addr: SocketAddr,
600 tcp: TcpStream,
601 options: &ConnectionOptions,
602 security: &security::ClientSecurity,
603 ) -> Result<(Box<dyn ClientDuplex>, Option<reauth::Reauth>), ClientError> {
604 let mut stream: Box<dyn ClientDuplex> = if security.protocol.requires_tls() {
606 let tls = security.tls.as_ref().ok_or_else(|| {
607 ClientError::Io(io::Error::other("TLS protocol without tls config"))
608 })?;
609 let connector = tls
610 .connector()
611 .map_err(|error| ClientError::InvalidConfig(error.to_string()))?;
612 let server_name = if tls.server_name.is_empty() {
613 addr.ip().to_string()
614 } else {
615 tls.server_name.clone()
616 };
617 let sni = tokio_rustls::rustls::pki_types::ServerName::try_from(server_name)
618 .map_err(|e| ClientError::Io(io::Error::other(format!("invalid SNI: {e}"))))?;
619 let s = connector
620 .connect(sni, tcp)
621 .await
622 .map_err(|source| tls_handshake_error(addr, source))?;
623 Box::new(s)
624 } else {
625 Box::new(tcp)
626 };
627
628 let mut reauth = None;
630 if security.protocol.requires_sasl() {
631 let creds = security.sasl.as_ref().ok_or_else(|| {
632 ClientError::Io(io::Error::other("SASL protocol without credentials"))
633 })?;
634 let target = addr.ip().to_string();
639 let server_name = security.sasl_handshake_host(Some(target.as_str()));
640 let session = sasl::outbound_sasl(
641 &mut *stream,
642 creds,
643 server_name,
644 &options.client_id,
645 options.frame_max,
646 )
647 .await
648 .map_err(|source| sasl_error(addr, source))?;
649 reauth = session
650 .needs_reauthentication()
651 .then(|| reauth::Reauth::new(addr, creds.clone(), server_name.to_owned(), session));
652 }
653 Ok((stream, reauth))
654 }
655
656 #[tracing::instrument(level = "debug", skip_all, err(level = "debug"))]
665 pub async fn from_stream(
670 stream: Box<dyn ClientDuplex>,
671 options: ConnectionOptions,
672 ) -> Result<Self, ClientError> {
673 Self::from_stream_with_reauth(stream, options, None).await
674 }
675
676 pub(crate) async fn from_stream_with_reauth(
679 stream: Box<dyn ClientDuplex>,
680 options: ConnectionOptions,
681 reauth: Option<reauth::Reauth>,
682 ) -> Result<Self, ClientError> {
683 options.validate().map_err(ClientError::InvalidConfig)?;
684 let (writer_tx, writer_rx) =
685 mpsc::channel::<DispatchItem>(options.dispatch_queue_capacity.get());
686 let shutdown = CancellationToken::new();
687 let pending: Pending = Arc::new(DashMap::new());
688 let activity = Arc::new(Activity::new());
689
690 let (reader_handle, writer_handle) = spawn_io_tasks(
691 stream,
692 writer_rx,
693 IoContext {
694 shutdown: shutdown.clone(),
695 pending: Arc::clone(&pending),
696 activity,
697 frame_max: options.frame_max,
698 max_idle: options.connections_max_idle.to_std(),
699 },
700 );
701
702 let mut inner = ConnectionInner {
703 versions: ApiVersionTable::default(),
704 options: options.clone(),
705 next_corr_id: AtomicI32::new(0),
706 pending,
707 writer_tx,
708 shutdown,
709 reauth,
710 metrics: OnceLock::new(),
711 _reader: reader_handle,
712 _writer: writer_handle,
713 };
714 inner.versions = fetch_api_versions(&inner).await?;
715
716 Ok(Self {
717 inner: Arc::new(inner),
718 })
719 }
720
721 #[must_use]
727 pub fn advertised_api_range(&self, api_key: i16) -> Option<(i16, i16)> {
728 self.inner.versions.broker_range(api_key)
729 }
730
731 #[cfg_attr(test, mutants::skip)]
744 #[tracing::instrument(
745 level = "debug",
746 skip_all,
747 fields(api_key = R::API_KEY, version = field::Empty),
748 err(level = "debug"),
749 )]
750 pub async fn send<R: ProtocolRequest>(&self, req: R) -> Result<R::Response, ClientError> {
751 let version = self.inner.versions.negotiate::<R>()?;
753 tracing::Span::current().record("version", version);
754
755 let corr_id = self.next_correlation_id();
757
758 let body_flexible = version >= R::FLEXIBLE_MIN;
764 let mut frame = build_request_header(
765 ApiKey(R::API_KEY),
766 ApiVersion(version),
767 corr_id,
768 &self.inner.options.client_id,
769 body_flexible,
770 )?;
771 req.encode(&mut frame, version)?;
772
773 let body_bytes = self.dispatch_request(corr_id, frame).await?;
775
776 let mut cursor: &[u8] = &body_bytes;
789 let uses_flexible_resp_header = body_flexible && R::API_KEY != API_VERSIONS_KEY;
790 if uses_flexible_resp_header {
791 cursor = skip_tagged_fields(cursor)?;
792 }
793
794 let resp = <R::Response as krabka_protocol::Decode>::decode(&mut cursor, version)?;
795 Ok(resp)
796 }
797
798 pub async fn send_no_response<R: ProtocolRequest>(&self, req: R) -> Result<(), ClientError> {
806 let version = self.inner.versions.negotiate::<R>()?;
807 let corr_id = self.next_correlation_id();
808 let body_flexible = version >= R::FLEXIBLE_MIN;
809 let mut frame = build_request_header(
810 ApiKey(R::API_KEY),
811 ApiVersion(version),
812 corr_id,
813 &self.inner.options.client_id,
814 body_flexible,
815 )?;
816 req.encode(&mut frame, version)?;
817 let guard = self.reauthenticate_if_due().await?;
818 if let Some(metrics) = self.inner.metrics.get() {
819 metrics.request(frame.len());
820 }
821 let sent = self
822 .inner
823 .writer_tx
824 .send(DispatchItem {
825 bytes: frame.freeze(),
826 })
827 .await
828 .map_err(|_| ClientError::Disconnected);
829 drop(guard);
830 sent
831 }
832
833 #[cfg_attr(test, mutants::skip)]
856 #[tracing::instrument(
857 level = "debug",
858 skip_all,
859 fields(api_key, api_version),
860 err(level = "debug")
861 )]
862 pub async fn raw_request(
863 &self,
864 api_key: i16,
865 api_version: i16,
866 body: Bytes,
867 ) -> Result<Bytes, ClientError> {
868 let corr_id = self.next_correlation_id();
869
870 let mut frame = build_request_header(
873 ApiKey(api_key),
874 ApiVersion(api_version),
875 corr_id,
876 &self.inner.options.client_id,
877 true,
878 )?;
879 frame.put_slice(&body);
880
881 let body_bytes = self.dispatch_request(corr_id, frame).await?;
882
883 let body = skip_tagged_fields(&body_bytes)?;
886 Ok(body_bytes.slice(body_bytes.len() - body.len()..))
887 }
888
889 #[must_use]
892 #[cfg_attr(test, mutants::skip)]
893 pub fn versions(&self) -> &ApiVersionTable {
894 &self.inner.versions
895 }
896
897 #[must_use]
899 pub fn in_flight(&self) -> usize {
900 self.inner.pending.len()
901 }
902
903 fn next_correlation_id(&self) -> i32 {
907 self.inner.next_correlation_id()
908 }
909
910 #[must_use]
914 pub fn is_closed(&self) -> bool {
915 self.inner.shutdown.is_cancelled()
916 }
917
918 #[cfg_attr(test, mutants::skip)]
921 pub fn close(self) {
922 self.inner.shutdown.cancel();
923 }
925
926 async fn dispatch_request(&self, corr_id: i32, frame: BytesMut) -> Result<Bytes, ClientError> {
927 let guard = self.reauthenticate_if_due().await?;
928 let _registration = PendingRegistration {
931 pending: &self.inner.pending,
932 corr_id,
933 };
934 let metrics = self.inner.metrics.get();
935 if let Some(metrics) = metrics {
936 metrics.request(frame.len());
937 }
938 let rx = self.enqueue(corr_id, frame).await?;
939 drop(guard);
940 let response = self.await_response(corr_id, rx).await?;
941 if let Some(metrics) = metrics {
942 metrics.response(response.len());
943 }
944 Ok(response)
945 }
946
947 pub fn attach_metrics(&self, metrics: &telemetry::NetworkMetrics) {
951 self.inner.metrics.get_or_init(|| metrics.opened());
952 }
953
954 pub(crate) async fn dispatch_unguarded(
957 &self,
958 corr_id: i32,
959 frame: BytesMut,
960 ) -> Result<Bytes, ClientError> {
961 let _registration = PendingRegistration {
962 pending: &self.inner.pending,
963 corr_id,
964 };
965 let rx = self.enqueue(corr_id, frame).await?;
966 self.await_response(corr_id, rx).await
967 }
968
969 pub(crate) fn request_header(
971 &self,
972 api_key: ApiKey,
973 version: ApiVersion,
974 corr_id: i32,
975 flexible: bool,
976 ) -> Result<BytesMut, ClientError> {
977 build_request_header(
978 api_key,
979 version,
980 corr_id,
981 &self.inner.options.client_id,
982 flexible,
983 )
984 }
985
986 async fn reauthenticate_if_due(
993 &self,
994 ) -> Result<Option<RwLockReadGuard<'_, Option<Instant>>>, ClientError> {
995 let Some(reauth) = self.inner.reauth.as_ref() else {
996 return Ok(None);
997 };
998 loop {
999 let next = reauth.next.read().await;
1000 if next.is_none_or(|due| Instant::now() < due) {
1001 return Ok(Some(next));
1002 }
1003 drop(next);
1004 let mut next = reauth.next.write().await;
1005 if next.is_some_and(|due| Instant::now() >= due) {
1006 let mut channel = reauth::ConnectionChannel { connection: self };
1007 match Box::pin(reauth.authenticate(
1010 &mut channel,
1011 &self.inner.options.client_id,
1012 self.inner.options.frame_max,
1013 ))
1014 .await
1015 {
1016 Ok(due) => *next = due,
1017 Err(error) => {
1018 tracing::warn!(error = %error, "SASL re-authentication failed");
1019 self.inner.shutdown.cancel();
1020 return Err(error);
1021 }
1022 }
1023 }
1024 }
1025 }
1026
1027 async fn enqueue(
1029 &self,
1030 corr_id: i32,
1031 frame: BytesMut,
1032 ) -> Result<oneshot::Receiver<Result<Bytes, ClientError>>, ClientError> {
1033 let (tx, rx) = oneshot::channel::<Result<Bytes, ClientError>>();
1034 self.inner.pending.insert(corr_id, tx);
1035 self.inner
1036 .writer_tx
1037 .send(DispatchItem {
1038 bytes: frame.freeze(),
1039 })
1040 .await
1041 .map_err(|_| ClientError::Disconnected)?;
1042 Ok(rx)
1043 }
1044
1045 async fn await_response(
1047 &self,
1048 corr_id: i32,
1049 rx: oneshot::Receiver<Result<Bytes, ClientError>>,
1050 ) -> Result<Bytes, ClientError> {
1051 match timeout(self.inner.options.request_timeout.to_std(), rx).await {
1052 Ok(Ok(Ok(bytes))) => Ok(bytes),
1053 Ok(Ok(Err(err))) => Err(err),
1054 Ok(Err(_recv_closed)) => Err(ClientError::Disconnected),
1055 Err(_timeout) => {
1056 self.inner.pending.remove(&corr_id);
1057 self.inner.shutdown.cancel();
1061 Err(ClientError::Timeout(self.inner.options.request_timeout))
1062 }
1063 }
1064 }
1065}
1066
1067struct PendingRegistration<'a> {
1069 pending: &'a Pending,
1070 corr_id: i32,
1071}
1072
1073impl Drop for PendingRegistration<'_> {
1074 fn drop(&mut self) {
1075 self.pending.remove(&self.corr_id);
1076 }
1077}
1078
1079pub fn skip_tagged_fields(mut bytes: &[u8]) -> Result<&[u8], ClientError> {
1085 fn uvarint(bytes: &mut &[u8]) -> Result<u32, ClientError> {
1086 let mut value = 0_u32;
1087 for shift in (0..35).step_by(7) {
1088 let (&byte, rest) = bytes.split_first().ok_or_else(truncated)?;
1089 *bytes = rest;
1090 value |= u32::from(byte & 0x7F) << shift;
1091 if byte & 0x80 == 0 {
1092 return Ok(value);
1093 }
1094 }
1095 Err(truncated())
1096 }
1097 fn truncated() -> ClientError {
1098 ClientError::Io(io::Error::new(
1099 io::ErrorKind::InvalidData,
1100 "invalid tagged fields in a response header",
1101 ))
1102 }
1103 if bytes.is_empty() {
1104 return Ok(bytes);
1105 }
1106 for _ in 0..uvarint(&mut bytes)? {
1107 uvarint(&mut bytes)?;
1108 let size = usize::try_from(uvarint(&mut bytes)?).map_err(|_| truncated())?;
1109 bytes = bytes.get(size..).ok_or_else(truncated)?;
1110 }
1111 Ok(bytes)
1112}
1113
1114fn spawn_io_tasks(
1142 stream: Box<dyn ClientDuplex>,
1143 mut writer_rx: mpsc::Receiver<DispatchItem>,
1144 context: IoContext,
1145) -> (JoinHandle<()>, JoinHandle<()>) {
1146 use futures_util::StreamExt;
1147 use tokio_util::codec::{FramedRead, FramedWrite};
1148
1149 let IoContext {
1150 shutdown,
1151 pending,
1152 activity,
1153 frame_max,
1154 max_idle,
1155 } = context;
1156
1157 let (read_half, write_half) = split(stream);
1158 let mut framed_read = FramedRead::new(read_half, framing::codec_with_max(frame_max));
1159 let mut framed_write = FramedWrite::new(write_half, framing::codec_with_max(frame_max));
1160
1161 let writer_shutdown = shutdown.clone();
1165 let writer_activity = Arc::clone(&activity);
1166 let writer_pending = Arc::clone(&pending);
1167 let writer = tokio::spawn(async move {
1168 write_loop(
1169 &writer_shutdown,
1170 &writer_activity,
1171 &writer_pending,
1172 max_idle,
1173 &mut writer_rx,
1174 &mut framed_write,
1175 )
1176 .await;
1177 writer_shutdown.cancel();
1181 });
1182
1183 let reader = tokio::spawn(async move {
1186 loop {
1187 tokio::select! {
1188 () = shutdown.cancelled() => break,
1189 maybe_frame = framed_read.next() => {
1190 let Some(frame) = maybe_frame else { break; };
1191 let Ok(frame) = frame else { break; };
1192 activity.touch();
1193 let Some((corr_id, body)) = frame.split_first_chunk::<4>() else { continue; };
1194 let corr_id = i32::from_be_bytes(*corr_id);
1195 if let Some((_, tx)) = pending.remove(&corr_id) {
1196 let body = Bytes::copy_from_slice(body);
1197 let _ = tx.send(Ok(body));
1198 }
1199 }
1200 }
1201 }
1202 shutdown.cancel();
1204 let keys: Vec<i32> = pending.iter().map(|e| *e.key()).collect();
1205 for k in keys {
1206 if let Some((_, tx)) = pending.remove(&k) {
1207 let _ = tx.send(Err(ClientError::Disconnected));
1208 }
1209 }
1210 });
1211
1212 (reader, writer)
1213}
1214
1215struct IoContext {
1217 shutdown: CancellationToken,
1218 pending: Pending,
1219 activity: Arc<Activity>,
1220 frame_max: ClientFrameMax,
1221 max_idle: Duration,
1222}
1223
1224async fn write_loop<W>(
1228 shutdown: &CancellationToken,
1229 activity: &Activity,
1230 pending: &Pending,
1231 max_idle: Duration,
1232 writer_rx: &mut mpsc::Receiver<DispatchItem>,
1233 framed_write: &mut W,
1234) where
1235 W: futures_util::Sink<Bytes> + Unpin,
1236{
1237 use futures_util::SinkExt;
1238
1239 loop {
1240 let idle_deadline = activity.last() + max_idle;
1241 tokio::select! {
1242 () = shutdown.cancelled() => break,
1243 item = writer_rx.recv() => {
1244 let Some(item) = item else { break; };
1245 activity.touch();
1246 let written = tokio::select! {
1250 () = shutdown.cancelled() => break,
1251 written = framed_write.send(item.bytes) => written,
1252 };
1253 if written.is_err() {
1254 break;
1255 }
1256 activity.touch();
1257 }
1258 () = sleep_until(idle_deadline), if !max_idle.is_zero() => {
1259 let idle = activity.last() + max_idle <= Instant::now();
1260 if idle && pending.is_empty() && writer_rx.is_empty() {
1261 tracing::debug!(
1262 max_idle_ms = max_idle.as_millis(),
1263 "closing a connection idle for connections.max.idle.ms"
1264 );
1265 break;
1266 }
1267 if idle {
1268 activity.touch();
1271 }
1272 }
1273 }
1274 }
1275}
1276
1277fn build_request_header(
1295 api_key: ApiKey,
1296 version: ApiVersion,
1297 corr_id: i32,
1298 client_id: &str,
1299 with_tagged_fields: bool,
1300) -> Result<BytesMut, ClientError> {
1301 let mut buf = BytesMut::with_capacity(32);
1302 buf.put_i16(api_key.0);
1303 buf.put_i16(version.0);
1304 buf.put_i32(corr_id);
1305 let n = i16::try_from(client_id.len()).map_err(|_| {
1306 ClientError::InvalidConfig(format!(
1307 "client_id is {} bytes, longer than {} bytes",
1308 client_id.len(),
1309 i16::MAX
1310 ))
1311 })?;
1312 buf.put_i16(n);
1313 buf.put_slice(client_id.as_bytes());
1314 if with_tagged_fields {
1315 buf.put_u8(0); }
1317 Ok(buf)
1318}
1319
1320pub const CLIENT_SOFTWARE_NAME: &str = "krabka-client-rs";
1322
1323pub const CLIENT_SOFTWARE_VERSION: &str = env!("CARGO_PKG_VERSION");
1325
1326const UNSUPPORTED_VERSION: i16 = 35;
1328
1329pub fn api_versions_request() -> api_versions_request::ApiVersionsRequest {
1332 api_versions_request::ApiVersionsRequest {
1333 client_software_name: CLIENT_SOFTWARE_NAME.to_owned(),
1334 client_software_version: CLIENT_SOFTWARE_VERSION.to_owned(),
1335 ..Default::default()
1336 }
1337}
1338
1339pub fn decode_api_versions_response(
1346 body: &[u8],
1347 version: i16,
1348) -> Result<api_versions_response::ApiVersionsResponse, ClientError> {
1349 use krabka_protocol::{Decode as _, owned::api_versions_response::ApiVersionsResponse};
1350
1351 let decode = |version| {
1352 let mut cursor = body;
1353 let response = ApiVersionsResponse::decode(&mut cursor, version)?;
1354 if cursor.is_empty() {
1355 Ok(response)
1356 } else {
1357 Err(ClientError::Io(io::Error::new(
1358 io::ErrorKind::InvalidData,
1359 format!(
1360 "ApiVersions response v{version} has {} bytes after its end",
1361 cursor.len()
1362 ),
1363 )))
1364 }
1365 };
1366 match decode(version) {
1367 Err(_) if version != 0 => decode(0),
1368 result => result,
1369 }
1370}
1371
1372pub fn api_versions_retry_version(
1379 response: &api_versions_response::ApiVersionsResponse,
1380 version: i16,
1381) -> Option<i16> {
1382 use api_versions_request::API_KEY;
1383
1384 (response.error_code == UNSUPPORTED_VERSION && version > 0).then(|| {
1385 response
1386 .api_keys
1387 .iter()
1388 .find(|key| key.api_key == API_KEY)
1389 .map_or(0, |key| key.max_version)
1390 .min(version - 1)
1391 .max(0)
1392 })
1393}
1394
1395#[tracing::instrument(level = "debug", skip_all, err(level = "debug"))]
1403async fn fetch_api_versions(conn: &ConnectionInner) -> Result<ApiVersionTable, ClientError> {
1404 use krabka_protocol::{Encode, owned::api_versions_request::ApiVersionsRequest};
1405
1406 let mut version = ApiVersionsRequest::MAX_VERSION;
1407 loop {
1408 let corr_id = conn.next_correlation_id();
1409 let mut frame = build_request_header(
1410 ApiKey(ApiVersionsRequest::API_KEY),
1411 ApiVersion(version),
1412 corr_id,
1413 &conn.options.client_id,
1414 version >= ApiVersionsRequest::FLEXIBLE_MIN,
1415 )?;
1416 api_versions_request().encode(&mut frame, version)?;
1417
1418 let (tx, rx) = oneshot::channel::<Result<Bytes, ClientError>>();
1419 conn.pending.insert(corr_id, tx);
1420 conn.writer_tx
1421 .send(DispatchItem {
1422 bytes: frame.freeze(),
1423 })
1424 .await
1425 .map_err(|_| ClientError::Disconnected)?;
1426
1427 let body_bytes = timeout(conn.options.request_timeout.to_std(), rx)
1428 .await
1429 .map_err(|_| ClientError::Timeout(conn.options.request_timeout))?
1430 .map_err(|_| ClientError::Disconnected)??;
1431
1432 let response = decode_api_versions_response(&body_bytes, version)?;
1436 if let Some(retry) = api_versions_retry_version(&response, version) {
1437 version = retry;
1438 continue;
1439 }
1440 if response.error_code != 0 {
1441 return Err(ClientError::Server {
1442 error_code: response.error_code,
1443 });
1444 }
1445 return Ok(ApiVersionTable::from_response(&response));
1446 }
1447}
1448
1449#[cfg(test)]
1450mod tests {
1451 use assert2::assert;
1452 use krabka_units::{ByteSize, bytes, kibibytes, mebibytes, micros, millis};
1453 use tokio::io::duplex;
1454
1455 use super::*;
1456 use crate::Client;
1457
1458 #[test]
1459 fn client_dns_timeout_validates_and_preserves_milliseconds() {
1460 let timeout = ClientDnsTimeout::new(millis(37)).expect("positive timeout");
1461 assert!(timeout.time() == millis(37));
1462 assert!(timeout.milliseconds() == 37);
1463 assert!(ClientDnsTimeout::new(Time::ZERO).is_err());
1464 assert!(ClientDnsTimeout::new(micros(1)).is_err());
1465 assert!(ClientDnsTimeout::new(millis(1) + micros(1)).is_err());
1466 }
1467
1468 #[test]
1469 fn connection_options_default_to_kafka_client_settings() {
1470 let options = ConnectionOptions::default();
1471 assert!(options.reconnect_backoff == millis(50));
1472 assert!(options.reconnect_backoff_max == secs(1));
1473 assert!(options.socket_connection_setup_timeout_max == secs(30));
1474 assert!(options.connections_max_idle == minutes(9));
1475 assert!(options.client_dns_lookup == ClientDnsLookup::UseAllDnsIps);
1476 assert!(options.send_buffer == Some(kibibytes(128)));
1477 assert!(options.receive_buffer == Some(kibibytes(64)));
1478 }
1479
1480 #[test]
1481 fn connection_options_own_named_defaults() {
1482 let options = ConnectionOptions::default();
1483 assert!(DEFAULT_CLIENT_DNS_TIMEOUT == secs(10));
1484 assert!(DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT == secs(10));
1485 assert!(DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MAX == secs(30));
1486 assert!(DEFAULT_CLIENT_REQUEST_TIMEOUT == secs(30));
1487 assert!(options.dns_timeout == ClientDnsTimeout::default());
1488 assert!(options.dns_timeout.time() == DEFAULT_CLIENT_DNS_TIMEOUT);
1489 assert!(options.socket_connection_setup_timeout == DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT);
1490 assert!(options.request_timeout == DEFAULT_CLIENT_REQUEST_TIMEOUT);
1491 }
1492
1493 #[test]
1494 fn connection_resource_defaults_preserve_existing_values() {
1495 assert!(
1496 ConnectionDispatchQueueCapacity::default().get()
1497 == DEFAULT_CONNECTION_DISPATCH_QUEUE_CAPACITY
1498 );
1499 assert!(ConnectionDispatchQueueCapacity::default().get() == 64);
1500 assert!(ClientFrameMax::default().size() == mebibytes(100));
1501 assert!(MAX_CLIENT_FRAME_BYTES == mebibytes(100));
1502 }
1503
1504 #[test]
1507 fn validate_rejects_gssapi_only_without_the_kerberos_client() {
1508 use crate::security::{ClientSecurity, SaslCredentials};
1509
1510 let with_sasl = |sasl| ConnectionOptions {
1511 security: Some(Box::new(ClientSecurity {
1512 protocol: krabka_security::ListenerProtocol::SaslPlaintext,
1513 tls: None,
1514 sasl: Some(sasl),
1515 sasl_host: Some("broker-1.example.com".into()),
1516 })),
1517 ..Default::default()
1518 };
1519 let gssapi = with_sasl(SaslCredentials::Gssapi {
1520 keytab_path: "/etc/krb5.keytab".into(),
1521 client_principal: "[email protected]".into(),
1522 service_name: "kafka".into(),
1523 kdc_url: "kdc.example.com:88".into(),
1524 });
1525 let plain = with_sasl(SaslCredentials::Plain {
1526 username: "u".into(),
1527 password: "p".into(),
1528 });
1529 let want_gssapi = sasl::GSSAPI_CLIENT
1530 .then_some(())
1531 .ok_or_else(|| format!("SASL/GSSAPI: {}", sasl::GSSAPI_UNAVAILABLE));
1532 assert!((gssapi.validate(), plain.validate()) == (want_gssapi, Ok(())));
1533 }
1534
1535 #[test]
1536 fn defaults_equal_their_validated_constants() {
1537 assert!(
1538 ClientDnsTimeout::default()
1539 == ClientDnsTimeout::new(DEFAULT_CLIENT_DNS_TIMEOUT).unwrap()
1540 );
1541 assert!(
1542 ConnectionDispatchQueueCapacity::default()
1543 == ConnectionDispatchQueueCapacity::new(DEFAULT_CONNECTION_DISPATCH_QUEUE_CAPACITY)
1544 .unwrap()
1545 );
1546 assert!(
1547 ClientFrameMax::default()
1548 == ClientFrameMax::try_from(DEFAULT_CLIENT_FRAME_MAX).unwrap()
1549 );
1550 }
1551
1552 #[test]
1553 fn request_header_rejects_a_client_id_longer_than_i16() {
1554 let client_id = "x".repeat(usize::from(i16::MAX.unsigned_abs()) + 1);
1555 let result = build_request_header(ApiKey(18), ApiVersion(0), 1, &client_id, false);
1556 assert!(let Err(ClientError::InvalidConfig(_)) = result);
1557 }
1558
1559 #[test]
1560 fn transport_options_validate_duration_and_socket_boundaries() {
1561 let setters: [fn(&mut ConnectionOptions, Time); 6] = [
1562 |options, value| options.request_timeout = value,
1563 |options, value| options.socket_connection_setup_timeout = value,
1564 |options, value| options.socket_connection_setup_timeout_max = value,
1565 |options, value| options.reconnect_backoff = value,
1566 |options, value| options.reconnect_backoff_max = value,
1567 |options, value| options.connections_max_idle = value,
1568 ];
1569 for set in setters {
1570 for value in [
1571 Time::from_secs_f64(-1.0),
1572 Time::from_secs_f64(f64::NAN),
1573 Time::from_secs_f64(f64::INFINITY),
1574 Time::from_secs_f64(f64::MAX),
1575 ] {
1576 let mut options = ConnectionOptions::default();
1577 set(&mut options, value);
1578 assert!(options.validate().is_err());
1579 }
1580 }
1581 for value in [
1582 ByteSize::ZERO,
1583 ByteSize::from_bytes_f64(-1.0),
1584 ByteSize::from_bytes_f64(0.5),
1585 ByteSize::from_bytes_f64(f64::NAN),
1586 ByteSize::from_bytes_f64(f64::INFINITY),
1587 ByteSize::from_bytes_f64(f64::from(i32::MAX) + 1.0),
1588 ] {
1589 assert!(
1590 (ConnectionOptions {
1591 send_buffer: Some(value),
1592 ..Default::default()
1593 })
1594 .validate()
1595 .is_err()
1596 );
1597 assert!(
1598 (ConnectionOptions {
1599 receive_buffer: Some(value),
1600 ..Default::default()
1601 })
1602 .validate()
1603 .is_err()
1604 );
1605 }
1606 assert!(
1607 (ConnectionOptions {
1608 request_timeout: Time::ZERO,
1609 ..Default::default()
1610 })
1611 .validate()
1612 .is_err()
1613 );
1614 assert!(
1615 (ConnectionOptions {
1616 socket_connection_setup_timeout: Time::ZERO,
1617 ..Default::default()
1618 })
1619 .validate()
1620 .is_err()
1621 );
1622 assert!(
1623 (ConnectionOptions {
1624 socket_connection_setup_timeout: secs(31),
1625 ..Default::default()
1626 })
1627 .validate()
1628 .is_err()
1629 );
1630 assert!(
1631 (ConnectionOptions {
1632 reconnect_backoff: secs(2),
1633 ..Default::default()
1634 })
1635 .validate()
1636 .is_err()
1637 );
1638 assert!(
1639 (ConnectionOptions {
1640 reconnect_backoff: Time::ZERO,
1641 reconnect_backoff_max: Time::ZERO,
1642 connections_max_idle: Time::ZERO,
1643 send_buffer: None,
1644 receive_buffer: Some(bytes(1)),
1645 request_timeout: millis(1),
1646 socket_connection_setup_timeout: millis(1),
1647 socket_connection_setup_timeout_max: millis(1),
1648 ..Default::default()
1649 })
1650 .validate()
1651 .is_ok()
1652 );
1653 }
1654
1655 #[tokio::test]
1656 async fn invalid_options_fail_before_connect_or_stream_negotiation() {
1657 let options = ConnectionOptions {
1658 request_timeout: Time::from_secs_f64(f64::NAN),
1659 ..Default::default()
1660 };
1661 let result = Connection::connect("127.0.0.1:1".parse().unwrap(), options.clone()).await;
1662 assert!(matches!(result, Err(ClientError::InvalidConfig(_))));
1663 let result = Client::builder()
1664 .bootstrap("invalid-bootstrap")
1665 .request_timeout(Time::from_secs_f64(f64::NAN))
1666 .build()
1667 .await;
1668 assert!(matches!(result, Err(ClientError::InvalidConfig(_))));
1669 let (stream, _peer) = duplex(1024);
1670 let result = Connection::from_stream(Box::new(stream), options).await;
1671 assert!(matches!(result, Err(ClientError::InvalidConfig(_))));
1672 }
1673
1674 #[test]
1675 fn connection_resource_policy_validates_boundaries() {
1676 assert!(ConnectionDispatchQueueCapacity::new(0).is_err());
1677 assert!(ConnectionDispatchQueueCapacity::new(7).unwrap().get() == 7);
1678
1679 assert!(ClientFrameMax::try_from(bytes(0)).is_err());
1680 assert!(ClientFrameMax::try_from(ByteSize::from_bytes_f64(1.5)).is_err());
1681 assert!(ClientFrameMax::try_from(mebibytes(100) + bytes(1)).is_err());
1682 assert!(ClientFrameMax::try_from(kibibytes(32)).unwrap().size() == kibibytes(32));
1683 }
1684}
1685
1686#[cfg(test)]
1687mod secured_tests {
1688 use assert2::check;
1689 use krabka_protocol::Encode as _;
1690 use krabka_security::ListenerProtocol;
1691 use security::{ClientSecurity, SaslCredentials};
1692 use tokio::net::TcpListener;
1693
1694 use super::*;
1695
1696 #[tokio::test]
1700 async fn connect_secured_rejects_a_policy_without_its_material() {
1701 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1702 let addr = listener.local_addr().unwrap();
1703 let mut messages = Vec::new();
1704 for protocol in [ListenerProtocol::Ssl, ListenerProtocol::SaslPlaintext] {
1705 let security = ClientSecurity {
1706 protocol,
1707 tls: None,
1708 sasl: None,
1709 sasl_host: None,
1710 };
1711 let result =
1712 Connection::connect_secured(addr, ConnectionOptions::default(), &security).await;
1713 messages.push(result.err().map(|error| error.to_string()));
1714 }
1715 check!(
1716 messages
1717 == vec![
1718 Some("I/O: TLS protocol without tls config".to_owned()),
1719 Some("I/O: SASL protocol without credentials".to_owned()),
1720 ]
1721 );
1722 }
1723
1724 #[test]
1727 fn api_versions_response_with_trailing_bytes_is_invalid() {
1728 let mut body = BytesMut::new();
1729 api_versions_response::ApiVersionsResponse::default()
1730 .encode(&mut body, 0)
1731 .unwrap();
1732 body.put_u8(0xff);
1733
1734 let result = decode_api_versions_response(&body, 0).map_err(|error| error.to_string());
1735
1736 check!(result == Err("I/O: ApiVersions response v0 has 1 bytes after its end".to_owned()));
1737 }
1738
1739 #[tokio::test]
1743 async fn connect_secured_runs_sasl_then_api_versions() {
1744 use krabka_protocol::{
1745 Encode,
1746 owned::{
1747 api_versions_response::ApiVersionsResponse,
1748 sasl_authenticate_response::SaslAuthenticateResponse,
1749 sasl_handshake_response::SaslHandshakeResponse,
1750 },
1751 };
1752 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1753
1754 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1755 let addr = listener.local_addr().unwrap();
1756 let server = tokio::spawn(async move {
1757 let (mut s, _) = listener.accept().await.unwrap();
1758 let replies: [(BytesMut, bool); 4] = [
1760 {
1761 let mock::MockReply::Respond(body) = mock::MockSaslAnswer::Accept
1764 .reply(api_versions_request::API_KEY, 0)
1765 .unwrap()
1766 .unwrap()
1767 else {
1768 unreachable!("ApiVersions v0 has a reply")
1769 };
1770 (BytesMut::from(&body[..]), false)
1771 },
1772 {
1773 let mut b = BytesMut::new();
1774 SaslHandshakeResponse {
1775 error_code: 0,
1776 ..Default::default()
1777 }
1778 .encode(&mut b, 1)
1779 .unwrap();
1780 (b, false)
1781 },
1782 {
1783 let mut b = BytesMut::new();
1784 SaslAuthenticateResponse {
1785 error_code: 0,
1786 ..Default::default()
1787 }
1788 .encode(&mut b, 2)
1789 .unwrap();
1790 (b, true)
1791 },
1792 {
1793 let mut b = BytesMut::new();
1794 ApiVersionsResponse::default().encode(&mut b, 0).unwrap();
1795 (b, false)
1797 },
1798 ];
1799 for (body, flex_header) in replies {
1800 let req_len = s.read_u32().await.unwrap();
1801 let mut req = vec![0u8; req_len as usize];
1802 s.read_exact(&mut req).await.unwrap();
1803 let corr = i32::from_be_bytes([req[4], req[5], req[6], req[7]]);
1804 let mut frame = BytesMut::new();
1805 frame.put_i32(corr);
1806 if flex_header {
1807 frame.put_u8(0);
1808 }
1809 frame.put_slice(&body);
1810 s.write_u32(u32::try_from(frame.len()).unwrap())
1811 .await
1812 .unwrap();
1813 s.write_all(&frame).await.unwrap();
1814 s.flush().await.unwrap();
1815 }
1816 });
1817 let security = ClientSecurity {
1818 protocol: ListenerProtocol::SaslPlaintext,
1819 tls: None,
1820 sasl: Some(SaslCredentials::Plain {
1821 username: "u".into(),
1822 password: "p".into(),
1823 }),
1824 sasl_host: None,
1825 };
1826 let conn = Connection::connect_secured(addr, ConnectionOptions::default(), &security)
1827 .await
1828 .expect("secured connect completes");
1829 conn.close();
1830 server.await.unwrap();
1831 }
1832}
1833
1834#[cfg(test)]
1835mod tls_handshake_failure_tests {
1836 use krabka_security::ListenerProtocol;
1837 use tokio::{
1838 io::{AsyncReadExt, AsyncWriteExt},
1839 net::TcpListener,
1840 };
1841
1842 use super::*;
1843 use crate::{
1844 error::AuthenticationError,
1845 security::{ClientSecurity, TlsConnectorConfig},
1846 };
1847
1848 #[derive(Clone, Copy, Debug)]
1850 enum Peer {
1851 Plaintext,
1854 Close,
1856 }
1857
1858 #[derive(Debug, PartialEq, Eq)]
1860 enum Failure {
1861 Rejected(io::ErrorKind),
1862 Transport(io::ErrorKind),
1863 Other(String),
1864 }
1865
1866 async fn listener(peer: Peer) -> SocketAddr {
1867 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1868 let addr = listener.local_addr().unwrap();
1869 tokio::spawn(async move {
1870 let (mut stream, _) = listener.accept().await.unwrap();
1871 let mut buf = [0_u8; 1024];
1872 let _ = stream.read(&mut buf).await;
1873 if matches!(peer, Peer::Plaintext) {
1874 let _ = stream.write_all(b"HTTP/1.0 400 Bad Request\r\n\r\n").await;
1875 while matches!(stream.read(&mut buf).await, Ok(n) if n > 0) {}
1876 }
1877 });
1878 addr
1879 }
1880
1881 #[tokio::test]
1885 async fn tls_rejection_is_an_authentication_failure_and_eof_is_not() {
1886 let security = ClientSecurity {
1887 protocol: ListenerProtocol::Ssl,
1888 tls: Some(TlsConnectorConfig::default()),
1889 sasl: None,
1890 sasl_host: None,
1891 };
1892 for (peer, expected) in [
1893 (
1894 Peer::Plaintext,
1895 Failure::Rejected(io::ErrorKind::InvalidData),
1896 ),
1897 (
1898 Peer::Close,
1899 Failure::Transport(io::ErrorKind::UnexpectedEof),
1900 ),
1901 ] {
1902 let addr = listener(peer).await;
1903 let result =
1904 Connection::connect_secured(addr, ConnectionOptions::default(), &security).await;
1905 let failure = match result {
1906 Err(ClientError::Authentication {
1907 source: AuthenticationError::Tls(source),
1908 ..
1909 }) => Failure::Rejected(source.kind()),
1910 Err(ClientError::Tls { source, .. }) => Failure::Transport(source.kind()),
1911 Err(error) => Failure::Other(error.to_string()),
1912 Ok(_) => Failure::Other("connected".into()),
1913 };
1914 assert2::check!(failure == expected, "{peer:?}");
1915 }
1916 }
1917}
1918
1919#[cfg(test)]
1920mod io_task_tests {
1921 use std::time::{Duration, Instant};
1922
1923 use krabka_protocol::{
1924 Encode,
1925 owned::{
1926 api_versions_response::{ApiVersion, ApiVersionsResponse},
1927 metadata_request::MetadataRequest,
1928 metadata_response::MetadataResponse,
1929 },
1930 };
1931 use tokio::{
1932 io::{AsyncReadExt, AsyncWriteExt},
1933 net::TcpListener,
1934 };
1935
1936 use super::*;
1937
1938 #[tokio::test]
1945 async fn server_close_mid_request_yields_prompt_disconnected() {
1946 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1947 let addr = listener.local_addr().unwrap();
1948 let server = tokio::spawn(async move {
1949 let (mut s, _) = listener.accept().await.unwrap();
1950 let len = s.read_u32().await.unwrap();
1952 let mut req = vec![0u8; len as usize];
1953 s.read_exact(&mut req).await.unwrap();
1954 let corr = i32::from_be_bytes([req[4], req[5], req[6], req[7]]);
1955 let mut body = BytesMut::new();
1956 ApiVersionsResponse::default().encode(&mut body, 0).unwrap();
1957 let mut frame = BytesMut::new();
1958 frame.put_i32(corr);
1959 frame.put_slice(&body);
1960 s.write_u32(u32::try_from(frame.len()).unwrap())
1961 .await
1962 .unwrap();
1963 s.write_all(&frame).await.unwrap();
1964 s.flush().await.unwrap();
1965 let len2 = s.read_u32().await.unwrap();
1968 let mut req2 = vec![0u8; len2 as usize];
1969 s.read_exact(&mut req2).await.unwrap();
1970 });
1972
1973 let stream = TcpStream::connect(addr).await.unwrap();
1974 let opts = ConnectionOptions {
1975 request_timeout: secs(5),
1976 socket_connection_setup_timeout: secs(5),
1977 ..Default::default()
1978 };
1979 let conn = Connection::from_stream(Box::new(stream), opts)
1980 .await
1981 .expect("plaintext from_stream completes");
1982
1983 let started = Instant::now();
1984 let result = conn.raw_request(3, 0, Bytes::new()).await;
1986 assert!(
1987 matches!(result, Err(ClientError::Disconnected)),
1988 "server close mid-request must yield Disconnected, got {result:?}"
1989 );
1990 assert!(
1991 started.elapsed() < Duration::from_secs(4),
1992 "drain must be prompt (reader EOF), not a request-timeout stall"
1993 );
1994 server.await.unwrap();
1995 }
1996
1997 #[tokio::test]
1998 async fn no_response_request_does_not_capture_the_next_response() {
1999 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
2000 let addr = listener.local_addr().unwrap();
2001 let server = tokio::spawn(async move {
2002 let (mut socket, _) = listener.accept().await.unwrap();
2003
2004 let handshake_len = socket.read_u32().await.unwrap();
2005 let mut handshake = vec![0u8; handshake_len as usize];
2006 socket.read_exact(&mut handshake).await.unwrap();
2007 let handshake_corr =
2008 i32::from_be_bytes([handshake[4], handshake[5], handshake[6], handshake[7]]);
2009 let mut handshake_body = BytesMut::new();
2010 ApiVersionsResponse {
2011 api_keys: vec![ApiVersion {
2012 api_key: MetadataRequest::API_KEY,
2013 min_version: 0,
2014 max_version: 0,
2015 ..Default::default()
2016 }],
2017 ..Default::default()
2018 }
2019 .encode(&mut handshake_body, 0)
2020 .unwrap();
2021 let mut handshake_response = BytesMut::new();
2022 handshake_response.put_i32(handshake_corr);
2023 handshake_response.put_slice(&handshake_body);
2024 socket
2025 .write_u32(u32::try_from(handshake_response.len()).unwrap())
2026 .await
2027 .unwrap();
2028 socket.write_all(&handshake_response).await.unwrap();
2029 socket.flush().await.unwrap();
2030
2031 let one_way_len = socket.read_u32().await.unwrap();
2032 let mut one_way = vec![0u8; one_way_len as usize];
2033 socket.read_exact(&mut one_way).await.unwrap();
2034 let one_way_corr = i32::from_be_bytes([one_way[4], one_way[5], one_way[6], one_way[7]]);
2035
2036 let request_len = socket.read_u32().await.unwrap();
2037 let mut request = vec![0u8; request_len as usize];
2038 socket.read_exact(&mut request).await.unwrap();
2039 let request_corr = i32::from_be_bytes([request[4], request[5], request[6], request[7]]);
2040 assert2::assert!(request_corr == one_way_corr.wrapping_add(1));
2041
2042 let mut body = BytesMut::new();
2043 MetadataResponse::default().encode(&mut body, 0).unwrap();
2044 let mut response = BytesMut::new();
2045 response.put_i32(request_corr);
2046 response.put_slice(&body);
2047 socket
2048 .write_u32(u32::try_from(response.len()).unwrap())
2049 .await
2050 .unwrap();
2051 socket.write_all(&response).await.unwrap();
2052 socket.flush().await.unwrap();
2053 });
2054
2055 let stream = TcpStream::connect(addr).await.unwrap();
2056 let conn = Connection::from_stream(Box::new(stream), ConnectionOptions::default())
2057 .await
2058 .expect("plaintext from_stream completes");
2059 conn.send_no_response(MetadataRequest::default())
2060 .await
2061 .expect("one-way request enqueues");
2062 let response = conn
2063 .send(MetadataRequest::default())
2064 .await
2065 .expect("subsequent response remains correlated");
2066 assert2::assert!(response == MetadataResponse::default());
2067
2068 conn.close();
2069 server.await.unwrap();
2070 }
2071}
2072
2073#[cfg(test)]
2074mod connection_policy_tests {
2075 use std::{iter::repeat_with, time::Duration};
2076
2077 use assert2::check;
2078 use krabka_protocol::{
2079 Encode,
2080 owned::{
2081 api_versions_response::{ApiVersion, ApiVersionsResponse},
2082 metadata_request::MetadataRequest,
2083 },
2084 };
2085 use krabka_security::ListenerProtocol;
2086 use security::{ClientSecurity, SaslCredentials};
2087 use tokio::{
2088 io::{AsyncReadExt, AsyncWriteExt, DuplexStream, duplex},
2089 net::TcpListener,
2090 time::sleep,
2091 };
2092
2093 use super::*;
2094
2095 async fn answer_api_versions(mut server: DuplexStream) -> DuplexStream {
2098 let len = server.read_u32().await.unwrap();
2099 let mut request = vec![0_u8; len as usize];
2100 server.read_exact(&mut request).await.unwrap();
2101 let mut body = BytesMut::new();
2102 body.put_slice(&request[4..8]);
2103 ApiVersionsResponse {
2104 api_keys: vec![ApiVersion {
2105 api_key: MetadataRequest::API_KEY,
2106 min_version: 0,
2107 max_version: 0,
2108 ..Default::default()
2109 }],
2110 ..Default::default()
2111 }
2112 .encode(&mut body, 0)
2113 .unwrap();
2114 server
2115 .write_u32(u32::try_from(body.len()).unwrap())
2116 .await
2117 .unwrap();
2118 server.write_all(&body).await.unwrap();
2119 server
2120 }
2121
2122 async fn connection(options: ConnectionOptions) -> (Connection, DuplexStream) {
2123 let (client, server) = duplex(64 * 1024);
2124 let (connection, server) = tokio::join!(
2125 Connection::from_stream(Box::new(client), options),
2126 answer_api_versions(server)
2127 );
2128 (connection.unwrap(), server)
2129 }
2130
2131 #[tokio::test(start_paused = true)]
2135 async fn an_idle_connection_closes_after_connections_max_idle() {
2136 let max_idle = Duration::from_mins(9);
2137 let ms = Duration::from_millis;
2138 for (name, idle, waiting, closed) in [
2139 (
2140 "just below the limit",
2141 max_idle.saturating_sub(ms(1)),
2142 false,
2143 false,
2144 ),
2145 ("just past the limit", max_idle + ms(1), false, true),
2146 (
2147 "a request waits for its response",
2148 max_idle * 2,
2149 true,
2150 false,
2151 ),
2152 ] {
2153 let (connection, _server) = connection(ConnectionOptions {
2154 request_timeout: secs(3600),
2155 ..ConnectionOptions::default()
2156 })
2157 .await;
2158 let waiter = waiting.then(|| {
2159 let connection = connection.clone();
2160 tokio::spawn(async move { connection.send(MetadataRequest::default()).await })
2161 });
2162 sleep(idle).await;
2163 check!(connection.is_closed() == closed, "{name}");
2164 if let Some(waiter) = waiter {
2165 waiter.abort();
2166 }
2167 }
2168 }
2169
2170 #[tokio::test(start_paused = true)]
2171 async fn zero_idle_budget_disables_eviction_instead_of_closing_immediately() {
2172 let (connection, _server) = connection(ConnectionOptions {
2173 connections_max_idle: Time::ZERO,
2174 ..ConnectionOptions::default()
2175 })
2176 .await;
2177 sleep(Duration::from_hours(24)).await;
2178 check!(!connection.is_closed());
2179 connection.close();
2180 }
2181
2182 #[tokio::test(start_paused = true)]
2185 async fn a_request_timeout_closes_the_connection() {
2186 let (connection, _server) = connection(ConnectionOptions {
2187 request_timeout: secs(30),
2188 ..ConnectionOptions::default()
2189 })
2190 .await;
2191 let result = connection.send(MetadataRequest::default()).await;
2192 check!(matches!(result, Err(ClientError::Timeout(_))));
2193 check!(connection.is_closed());
2194 check!(connection.in_flight() == 0);
2195 }
2196
2197 #[tokio::test]
2200 async fn the_setup_timeout_covers_a_sasl_handshake_that_never_answers() {
2201 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
2202 let addr = listener.local_addr().unwrap();
2203 let server = tokio::spawn(async move {
2204 let (mut stream, _) = listener.accept().await.unwrap();
2205 let mut buffer = [0_u8; 1024];
2206 while matches!(stream.read(&mut buffer).await, Ok(read) if read > 0) {}
2207 });
2208 let security = ClientSecurity {
2209 protocol: ListenerProtocol::SaslPlaintext,
2210 tls: None,
2211 sasl: Some(SaslCredentials::Plain {
2212 username: "u".into(),
2213 password: "p".into(),
2214 }),
2215 sasl_host: None,
2216 };
2217 let options = ConnectionOptions {
2218 socket_connection_setup_timeout: millis(100),
2219 request_timeout: secs(30),
2220 ..ConnectionOptions::default()
2221 };
2222 let started = time::Instant::now();
2223 let result = Connection::connect_secured(addr, options, &security).await;
2224 check!(matches!(result, Err(ClientError::Timeout(timeout)) if timeout == millis(100)));
2225 check!(started.elapsed() < Duration::from_secs(5));
2226 server.abort();
2227 }
2228
2229 #[tokio::test(start_paused = true)]
2232 async fn a_write_blocked_by_a_peer_that_does_not_read_ends_at_shutdown() {
2233 let (client, server) = duplex(64 * 1024);
2234 let (connection, _server) = tokio::join!(
2235 Connection::from_stream(
2236 Box::new(client),
2237 ConnectionOptions {
2238 request_timeout: secs(30),
2239 ..ConnectionOptions::default()
2240 },
2241 ),
2242 answer_api_versions(server)
2243 );
2244 let connection = connection.unwrap();
2245 let result = connection
2248 .raw_request(3, 0, Bytes::from(vec![0_u8; 256 * 1024]))
2249 .await;
2250 check!(matches!(result, Err(ClientError::Timeout(_))));
2251 sleep(Duration::from_millis(1)).await;
2252 check!(connection.inner.writer_tx.is_closed());
2254 }
2255
2256 #[derive(Clone, Copy, Debug)]
2258 enum Broker {
2259 ListsUpTo(i16),
2262 OnlyVersionZero,
2265 }
2266
2267 #[tokio::test]
2272 async fn api_versions_negotiation_follows_the_broker_and_keeps_the_features() {
2273 use krabka_protocol::{
2274 Decode as _,
2275 owned::{
2276 api_versions_request::ApiVersionsRequest,
2277 api_versions_response::{FinalizedFeatureKey, SupportedFeatureKey},
2278 },
2279 };
2280
2281 let features = |version: i16| {
2282 if version >= 3 {
2283 crate::FinalizedFeatures {
2284 epoch: 7,
2285 levels: maplit::btreemap! {"transaction.version".to_owned() => 2},
2286 }
2287 } else {
2288 crate::FinalizedFeatures::default()
2289 }
2290 };
2291 let software = |version: i16| {
2292 if version >= 3 {
2293 (
2294 CLIENT_SOFTWARE_NAME.to_owned(),
2295 CLIENT_SOFTWARE_VERSION.to_owned(),
2296 )
2297 } else {
2298 (String::new(), String::new())
2299 }
2300 };
2301 for (name, broker, expected_versions, expected_features) in [
2302 (
2303 "a broker at version 5",
2304 Broker::ListsUpTo(5),
2305 vec![5],
2306 features(5),
2307 ),
2308 (
2309 "a broker at version 3",
2310 Broker::ListsUpTo(3),
2311 vec![5, 3],
2312 features(3),
2313 ),
2314 (
2315 "a broker at version 2",
2316 Broker::ListsUpTo(2),
2317 vec![5, 2],
2318 features(2),
2319 ),
2320 (
2321 "an old broker",
2322 Broker::OnlyVersionZero,
2323 vec![5, 0],
2324 features(0),
2325 ),
2326 ] {
2327 let (client, mut server) = duplex(64 * 1024);
2328 let script = tokio::spawn(async move {
2329 let mut seen = Vec::new();
2330 loop {
2331 let Ok(len) = server.read_u32().await else {
2332 return seen;
2333 };
2334 let mut request = vec![0_u8; len as usize];
2335 server.read_exact(&mut request).await.unwrap();
2336 let version = i16::from_be_bytes([request[2], request[3]]);
2337 let client_id_len = usize::from(u16::from_be_bytes([request[8], request[9]]));
2338 let mut body = &request[10 + client_id_len + usize::from(version >= 3)..];
2339 let decoded = ApiVersionsRequest::decode(&mut body, version).unwrap();
2340 seen.push((
2341 version,
2342 (
2343 decoded.client_software_name,
2344 decoded.client_software_version,
2345 ),
2346 ));
2347 let max = match broker {
2348 Broker::ListsUpTo(max) => max,
2349 Broker::OnlyVersionZero => 0,
2350 };
2351 let (response, encoded_at) = if version > max {
2352 let api_keys = match broker {
2353 Broker::ListsUpTo(max) => vec![ApiVersion {
2354 api_key: ApiVersionsRequest::API_KEY,
2355 min_version: 0,
2356 max_version: max,
2357 ..Default::default()
2358 }],
2359 Broker::OnlyVersionZero => Vec::new(),
2360 };
2361 (
2362 ApiVersionsResponse {
2363 error_code: UNSUPPORTED_VERSION,
2364 api_keys,
2365 ..Default::default()
2366 },
2367 0,
2368 )
2369 } else {
2370 (
2371 ApiVersionsResponse {
2372 api_keys: vec![ApiVersion {
2373 api_key: MetadataRequest::API_KEY,
2374 min_version: 0,
2375 max_version: 12,
2376 ..Default::default()
2377 }],
2378 supported_features: vec![SupportedFeatureKey {
2379 name: "transaction.version".into(),
2380 min_version: 0,
2381 max_version: 2,
2382 ..Default::default()
2383 }],
2384 finalized_features_epoch: 7,
2385 finalized_features: vec![FinalizedFeatureKey {
2386 name: "transaction.version".into(),
2387 max_version_level: 2,
2388 min_version_level: 2,
2389 ..Default::default()
2390 }],
2391 ..Default::default()
2392 },
2393 version,
2394 )
2395 };
2396 let mut frame = BytesMut::new();
2397 frame.put_slice(&request[4..8]);
2398 response.encode(&mut frame, encoded_at).unwrap();
2399 server
2400 .write_u32(u32::try_from(frame.len()).unwrap())
2401 .await
2402 .unwrap();
2403 server.write_all(&frame).await.unwrap();
2404 }
2405 });
2406 let connection =
2407 Connection::from_stream(Box::new(client), ConnectionOptions::default())
2408 .await
2409 .unwrap();
2410 let observed_features = connection.versions().finalized_features().clone();
2411 let metadata = connection.advertised_api_range(MetadataRequest::API_KEY);
2412 connection.close();
2413 let seen = script.await.unwrap();
2414 check!(
2415 seen == expected_versions
2416 .iter()
2417 .map(|version| (*version, software(*version)))
2418 .collect::<Vec<_>>(),
2419 "{name}"
2420 );
2421 check!(observed_features == expected_features, "{name}");
2422 check!(metadata == Some((0, 12)), "{name}");
2423 }
2424 }
2425
2426 #[tokio::test]
2429 async fn normal_correlation_ids_wrap_before_the_sasl_range() {
2430 let (connection, _server) = connection(ConnectionOptions::default()).await;
2431 let reserved = sasl::MIN_RESERVED_CORRELATION_ID;
2432 connection
2433 .inner
2434 .next_corr_id
2435 .store(reserved - 2, Ordering::Relaxed);
2436 let ids = repeat_with(|| connection.next_correlation_id())
2437 .take(3)
2438 .collect::<Vec<_>>();
2439 check!(ids == vec![reserved - 2, reserved - 1, 0]);
2440 }
2441
2442 #[test]
2443 fn flexible_response_headers_skip_every_tagged_field() {
2444 for (name, header_and_body, expected) in [
2445 ("empty body", vec![], Ok(vec![])),
2446 ("no tagged field", vec![0, 7, 8], Ok(vec![7, 8])),
2447 (
2448 "two tagged fields",
2449 vec![2, 1, 2, 0xAA, 0xBB, 5, 0, 7],
2450 Ok(vec![7]),
2451 ),
2452 ("truncated field", vec![1, 1, 4, 0xAA], Err(())),
2453 ] {
2454 check!(
2455 skip_tagged_fields(&header_and_body)
2456 .map(<[u8]>::to_vec)
2457 .map_err(drop)
2458 == expected,
2459 "{name}"
2460 );
2461 }
2462 }
2463
2464 #[tokio::test(start_paused = true)]
2467 async fn a_dropped_request_leaves_no_pending_entry() {
2468 let (connection, _server) = connection(ConnectionOptions::default()).await;
2469 let sending = connection.clone();
2470 let task = tokio::spawn(async move { sending.send(MetadataRequest::default()).await });
2471 sleep(Duration::from_millis(1)).await;
2472 check!(connection.in_flight() == 1);
2473 task.abort();
2474 let _ = task.await;
2475 check!(connection.in_flight() == 0);
2476 }
2477}