Skip to main content

tuwunel_service/appservice/
ping.rs

1use bytes::BytesMut;
2use reqwest::Request;
3use ruma::api::{
4	OutgoingRequest,
5	appservice::{Registration, ping::send_ping},
6	auth_scheme::SendAccessToken,
7	error::{BadStatusErrorData, ErrorKind},
8};
9use tuwunel_core::{
10	Error, Result, err, http::StatusCode, implement, utils::string_from_bytes, warn,
11};
12
13use super::request::add_access_token_query;
14use crate::client::read_response_capped;
15
16/// Pings an appservice per MSC2659, surfacing the spec's failure error codes.
17///
18/// Returns `Ok(())` without pinging when the registration has no URL set.
19#[implement(super::Service)]
20pub async fn ping(
21	&self,
22	registration: Registration,
23	request: send_ping::v1::Request,
24) -> Result<()> {
25	let Some(dest) = registration.url else {
26		return Ok(());
27	};
28
29	if dest == *"null" || dest.is_empty() {
30		return Ok(());
31	}
32
33	let hs_token = registration.hs_token.as_str();
34	let mut http_request = request
35		.try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(hs_token), ())
36		.map_err(|e| {
37			err!(Request(ConnectionFailed(warn!(
38				appservice = %registration.id,
39				%dest,
40				?e,
41				"Failed to find appservice destination"
42			))))
43		})?
44		.map(BytesMut::freeze);
45
46	add_access_token_query(&mut http_request, hs_token);
47
48	let reqwest_request = Request::try_from(http_request)?;
49
50	let response = self
51		.services
52		.client
53		.appservice
54		.execute(reqwest_request)
55		.await
56		.map_err(|e| {
57			if e.is_timeout() {
58				err!(Request(ConnectionTimeout(warn!(
59					appservice = %registration.id,
60					%dest,
61					?e,
62					"Connection to appservice timed out"
63				))))
64			} else {
65				err!(Request(ConnectionFailed(warn!(
66					appservice = %registration.id,
67					%dest,
68					?e,
69					"Could not send request to appservice"
70				))))
71			}
72		})?;
73
74	let status = response.status();
75	if status.is_success() {
76		return Ok(());
77	}
78
79	let limit = self.services.config.max_response_size;
80	let body = read_response_capped(response, limit)
81		.await
82		.ok()
83		.and_then(|body| string_from_bytes(&body).ok());
84
85	warn!(
86		appservice = %registration.id,
87		%status,
88		%dest,
89		"Appservice returned unsuccessful HTTP response to ping"
90	);
91
92	Err(Error::Request(
93		ErrorKind::BadStatus(BadStatusErrorData { status: Some(status), body }),
94		format!("Appservice returned status {status}").into(),
95		StatusCode::BAD_REQUEST,
96	))
97}