1use std::net::{IpAddr, SocketAddr};
52use std::sync::atomic::{AtomicU64, Ordering};
53use std::sync::{Arc, OnceLock};
54use std::time::Duration;
55
56use serde::{Deserialize, Serialize};
57use thornode_pulse_wire::frame::{decode_datagram, decode_frame};
58use tokio::io::AsyncReadExt;
59
60pub use thornode_pulse_wire::derive::{
61 compute_unit_limit, compute_unit_price, fee_payer, program_ids, static_writable_accounts,
62};
63pub use thornode_pulse_wire::frame::{Datagram, Frame, FullTx, FullTxV2};
64pub use thornode_pulse_wire::protocol::RetryClass;
65
66pub const NO_SEQ_ASSIGNED: u64 = u64::MAX;
72
73#[derive(Debug, Clone, Default, Serialize)]
79pub struct Filter {
80 #[serde(skip_serializing_if = "Vec::is_empty")]
81 pub account_include: Vec<String>,
82 #[serde(skip_serializing_if = "Vec::is_empty")]
83 pub account_exclude: Vec<String>,
84 #[serde(skip_serializing_if = "Vec::is_empty")]
85 pub account_required: Vec<String>,
86 #[serde(skip_serializing_if = "Option::is_none")]
91 pub vote: Option<bool>,
92}
93
94impl Filter {
95 pub fn all() -> Self {
99 Filter::default()
100 }
101
102 pub fn accounts<I, S>(accounts: I) -> Self
105 where
106 I: IntoIterator<Item = S>,
107 S: Into<String>,
108 {
109 Filter {
110 account_include: accounts.into_iter().map(Into::into).collect(),
111 ..Default::default()
112 }
113 }
114
115 pub fn with_vote(mut self, include: bool) -> Self {
119 self.vote = Some(include);
120 self
121 }
122}
123
124#[derive(Serialize)]
131struct Control<'a> {
132 #[serde(flatten)]
133 filter: &'a Filter,
134 #[serde(skip_serializing_if = "str::is_empty")]
135 token: &'a str,
136 full: bool,
137 v: u32,
138 fields: &'a [&'a str],
139}
140
141#[derive(Debug, Clone, Deserialize)]
144pub struct Ack {
145 #[serde(rename = "type", default)]
150 pub message_type: Option<String>,
151 #[serde(default)]
155 pub ok: bool,
156 #[serde(default)]
158 pub reason: Option<String>,
159 #[serde(default)]
162 pub code: Option<u64>,
163 #[serde(default)]
166 pub v: Option<u32>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub enum Error {
172 InvalidEndpoint(String),
173 Connect(String),
174 ConnectTimeout,
175 Io(String),
176 Tls(String),
177 InsecureEndpointNotLoopback(SocketAddr),
180 ApplicationClosed(CloseInfo),
182 BadFrame,
184 BadFrameWithClose(CloseInfo),
189 BadPreamble,
194 BadPreambleWithClose(CloseInfo),
198 Rejected(String),
201 AckTimeout,
204 FullStreamTimeout,
207 VersionMismatch(u32),
210 MissingVersion,
213}
214
215impl std::fmt::Display for Error {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 Error::InvalidEndpoint(e) => write!(f, "invalid endpoint: {e}"),
219 Error::Connect(e) => write!(f, "connect: {e}"),
220 Error::ConnectTimeout => write!(
221 f,
222 "timed out after {}s connecting to the server",
223 CONNECT_TIMEOUT.as_secs()
224 ),
225 Error::Io(e) => write!(f, "io: {e}"),
226 Error::Tls(e) => write!(f, "tls: {e}"),
227 Error::InsecureEndpointNotLoopback(addr) => write!(
228 f,
229 "insecure local-dev TLS is restricted to loopback addresses, got {addr}"
230 ),
231 Error::ApplicationClosed(close) => write!(
232 f,
233 "server closed the connection (code {}): {}",
234 close.code, close.reason
235 ),
236 Error::BadFrame => write!(f, "malformed frame"),
237 Error::BadFrameWithClose(close) => write!(
238 f,
239 "truncated frame before server close (code {}): {}",
240 close.code, close.reason
241 ),
242 Error::BadPreamble => write!(
243 f,
244 "bad stream preamble: this server is not speaking pulse wire v2"
245 ),
246 Error::BadPreambleWithClose(close) => write!(
247 f,
248 "bad stream preamble before server close (code {}): {}",
249 close.code, close.reason
250 ),
251 Error::Rejected(reason) => write!(f, "control message rejected: {reason}"),
252 Error::AckTimeout => write!(
253 f,
254 "timed out after {}s waiting for the server's control ack",
255 ACK_TIMEOUT.as_secs()
256 ),
257 Error::FullStreamTimeout => write!(
258 f,
259 "timed out after {}s waiting for the full-tx stream and preamble",
260 FULL_STREAM_TIMEOUT.as_secs()
261 ),
262 Error::VersionMismatch(v) => write!(
263 f,
264 "server negotiated wire v{v}, this SDK speaks only wire v{}",
265 thornode_pulse_wire::frame::WIRE_VERSION
266 ),
267 Error::MissingVersion => write!(
268 f,
269 "successful initial control ack omitted the negotiated wire version"
270 ),
271 }
272 }
273}
274impl std::error::Error for Error {}
275
276pub type Result<T> = std::result::Result<T, Error>;
277
278#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct CloseInfo {
281 pub code: u64,
282 pub reason: String,
283}
284
285impl CloseInfo {
286 pub fn retry_class(&self) -> RetryClass {
289 thornode_pulse_wire::protocol::classify_close_code(self.code)
290 }
291
292 pub fn retryable(&self) -> bool {
295 self.retry_class() == RetryClass::Transient
296 }
297}
298
299impl Error {
300 pub fn close_info(&self) -> Option<&CloseInfo> {
303 match self {
304 Error::ApplicationClosed(close)
305 | Error::BadFrameWithClose(close)
306 | Error::BadPreambleWithClose(close) => Some(close),
307 _ => None,
308 }
309 }
310
311 pub fn is_bad_frame(&self) -> bool {
313 matches!(self, Error::BadFrame | Error::BadFrameWithClose(_))
314 }
315
316 pub fn is_bad_preamble(&self) -> bool {
318 matches!(self, Error::BadPreamble | Error::BadPreambleWithClose(_))
319 }
320}
321
322pub const DEFAULT_RECV_BUFFER: usize = 8 << 20; pub const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
329
330pub const FULL_STREAM_TIMEOUT: Duration = Duration::from_secs(10);
332
333fn client_socket(addr: SocketAddr, recv_buffer: usize) -> std::io::Result<std::net::UdpSocket> {
338 let sock = socket2::Socket::new(
339 socket2::Domain::for_address(addr),
340 socket2::Type::DGRAM,
341 Some(socket2::Protocol::UDP),
342 )?;
343 let _ = sock.set_recv_buffer_size(recv_buffer);
346 sock.bind(&addr.into())?;
347 sock.set_nonblocking(true)?;
348 Ok(sock.into())
349}
350
351pub struct PulseClient {
353 conn: quinn::Connection,
354 _endpoint: quinn::Endpoint,
355 token: Option<String>,
356}
357
358impl PulseClient {
359 pub fn builder(endpoint: impl Into<String>) -> PulseClientBuilder {
361 PulseClientBuilder::new(endpoint)
362 }
363
364 pub async fn connect(endpoint: impl Into<String>) -> Result<Self> {
367 Self::builder(endpoint).connect().await
368 }
369
370 pub async fn connect_with_token(
373 endpoint: impl Into<String>,
374 token: impl Into<String>,
375 ) -> Result<Self> {
376 Self::builder(endpoint).with_token(token).connect().await
377 }
378
379 pub async fn dangerous_connect_insecure_local_dev(addr: SocketAddr) -> Result<Self> {
383 connect_to(
384 addr,
385 "localhost".to_owned(),
386 None,
387 Trust::InsecureLocalDev,
388 CONNECT_TIMEOUT,
389 )
390 .await
391 }
392
393 pub async fn dangerous_connect_insecure_local_dev_with_token(
397 addr: SocketAddr,
398 token: impl Into<String>,
399 ) -> Result<Self> {
400 connect_to(
401 addr,
402 "localhost".to_owned(),
403 Some(token.into()),
404 Trust::InsecureLocalDev,
405 CONNECT_TIMEOUT,
406 )
407 .await
408 }
409
410 pub async fn subscribe_sig_first(self, filter: &Filter) -> Result<SigFirstSub> {
414 let ack = self.send_control(filter, false, &[]).await?;
415 ensure_initial_ack(&ack)?;
416 Ok(SigFirstSub::spawn(self.conn))
417 }
418
419 pub async fn subscribe_full(self, filter: &Filter, fields: &[&str]) -> Result<FullSub> {
428 let ack = self.send_control(filter, true, fields).await?;
429 ensure_initial_ack(&ack)?;
430 let setup = async {
434 let mut recv = self
435 .conn
436 .accept_uni()
437 .await
438 .map_err(|e| Error::Io(e.to_string()))?;
439 verify_preamble(&mut recv).await?;
440 Ok(recv)
441 };
442 let recv = match tokio::time::timeout(FULL_STREAM_TIMEOUT, setup).await {
443 Ok(result) => {
444 result.map_err(|e| merge_wire_and_terminal(e, terminal_error(&self.conn)))?
445 }
446 Err(_) => return Err(terminal_error(&self.conn).unwrap_or(Error::FullStreamTimeout)),
447 };
448 Ok(FullSub {
449 conn: self.conn,
450 recv,
451 buf: Vec::with_capacity(4096),
452 last_heartbeat: None,
453 })
454 }
455
456 async fn send_control(&self, filter: &Filter, full: bool, fields: &[&str]) -> Result<Ack> {
457 let token = self.token.as_deref().unwrap_or("");
458 control_round_trip(&self.conn, filter, full, fields, token).await
459 }
460}
461
462pub struct PulseClientBuilder {
466 endpoint: String,
467 token: Option<String>,
468 custom_ca_der: Vec<Vec<u8>>,
469}
470
471impl PulseClientBuilder {
472 pub fn new(endpoint: impl Into<String>) -> Self {
473 Self {
474 endpoint: endpoint.into(),
475 token: None,
476 custom_ca_der: Vec::new(),
477 }
478 }
479
480 pub fn with_token(mut self, token: impl Into<String>) -> Self {
481 self.token = Some(token.into());
482 self
483 }
484
485 pub fn add_custom_ca_der(mut self, certificate: impl Into<Vec<u8>>) -> Self {
488 self.custom_ca_der.push(certificate.into());
489 self
490 }
491
492 pub async fn connect(self) -> Result<PulseClient> {
493 let started = tokio::time::Instant::now();
494 let host = endpoint_host(&self.endpoint)?;
495 let resolved = tokio::time::timeout(CONNECT_TIMEOUT, async {
496 let mut addresses: Vec<_> = tokio::net::lookup_host(self.endpoint.as_str())
497 .await
498 .map_err(|e| Error::Connect(format!("resolve {}: {e}", self.endpoint)))?
499 .collect();
500 addresses.sort_by_key(|address| !address.is_ipv4());
505 addresses.into_iter().next().ok_or_else(|| {
506 Error::Connect(format!("{} resolved to no addresses", self.endpoint))
507 })
508 })
509 .await
510 .map_err(|_| Error::ConnectTimeout)??;
511
512 let remaining = CONNECT_TIMEOUT
513 .checked_sub(started.elapsed())
514 .ok_or(Error::ConnectTimeout)?;
515
516 connect_to(
517 resolved,
518 host,
519 self.token,
520 Trust::Verified(self.custom_ca_der),
521 remaining,
522 )
523 .await
524 }
525}
526
527enum Trust {
528 Verified(Vec<Vec<u8>>),
529 InsecureLocalDev,
530}
531
532fn endpoint_host(endpoint: &str) -> Result<String> {
533 let endpoint = endpoint.trim();
534 if endpoint.is_empty() || endpoint.contains("://") || endpoint.contains('/') {
535 return Err(Error::InvalidEndpoint(
536 "expected host:port without a URL scheme or path".to_owned(),
537 ));
538 }
539 if let Ok(addr) = endpoint.parse::<SocketAddr>() {
540 return Ok(addr.ip().to_string());
541 }
542 let (host, port) = endpoint
543 .rsplit_once(':')
544 .ok_or_else(|| Error::InvalidEndpoint(format!("{endpoint:?} must include a port")))?;
545 if host.is_empty() || host.contains(':') || host.chars().any(char::is_whitespace) {
546 return Err(Error::InvalidEndpoint(format!(
547 "{endpoint:?} has an invalid host"
548 )));
549 }
550 port.parse::<u16>()
551 .map_err(|_| Error::InvalidEndpoint(format!("{endpoint:?} has an invalid port")))?;
552 Ok(host.trim_end_matches('.').to_owned())
553}
554
555async fn connect_to(
556 addr: SocketAddr,
557 server_name: String,
558 token: Option<String>,
559 trust: Trust,
560 timeout: Duration,
561) -> Result<PulseClient> {
562 if matches!(trust, Trust::InsecureLocalDev) && !addr.ip().is_loopback() {
563 return Err(Error::InsecureEndpointNotLoopback(addr));
564 }
565 let _ = rustls::crypto::ring::default_provider().install_default();
566
567 let mut tls = match trust {
568 Trust::Verified(custom_ca_der) => {
569 let mut roots = rustls::RootCertStore::empty();
570 let native = rustls_native_certs::load_native_certs();
571 for cert in native.certs {
572 roots
573 .add(cert)
574 .map_err(|e| Error::Tls(format!("invalid native trust anchor: {e}")))?;
575 }
576 for cert in custom_ca_der {
577 roots
578 .add(rustls::pki_types::CertificateDer::from(cert))
579 .map_err(|e| Error::Tls(format!("invalid custom CA certificate: {e}")))?;
580 }
581 if roots.is_empty() {
582 return Err(Error::Tls(
583 "native certificate store contained no usable roots".to_owned(),
584 ));
585 }
586 rustls::ClientConfig::builder()
587 .with_root_certificates(roots)
588 .with_no_client_auth()
589 }
590 Trust::InsecureLocalDev => rustls::ClientConfig::builder()
591 .dangerous()
592 .with_custom_certificate_verifier(Arc::new(NoVerify))
593 .with_no_client_auth(),
594 };
595 tls.alpn_protocols = vec![thornode_pulse_wire::protocol::ALPN.to_vec()];
596
597 let qcc = quinn::crypto::rustls::QuicClientConfig::try_from(tls)
598 .map_err(|e| Error::Tls(e.to_string()))?;
599 let client_cfg = quinn::ClientConfig::new(Arc::new(qcc));
600
601 let bind_addr = SocketAddr::new(
604 if addr.is_ipv4() {
605 IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)
606 } else {
607 IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED)
608 },
609 0,
610 );
611 let socket =
612 client_socket(bind_addr, DEFAULT_RECV_BUFFER).map_err(|e| Error::Io(e.to_string()))?;
613 let mut endpoint = quinn::Endpoint::new(
614 quinn::EndpointConfig::default(),
615 None,
616 socket,
617 Arc::new(quinn::TokioRuntime),
618 )
619 .map_err(|e| Error::Io(e.to_string()))?;
620 endpoint.set_default_client_config(client_cfg);
621
622 let connecting = endpoint
623 .connect(addr, &server_name)
624 .map_err(|e| Error::Connect(e.to_string()))?;
625 let conn = tokio::time::timeout(timeout, connecting)
626 .await
627 .map_err(|_| Error::ConnectTimeout)?
628 .map_err(|error| {
629 close_info(&error)
630 .map(Error::ApplicationClosed)
631 .unwrap_or_else(|| Error::Connect(error.to_string()))
632 })?;
633
634 Ok(PulseClient {
635 conn,
636 _endpoint: endpoint,
637 token,
638 })
639}
640
641fn ensure_envelope(ack: &Ack) -> Result<()> {
650 let reason = ack.reason.clone().unwrap_or_default();
651 match ack.message_type.as_deref() {
652 Some("ack") => {
653 if ack.ok {
654 Ok(())
655 } else {
656 Err(Error::Rejected(reason))
657 }
658 }
659 Some("error") if !ack.ok => match ack.code {
660 Some(code) => Err(Error::ApplicationClosed(CloseInfo { code, reason })),
661 None => Err(Error::BadFrame),
662 },
663 _ => Err(Error::BadFrame),
666 }
667}
668
669fn ensure_initial_ack(ack: &Ack) -> Result<()> {
670 ensure_envelope(ack)?;
671 match ack.v {
672 Some(v) if v == thornode_pulse_wire::frame::WIRE_VERSION as u32 => Ok(()),
673 Some(v) => Err(Error::VersionMismatch(v)),
674 None => Err(Error::MissingVersion),
675 }
676}
677
678fn ensure_update_ack(ack: &Ack) -> Result<()> {
679 ensure_envelope(ack)?;
680 match ack.v {
683 Some(v) if v != thornode_pulse_wire::frame::WIRE_VERSION as u32 => {
684 Err(Error::VersionMismatch(v))
685 }
686 _ => Ok(()),
687 }
688}
689
690async fn control_round_trip(
695 conn: &quinn::Connection,
696 filter: &Filter,
697 full: bool,
698 fields: &[&str],
699 token: &str,
700) -> Result<Ack> {
701 let result = tokio::time::timeout(
702 ACK_TIMEOUT,
703 control_round_trip_inner(conn, filter, full, fields, token),
704 )
705 .await;
706 match result {
707 Ok(result) => result.map_err(|e| terminal_error(conn).unwrap_or(e)),
708 Err(_) => Err(terminal_error(conn).unwrap_or(Error::AckTimeout)),
709 }
710}
711
712async fn control_round_trip_inner(
713 conn: &quinn::Connection,
714 filter: &Filter,
715 full: bool,
716 fields: &[&str],
717 token: &str,
718) -> Result<Ack> {
719 let body = serde_json::to_vec(&Control {
720 filter,
721 token,
722 full,
723 v: thornode_pulse_wire::frame::WIRE_VERSION as u32,
724 fields,
725 })
726 .map_err(|_| Error::BadFrame)?;
727 let (mut send, mut recv) = conn.open_bi().await.map_err(|e| Error::Io(e.to_string()))?;
728 send.write_all(&body)
729 .await
730 .map_err(|e| Error::Io(e.to_string()))?;
731 let _ = send.finish();
732 read_ack(&mut recv).await
733}
734
735const MAX_ACK_BYTES: usize = 16 * 1024;
739
740pub const ACK_TIMEOUT: Duration = Duration::from_secs(10);
745
746async fn read_ack(recv: &mut quinn::RecvStream) -> Result<Ack> {
747 let mut len = [0u8; 4];
748 recv.read_exact(&mut len)
749 .await
750 .map_err(|e| Error::Io(e.to_string()))?;
751 let n = u32::from_be_bytes(len) as usize;
752 if n > MAX_ACK_BYTES {
753 return Err(Error::BadFrame);
754 }
755 let mut body = vec![0u8; n];
756 recv.read_exact(&mut body)
757 .await
758 .map_err(|e| Error::Io(e.to_string()))?;
759 serde_json::from_slice(&body).map_err(|_| Error::BadFrame)
760}
761
762fn close_info(error: &quinn::ConnectionError) -> Option<CloseInfo> {
763 match error {
764 quinn::ConnectionError::ApplicationClosed(close) => Some(CloseInfo {
765 code: close.error_code.into_inner(),
766 reason: String::from_utf8_lossy(&close.reason).into_owned(),
767 }),
768 _ => None,
769 }
770}
771
772fn terminal_error(conn: &quinn::Connection) -> Option<Error> {
773 conn.close_reason().and_then(|error| {
774 close_info(&error)
775 .map(Error::ApplicationClosed)
776 .or_else(|| match error {
777 quinn::ConnectionError::LocallyClosed => None,
778 other => Some(Error::Io(other.to_string())),
779 })
780 })
781}
782
783async fn verify_preamble<R: tokio::io::AsyncRead + Unpin>(recv: &mut R) -> Result<()> {
788 let mut buf = [0u8; 6];
789 debug_assert_eq!(thornode_pulse_wire::frame::PREAMBLE.len(), buf.len());
790 recv.read_exact(&mut buf)
791 .await
792 .map_err(|_| Error::BadPreamble)?;
793 if &buf != thornode_pulse_wire::frame::PREAMBLE {
794 return Err(Error::BadPreamble);
795 }
796 Ok(())
797}
798
799#[derive(Debug, Clone, Copy, PartialEq, Eq)]
803pub struct SigFirstItem {
804 pub slot: u64,
805 pub seq: u64,
806 pub signature: [u8; 64],
807}
808
809fn note_item_seq(last_seq: &mut Option<u64>, gaps: &AtomicU64, seq: u64) {
821 if let Some(last) = *last_seq {
822 gaps.fetch_add(
830 seq.saturating_sub(last.saturating_add(1)),
831 Ordering::Relaxed,
832 );
833 *last_seq = Some(last.max(seq));
834 } else {
835 *last_seq = Some(seq);
836 }
837}
838
839fn note_heartbeat_seq(last_seq: &mut Option<u64>, gaps: &AtomicU64, highest_seq: u64) {
849 if highest_seq == NO_SEQ_ASSIGNED {
850 return;
851 }
852 match *last_seq {
853 Some(last) if highest_seq > last => {
854 gaps.fetch_add(highest_seq - last, Ordering::Relaxed);
855 *last_seq = Some(highest_seq);
856 }
857 Some(_) => {}
860 None => *last_seq = Some(highest_seq),
863 }
864}
865
866fn apply_datagram(dg: &[u8], last_seq: &mut Option<u64>, gaps: &AtomicU64) -> Option<SigFirstItem> {
874 match decode_datagram(dg) {
875 Some(Datagram::SigFirst {
876 slot,
877 seq,
878 signature,
879 }) => {
880 note_item_seq(last_seq, gaps, seq);
881 Some(SigFirstItem {
882 slot,
883 seq,
884 signature,
885 })
886 }
887 Some(Datagram::Heartbeat { highest_seq, .. }) => {
888 note_heartbeat_seq(last_seq, gaps, highest_seq);
889 None
890 }
891 Some(Datagram::Unknown(_)) | None => None,
892 }
893}
894
895pub const SIG_QUEUE_LEN: usize = 4096;
902
903pub struct SigFirstSub {
914 conn: Option<quinn::Connection>,
915 rx: tokio::sync::broadcast::Receiver<SigFirstItem>,
916 dropped: Arc<AtomicU64>,
917 gaps: Arc<AtomicU64>,
918 fatal: Arc<OnceLock<Error>>,
920 drain: Option<tokio::task::JoinHandle<()>>,
921}
922
923impl SigFirstSub {
924 fn spawn(conn: quinn::Connection) -> Self {
925 let (tx, rx) = tokio::sync::broadcast::channel(SIG_QUEUE_LEN);
926 let dropped = Arc::new(AtomicU64::new(0));
927 let gaps = Arc::new(AtomicU64::new(0));
928 let fatal: Arc<OnceLock<Error>> = Arc::new(OnceLock::new());
929
930 let drain_conn = conn.clone();
931 let drain_fatal = Arc::clone(&fatal);
932 let drain_gaps = Arc::clone(&gaps);
933 let drain = tokio::spawn(async move {
934 let mut last_seq: Option<u64> = None;
935 loop {
936 match drain_conn.read_datagram().await {
937 Ok(dg) => {
938 if let Some(item) = apply_datagram(&dg, &mut last_seq, &drain_gaps) {
939 if tx.send(item).is_err() {
942 return;
943 }
944 }
945 }
946 Err(quinn::ConnectionError::LocallyClosed) => return,
947 Err(e) => {
948 let error = close_info(&e)
949 .map(Error::ApplicationClosed)
950 .unwrap_or_else(|| Error::Io(e.to_string()));
951 let _ = drain_fatal.set(error);
952 return;
953 }
954 }
955 }
956 });
957
958 SigFirstSub {
959 conn: Some(conn),
960 rx,
961 dropped,
962 gaps,
963 fatal,
964 drain: Some(drain),
965 }
966 }
967
968 #[cfg(test)]
969 fn for_test(rx: tokio::sync::broadcast::Receiver<SigFirstItem>) -> Self {
970 SigFirstSub {
971 conn: None,
972 rx,
973 dropped: Arc::new(AtomicU64::new(0)),
974 gaps: Arc::new(AtomicU64::new(0)),
975 fatal: Arc::new(OnceLock::new()),
976 drain: None,
977 }
978 }
979
980 pub async fn next(&mut self) -> Result<Option<SigFirstItem>> {
987 loop {
988 match self.rx.recv().await {
989 Ok(item) => return Ok(Some(item)),
990 Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
993 self.dropped.fetch_add(n, Ordering::Relaxed);
994 }
995 Err(tokio::sync::broadcast::error::RecvError::Closed) => {
996 return match self.fatal.get() {
997 Some(e) => Err(e.clone()),
998 None => Ok(None),
999 };
1000 }
1001 }
1002 }
1003 }
1004
1005 pub fn dropped(&self) -> u64 {
1008 self.dropped.load(Ordering::Relaxed)
1009 }
1010
1011 pub fn gaps(&self) -> u64 {
1024 self.gaps.load(Ordering::Relaxed)
1025 }
1026
1027 pub async fn update_filter(&self, filter: &Filter) -> Result<Ack> {
1032 let ack = match &self.conn {
1033 Some(conn) => control_round_trip(conn, filter, false, &[], "").await,
1034 None => Ok(Ack {
1036 message_type: Some("ack".to_owned()),
1037 ok: true,
1038 reason: None,
1039 code: None,
1040 v: None,
1041 }),
1042 }?;
1043 ensure_update_ack(&ack)?;
1044 Ok(ack)
1045 }
1046}
1047
1048impl Drop for SigFirstSub {
1049 fn drop(&mut self) {
1050 if let Some(drain) = self.drain.take() {
1051 drain.abort();
1052 }
1053 }
1054}
1055
1056const MAX_FULL_TX_FRAME: usize =
1062 thornode_pulse_wire::frame::MAX_FULL_TX_BODY + 2 * (u16::MAX as usize + 3) + 2;
1063
1064pub struct FullSub {
1066 conn: quinn::Connection,
1067 recv: quinn::RecvStream,
1068 buf: Vec<u8>,
1069 last_heartbeat: Option<(u64, u64)>,
1072}
1073
1074impl FullSub {
1075 pub async fn next(&mut self) -> Result<Option<Frame>> {
1081 match next_frame(&mut self.recv, &mut self.buf, &mut self.last_heartbeat).await {
1082 Ok(None) => match terminal_error(&self.conn) {
1083 Some(error) => Err(error),
1084 None => Ok(None),
1085 },
1086 Err(error) => Err(merge_wire_and_terminal(error, terminal_error(&self.conn))),
1087 ok => ok,
1088 }
1089 }
1090
1091 pub fn heartbeat(&self) -> Option<(u64, u64)> {
1103 self.last_heartbeat
1104 }
1105
1106 pub async fn update_filter(&self, filter: &Filter, fields: &[&str]) -> Result<Ack> {
1110 let ack = control_round_trip(&self.conn, filter, true, fields, "").await?;
1111 ensure_update_ack(&ack)?;
1112 Ok(ack)
1113 }
1114}
1115
1116async fn next_frame<R: tokio::io::AsyncRead + Unpin>(
1123 recv: &mut R,
1124 buf: &mut Vec<u8>,
1125 last_heartbeat: &mut Option<(u64, u64)>,
1126) -> Result<Option<Frame>> {
1127 loop {
1128 let len = match read_n_or_eof(recv, buf, 4).await? {
1130 Some(()) => {
1131 let l = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
1132 if l > MAX_FULL_TX_FRAME {
1133 return Err(Error::BadFrame);
1134 }
1135 l
1136 }
1137 None => return Ok(None),
1138 };
1139 match read_n_or_eof(recv, buf, len).await {
1140 Ok(Some(())) => match decode_frame(&buf[..len]) {
1141 Ok(Frame::Unknown(_)) => continue,
1142 Ok(Frame::Heartbeat {
1143 server_ts_ms,
1144 highest_seq,
1145 }) => {
1146 *last_heartbeat = Some((server_ts_ms, highest_seq));
1147 continue;
1148 }
1149 Ok(tx @ Frame::Tx(_)) => return Ok(Some(tx)),
1150 Err(_) => return Err(Error::BadFrame),
1151 },
1152 Ok(None) | Err(_) => return Err(Error::BadFrame),
1159 }
1160 }
1161}
1162
1163async fn read_n_or_eof<R: tokio::io::AsyncRead + Unpin>(
1167 recv: &mut R,
1168 buf: &mut Vec<u8>,
1169 n: usize,
1170) -> Result<Option<()>> {
1171 buf.resize(n, 0);
1172 let mut got = 0;
1173 while got < n {
1174 match recv.read(&mut buf[got..n]).await {
1175 Ok(0) => {
1176 return if got == 0 {
1177 Ok(None)
1178 } else {
1179 Err(Error::BadFrame)
1180 };
1181 }
1182 Ok(k) => got += k,
1183 Err(error) if got == 0 => return Err(Error::Io(error.to_string())),
1184 Err(_) => return Err(Error::BadFrame),
1185 }
1186 }
1187 Ok(Some(()))
1188}
1189
1190fn merge_wire_and_terminal(error: Error, terminal: Option<Error>) -> Error {
1191 match (error, terminal) {
1192 (Error::BadFrame, Some(Error::ApplicationClosed(close))) => Error::BadFrameWithClose(close),
1193 (Error::BadPreamble, Some(Error::ApplicationClosed(close))) => {
1194 Error::BadPreambleWithClose(close)
1195 }
1196 (_, Some(terminal)) => terminal,
1197 (error, None) => error,
1198 }
1199}
1200
1201#[cfg(test)]
1202mod tests {
1203 use super::*;
1204
1205 #[tokio::test]
1210 async fn stalled_consumer_loses_oldest_and_counts_them() {
1211 let (tx, rx) = tokio::sync::broadcast::channel(4);
1212 let mut sub = SigFirstSub::for_test(rx);
1213
1214 for slot in 0..10u64 {
1215 tx.send(SigFirstItem {
1216 slot,
1217 seq: slot,
1218 signature: [0u8; 64],
1219 })
1220 .unwrap();
1221 }
1222 drop(tx); let mut got = Vec::new();
1225 while let Some(item) = sub.next().await.unwrap() {
1226 got.push(item.slot);
1227 }
1228
1229 assert_eq!(got, vec![6, 7, 8, 9], "the freshest items must survive");
1230 assert_eq!(sub.dropped(), 6);
1231 }
1232
1233 #[tokio::test]
1235 async fn consumer_that_keeps_up_drops_nothing() {
1236 let (tx, rx) = tokio::sync::broadcast::channel(4);
1237 let mut sub = SigFirstSub::for_test(rx);
1238
1239 tx.send(SigFirstItem {
1240 slot: 1,
1241 seq: 0,
1242 signature: [0u8; 64],
1243 })
1244 .unwrap();
1245 tx.send(SigFirstItem {
1246 slot: 2,
1247 seq: 1,
1248 signature: [0u8; 64],
1249 })
1250 .unwrap();
1251 drop(tx);
1252
1253 let mut got = Vec::new();
1254 while let Some(item) = sub.next().await.unwrap() {
1255 got.push(item.slot);
1256 }
1257
1258 assert_eq!(got, vec![1, 2]);
1259 assert_eq!(sub.dropped(), 0);
1260 }
1261
1262 #[test]
1265 fn note_item_seq_counts_missed_numbers_between_consecutive_items() {
1266 let mut last_seq = None;
1267 let gaps = AtomicU64::new(0);
1268 note_item_seq(&mut last_seq, &gaps, 0);
1269 assert_eq!(
1270 gaps.load(Ordering::Relaxed),
1271 0,
1272 "first item establishes the baseline"
1273 );
1274 note_item_seq(&mut last_seq, &gaps, 3); assert_eq!(gaps.load(Ordering::Relaxed), 2);
1276 assert_eq!(last_seq, Some(3));
1277 }
1278
1279 #[test]
1280 fn note_item_seq_out_of_order_never_underflows() {
1281 let mut last_seq = Some(10u64);
1284 let gaps = AtomicU64::new(0);
1285 note_item_seq(&mut last_seq, &gaps, 3);
1286 assert_eq!(
1287 gaps.load(Ordering::Relaxed),
1288 0,
1289 "no underflow, no bogus gap"
1290 );
1291 assert_eq!(last_seq, Some(10), "watermark must not regress on reorder");
1297 note_item_seq(&mut last_seq, &gaps, 11);
1298 assert_eq!(
1299 gaps.load(Ordering::Relaxed),
1300 0,
1301 "seq 11 directly follows the watermark of 10"
1302 );
1303 assert_eq!(last_seq, Some(11));
1304 }
1305
1306 #[test]
1307 fn note_item_seq_reordering_does_not_double_count_the_same_gap() {
1308 let mut last_seq = None;
1312 let gaps = AtomicU64::new(0);
1313 for seq in [0u64, 2, 1, 3] {
1314 note_item_seq(&mut last_seq, &gaps, seq);
1315 }
1316 assert_eq!(
1317 gaps.load(Ordering::Relaxed),
1318 1,
1319 "one provisional gap from the 0->2 jump, never double-charged on the later in-order 3"
1320 );
1321 assert_eq!(
1322 last_seq,
1323 Some(3),
1324 "watermark must track the highest seq seen, not the latest arrival"
1325 );
1326 }
1327
1328 #[test]
1329 fn note_item_seq_sentinel_seq_does_not_overflow_the_gap_addition() {
1330 let mut last_seq = Some(u64::MAX);
1335 let gaps = AtomicU64::new(0);
1336 note_item_seq(&mut last_seq, &gaps, u64::MAX);
1337 assert_eq!(gaps.load(Ordering::Relaxed), 0);
1338 assert_eq!(last_seq, Some(u64::MAX));
1339 }
1340
1341 #[test]
1342 fn note_heartbeat_seq_sentinel_is_never_a_gap() {
1343 let mut last_seq = Some(5u64);
1347 let gaps = AtomicU64::new(0);
1348 note_heartbeat_seq(&mut last_seq, &gaps, NO_SEQ_ASSIGNED);
1349 assert_eq!(gaps.load(Ordering::Relaxed), 0);
1350 assert_eq!(
1351 last_seq,
1352 Some(5),
1353 "the sentinel must not overwrite a real baseline either"
1354 );
1355 }
1356
1357 #[test]
1358 fn note_heartbeat_seq_reveals_trailing_loss() {
1359 let mut last_seq = Some(2u64);
1363 let gaps = AtomicU64::new(0);
1364 note_heartbeat_seq(&mut last_seq, &gaps, 7);
1365 assert_eq!(gaps.load(Ordering::Relaxed), 5);
1366 assert_eq!(last_seq, Some(7));
1367 }
1368
1369 #[test]
1370 fn note_heartbeat_seq_first_observation_establishes_a_baseline_not_a_gap() {
1371 let mut last_seq = None;
1374 let gaps = AtomicU64::new(0);
1375 note_heartbeat_seq(&mut last_seq, &gaps, 9);
1376 assert_eq!(gaps.load(Ordering::Relaxed), 0);
1377 assert_eq!(last_seq, Some(9));
1378 }
1379
1380 #[test]
1383 fn apply_datagram_skips_an_unknown_type() {
1384 let mut last_seq = None;
1385 let gaps = AtomicU64::new(0);
1386 let buf = [200u8, 1, 2, 3];
1387 assert_eq!(apply_datagram(&buf, &mut last_seq, &gaps), None);
1388 assert_eq!(gaps.load(Ordering::Relaxed), 0);
1389 }
1390
1391 #[test]
1392 fn apply_datagram_forwards_sig_first_and_tracks_gaps() {
1393 let mut last_seq = None;
1394 let gaps = AtomicU64::new(0);
1395 let mut buf = [0u8; thornode_pulse_wire::frame::DG_SIG_FIRST_MIN];
1396
1397 thornode_pulse_wire::frame::encode_dg_sig_first(&mut buf, 100, 0, &[1u8; 64]);
1398 let item = apply_datagram(&buf, &mut last_seq, &gaps).expect("sig-first forwards");
1399 assert_eq!((item.slot, item.seq), (100, 0));
1400
1401 thornode_pulse_wire::frame::encode_dg_sig_first(&mut buf, 100, 3, &[1u8; 64]);
1402 let item = apply_datagram(&buf, &mut last_seq, &gaps).expect("sig-first forwards");
1403 assert_eq!(item.seq, 3);
1404 assert_eq!(gaps.load(Ordering::Relaxed), 2, "missed seq 1 and 2");
1405 }
1406
1407 #[test]
1408 fn apply_datagram_heartbeat_is_never_forwarded_as_an_item() {
1409 let mut last_seq = Some(1u64);
1410 let gaps = AtomicU64::new(0);
1411 let mut buf = [0u8; thornode_pulse_wire::frame::DG_HEARTBEAT_MIN];
1412 thornode_pulse_wire::frame::encode_dg_heartbeat(&mut buf, 123, 4);
1413 assert_eq!(apply_datagram(&buf, &mut last_seq, &gaps), None);
1414 assert_eq!(gaps.load(Ordering::Relaxed), 3);
1415 }
1416
1417 fn sample_full_tx() -> thornode_pulse_wire::frame::FullTx {
1420 thornode_pulse_wire::frame::FullTx {
1421 slot: 438_690_000,
1422 versioned: false,
1423 num_required_signatures: 1,
1424 num_readonly_signed_accounts: 0,
1425 num_readonly_unsigned_accounts: 0,
1426 recent_blockhash: [0xCC; 32],
1427 signatures: vec![[7u8; 64]],
1428 account_keys: vec![[0xA1; 32]],
1429 instructions: vec![thornode_pulse_wire::frame::FullInstruction {
1430 program_id_index: 0,
1431 accounts: vec![],
1432 data: vec![9, 9],
1433 }],
1434 address_table_lookups: vec![],
1435 }
1436 }
1437
1438 async fn write_framed(w: &mut (impl tokio::io::AsyncWrite + Unpin), body: &[u8]) {
1439 use tokio::io::AsyncWriteExt;
1440 w.write_all(&(body.len() as u32).to_be_bytes())
1441 .await
1442 .unwrap();
1443 w.write_all(body).await.unwrap();
1444 }
1445
1446 #[tokio::test]
1447 async fn next_frame_skips_unknown_and_folds_heartbeat_without_surfacing_it() {
1448 let (mut writer, mut reader) = tokio::io::duplex(4096);
1449
1450 write_framed(&mut writer, &[99u8, 0]).await;
1452
1453 let mut hb = Vec::new();
1455 hb.push(thornode_pulse_wire::frame::MSG_HEARTBEAT);
1456 hb.push(0);
1457 thornode_pulse_wire::frame::put_tlv(
1458 &mut hb,
1459 thornode_pulse_wire::frame::TLV_SERVER_TS_MS,
1460 &123u64.to_le_bytes(),
1461 );
1462 thornode_pulse_wire::frame::put_tlv(
1463 &mut hb,
1464 thornode_pulse_wire::frame::TLV_HIGHEST_SEQ,
1465 &7u64.to_le_bytes(),
1466 );
1467 write_framed(&mut writer, &hb).await;
1468
1469 let tx = sample_full_tx();
1471 let tx_bytes = thornode_pulse_wire::frame::encode_frame_tx(&tx, false, &[], &[]);
1472 write_framed(&mut writer, &tx_bytes).await;
1473 drop(writer); let mut buf = Vec::new();
1476 let mut last_heartbeat = None;
1477 let got = next_frame(&mut reader, &mut buf, &mut last_heartbeat)
1478 .await
1479 .unwrap();
1480 match got {
1481 Some(Frame::Tx(v2)) => assert_eq!(v2.tx, tx),
1482 other => panic!("expected Some(Frame::Tx(_)), got {other:?}"),
1483 }
1484 assert_eq!(
1485 last_heartbeat,
1486 Some((123, 7)),
1487 "the heartbeat must be captured via the accessor, not returned as an item"
1488 );
1489 }
1490
1491 #[tokio::test]
1492 async fn next_frame_returns_none_at_a_clean_end_of_stream() {
1493 let (writer, mut reader) = tokio::io::duplex(64);
1494 drop(writer);
1495 let mut buf = Vec::new();
1496 let mut last_heartbeat = None;
1497 assert_eq!(
1498 next_frame(&mut reader, &mut buf, &mut last_heartbeat)
1499 .await
1500 .unwrap(),
1501 None
1502 );
1503 }
1504
1505 #[tokio::test]
1508 async fn next_frame_rejects_a_length_prefix_with_no_body() {
1509 for body_bytes in [0usize, 3] {
1510 let (mut writer, mut reader) = tokio::io::duplex(64);
1511 {
1512 use tokio::io::AsyncWriteExt;
1513 writer.write_all(&64u32.to_be_bytes()).await.unwrap();
1514 if body_bytes > 0 {
1515 writer.write_all(&vec![0u8; body_bytes]).await.unwrap();
1516 }
1517 }
1518 drop(writer); let mut buf = Vec::new();
1520 let mut last_heartbeat = None;
1521 let err = next_frame(&mut reader, &mut buf, &mut last_heartbeat)
1522 .await
1523 .unwrap_err();
1524 assert!(
1525 matches!(err, Error::BadFrame),
1526 "a 64-byte frame truncated to {body_bytes} body bytes must be BadFrame, got {err:?}"
1527 );
1528 }
1529 }
1530
1531 #[tokio::test]
1535 async fn next_frame_rejects_a_partial_length_prefix() {
1536 let (mut writer, mut reader) = tokio::io::duplex(64);
1537 {
1538 use tokio::io::AsyncWriteExt;
1539 writer.write_all(&[0u8, 0, 1]).await.unwrap(); }
1541 drop(writer);
1542 let mut buf = Vec::new();
1543 let mut last_heartbeat = None;
1544 let err = next_frame(&mut reader, &mut buf, &mut last_heartbeat)
1545 .await
1546 .unwrap_err();
1547 assert!(matches!(err, Error::BadFrame), "got {err:?}");
1548 }
1549
1550 #[test]
1551 fn truncated_frame_preserves_a_simultaneous_application_close() {
1552 let close = CloseInfo {
1553 code: 3,
1554 reason: "capacity temporarily unavailable".to_owned(),
1555 };
1556 let error = merge_wire_and_terminal(
1557 Error::BadFrame,
1558 Some(Error::ApplicationClosed(close.clone())),
1559 );
1560 assert!(error.is_bad_frame());
1561 assert_eq!(error.close_info(), Some(&close));
1562 assert!(matches!(error, Error::BadFrameWithClose(_)));
1563 }
1564
1565 #[tokio::test]
1568 async fn verify_preamble_accepts_the_real_preamble() {
1569 let (mut writer, mut reader) = tokio::io::duplex(64);
1570 tokio::spawn(async move {
1571 use tokio::io::AsyncWriteExt;
1572 let _ = writer.write_all(thornode_pulse_wire::frame::PREAMBLE).await;
1573 });
1574 verify_preamble(&mut reader).await.unwrap();
1575 }
1576
1577 #[tokio::test]
1578 async fn verify_preamble_rejects_a_mismatched_header_loudly() {
1579 let (mut writer, mut reader) = tokio::io::duplex(64);
1580 tokio::spawn(async move {
1581 use tokio::io::AsyncWriteExt;
1582 let _ = writer.write_all(b"XXXXXX").await;
1583 });
1584 let err = verify_preamble(&mut reader).await.unwrap_err();
1585 assert!(matches!(err, Error::BadPreamble), "got {err:?}");
1586 }
1587
1588 #[tokio::test]
1589 async fn verify_preamble_rejects_a_short_stream_loudly() {
1590 let (writer, mut reader) = tokio::io::duplex(64);
1591 drop(writer); let err = verify_preamble(&mut reader).await.unwrap_err();
1593 assert!(matches!(err, Error::BadPreamble), "got {err:?}");
1594 }
1595
1596 fn parse_ack(json: &str) -> Ack {
1599 serde_json::from_str(json).expect("ack envelope must deserialize")
1600 }
1601
1602 #[test]
1606 fn an_ack_negotiating_an_older_version_is_rejected() {
1607 let err = ensure_initial_ack(&parse_ack(r#"{"type":"ack","ok":true,"v":1}"#)).unwrap_err();
1608 match err {
1609 Error::VersionMismatch(v) => assert_eq!(v, 1),
1610 other => panic!("expected VersionMismatch, got {other:?}"),
1611 }
1612 }
1613
1614 #[test]
1615 fn initial_ack_requires_v_but_update_ack_may_omit_it() {
1616 ensure_initial_ack(&parse_ack(r#"{"type":"ack","ok":true,"v":2}"#))
1617 .expect("v2 is what we speak");
1618 let missing = parse_ack(r#"{"type":"ack","ok":true}"#);
1619 assert_eq!(ensure_initial_ack(&missing), Err(Error::MissingVersion));
1620 ensure_update_ack(&missing).expect("an update ack intentionally omits v");
1621 ensure_update_ack(&parse_ack(r#"{"type":"ack","ok":true,"v":2}"#))
1622 .expect("a matching additive update version is harmless");
1623 assert_eq!(
1624 ensure_update_ack(&parse_ack(r#"{"type":"ack","ok":true,"v":3}"#)),
1625 Err(Error::VersionMismatch(3))
1626 );
1627 }
1628
1629 #[test]
1630 fn ack_envelope_requires_a_known_type() {
1631 for json in [
1632 r#"{"ok":true,"v":2}"#,
1633 r#"{"type":"future","ok":true,"v":2}"#,
1634 r#"{"type":"error","ok":true,"code":4,"v":2}"#,
1635 ] {
1636 assert_eq!(ensure_initial_ack(&parse_ack(json)), Err(Error::BadFrame));
1637 }
1638 }
1639
1640 #[test]
1643 fn the_code_4_error_envelope_surfaces_typed_close_not_a_bad_frame() {
1644 let ack = parse_ack(
1645 r#"{"type":"error","code":4,"reason":"unsupported protocol version; this server speaks wire v2"}"#,
1646 );
1647 assert!(!ack.ok, "an envelope with no `ok` field is not a success");
1648 match ensure_initial_ack(&ack).unwrap_err() {
1649 Error::ApplicationClosed(close) => {
1650 assert_eq!(close.code, 4);
1651 assert_eq!(
1652 close.reason,
1653 "unsupported protocol version; this server speaks wire v2"
1654 );
1655 assert_eq!(close.retry_class(), RetryClass::NonRetryable);
1656 assert!(!close.retryable());
1657 }
1658 other => panic!("expected typed code-4 close, got {other:?}"),
1659 }
1660 }
1661
1662 #[test]
1663 fn a_rejection_ack_surfaces_its_reason() {
1664 match ensure_initial_ack(&parse_ack(
1665 r#"{"type":"ack","ok":false,"reason":"quota exceeded: 51 > 50 accounts"}"#,
1666 ))
1667 .unwrap_err()
1668 {
1669 Error::Rejected(reason) => assert_eq!(reason, "quota exceeded: 51 > 50 accounts"),
1670 other => panic!("expected Rejected, got {other:?}"),
1671 }
1672 }
1673
1674 #[test]
1682 fn client_socket_enlarges_the_receive_buffer() {
1683 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
1684
1685 let default_size = socket2::Socket::from(std::net::UdpSocket::bind(addr).unwrap())
1686 .recv_buffer_size()
1687 .unwrap();
1688 let ours = socket2::Socket::from(client_socket(addr, DEFAULT_RECV_BUFFER).unwrap())
1689 .recv_buffer_size()
1690 .unwrap();
1691
1692 assert!(
1693 ours > default_size,
1694 "recv buffer {ours} is no larger than the default {default_size}"
1695 );
1696 }
1697
1698 #[test]
1701 fn client_socket_survives_a_clamped_request() {
1702 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
1703
1704 let sock = client_socket(addr, 1 << 30).expect("clamped request must not fail");
1705
1706 assert!(sock.local_addr().is_ok());
1707 }
1708}
1709
1710#[derive(Debug)]
1712struct NoVerify;
1713impl rustls::client::danger::ServerCertVerifier for NoVerify {
1714 fn verify_server_cert(
1715 &self,
1716 _e: &rustls::pki_types::CertificateDer<'_>,
1717 _i: &[rustls::pki_types::CertificateDer<'_>],
1718 _s: &rustls::pki_types::ServerName<'_>,
1719 _o: &[u8],
1720 _n: rustls::pki_types::UnixTime,
1721 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
1722 Ok(rustls::client::danger::ServerCertVerified::assertion())
1723 }
1724 fn verify_tls12_signature(
1725 &self,
1726 _m: &[u8],
1727 _c: &rustls::pki_types::CertificateDer<'_>,
1728 _d: &rustls::DigitallySignedStruct,
1729 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1730 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
1731 }
1732 fn verify_tls13_signature(
1733 &self,
1734 _m: &[u8],
1735 _c: &rustls::pki_types::CertificateDer<'_>,
1736 _d: &rustls::DigitallySignedStruct,
1737 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1738 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
1739 }
1740 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
1741 rustls::crypto::ring::default_provider()
1742 .signature_verification_algorithms
1743 .supported_schemes()
1744 }
1745}