1use std::sync::Arc;
2
3use axum::{
4 extract::{Json, Path, State},
5 http::status::StatusCode,
6 response::IntoResponse,
7};
8use futures::FutureExt;
9use hopr_lib::{
10 Address, Multiaddr,
11 errors::{HoprLibError, HoprStatusError, HoprTransportError},
12};
13use serde::{Deserialize, Serialize};
14use serde_with::{DisplayFromStr, DurationMilliSeconds, serde_as};
15use tracing::debug;
16
17use crate::{ApiError, ApiErrorStatus, BASE_PATH, InternalState};
18
19#[serde_as]
20#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
21#[schema(example = json!({
22 "announced": [
23 "/ip4/10.0.2.100/tcp/19093"
24 ],
25 "observed": [
26 "/ip4/10.0.2.100/tcp/19093"
27 ]
28}))]
29pub(crate) struct NodePeerInfoResponse {
31 #[serde_as(as = "Vec<DisplayFromStr>")]
32 #[schema(value_type = Vec<String>, example = json!(["/ip4/10.0.2.100/tcp/19093"]))]
33 announced: Vec<Multiaddr>,
34 #[serde_as(as = "Vec<DisplayFromStr>")]
35 #[schema(value_type = Vec<String>, example = json!(["/ip4/10.0.2.100/tcp/19093"]))]
36 observed: Vec<Multiaddr>,
37}
38
39#[serde_as]
40#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
41#[serde(rename_all = "camelCase")]
42pub(crate) struct DestinationParams {
43 #[serde_as(as = "DisplayFromStr")]
44 #[schema(value_type = String)]
45 destination: Address,
46}
47
48#[utoipa::path(
53 get,
54 path = const_format::formatcp!("{BASE_PATH}/peers/{{destination}}"),
55 params(
56 ("destination" = String, Path, description = "Address of the requested peer", example = "0x07eaf07d6624f741e04f4092a755a9027aaab7f6"),
57 ),
58 responses(
59 (status = 200, description = "Peer information fetched successfully.", body = NodePeerInfoResponse),
60 (status = 400, description = "Invalid destination", body = ApiError),
61 (status = 401, description = "Invalid authorization token.", body = ApiError),
62 (status = 422, description = "Unknown failure", body = ApiError)
63 ),
64 security(
65 ("api_token" = []),
66 ("bearer_token" = [])
67 ),
68 tag = "Peers",
69)]
70pub(super) async fn show_peer_info(
71 Path(DestinationParams { destination }): Path<DestinationParams>,
72 State(state): State<Arc<InternalState>>,
73) -> impl IntoResponse {
74 let hopr = state.hopr.clone();
75
76 match hopr.chain_key_to_peerid(&destination).await {
77 Ok(Some(peer)) => {
78 let res = futures::try_join!(
79 hopr.multiaddresses_announced_on_chain(&peer),
80 hopr.network_observed_multiaddresses(&peer).map(Ok)
81 );
82 match res {
83 Ok((announced, observed)) => Ok((StatusCode::OK, Json(NodePeerInfoResponse { announced, observed }))),
84 Err(error) => Err(ApiErrorStatus::UnknownFailure(error.to_string())),
85 }
86 }
87 Ok(None) => Err(ApiErrorStatus::PeerNotFound),
88 Err(_) => Err(ApiErrorStatus::PeerNotFound),
89 }
90}
91
92#[serde_as]
93#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
94#[schema(example = json!({
95 "latency": 200,
96}))]
97#[serde(rename_all = "camelCase")]
98pub(crate) struct PingResponse {
100 #[serde_as(as = "DurationMilliSeconds<u64>")]
101 #[schema(value_type = u64, example = 200)]
102 latency: std::time::Duration,
103}
104
105#[utoipa::path(
107 post,
108 path = const_format::formatcp!("{BASE_PATH}/peers/{{destination}}/ping"),
109 description = "Directly ping the given peer",
110 params(
111 ("destination" = String, Path, description = "Address of the requested peer", example = "0x07eaf07d6624f741e04f4092a755a9027aaab7f6"),
112 ),
113 responses(
114 (status = 200, description = "Ping successful", body = PingResponse),
115 (status = 400, description = "Invalid peer id", body = ApiError),
116 (status = 401, description = "Invalid authorization token.", body = ApiError),
117 (status = 404, description = "Peer id not found in the network.", body = ApiError),
118 (status = 408, description = "Peer timed out.", body = ApiError),
119 (status = 412, description = "The node is not ready."),
120 (status = 422, description = "Unknown failure", body = ApiError)
121 ),
122 security(
123 ("api_token" = []),
124 ("bearer_token" = [])
125 ),
126 tag = "Peers",
127)]
128pub(super) async fn ping_peer(
129 Path(DestinationParams { destination }): Path<DestinationParams>,
130 State(state): State<Arc<InternalState>>,
131) -> Result<impl IntoResponse, ApiError> {
132 debug!(%destination, "Manually ping peer");
133
134 let hopr = state.hopr.clone();
135
136 match hopr.chain_key_to_peerid(&destination).await {
137 Ok(Some(peer)) => match hopr.ping(&peer).await {
138 Ok((latency, _status)) => {
139 let resp = Json(PingResponse { latency: latency / 2 });
140 Ok((StatusCode::OK, resp).into_response())
141 }
142 Err(HoprLibError::TransportError(HoprTransportError::Protocol(
143 hopr_lib::errors::ProtocolError::Timeout,
144 ))) => Ok((StatusCode::REQUEST_TIMEOUT, ApiErrorStatus::Timeout).into_response()),
145 Err(HoprLibError::TransportError(HoprTransportError::Probe(
146 hopr_lib::ProbeError::ProbeNeighborTimeout(_),
147 ))) => Ok((StatusCode::REQUEST_TIMEOUT, ApiErrorStatus::Timeout).into_response()),
148 Err(HoprLibError::TransportError(HoprTransportError::Probe(hopr_lib::ProbeError::PingerError(_, e)))) => {
149 Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::PingError(e)).into_response())
150 }
151 Err(HoprLibError::TransportError(HoprTransportError::Probe(hopr_lib::ProbeError::NonExistingPeer))) => {
152 Ok((StatusCode::NOT_FOUND, ApiErrorStatus::PeerNotFound).into_response())
153 }
154 Err(HoprLibError::StatusError(HoprStatusError::NotThereYet(..))) => {
155 Ok((StatusCode::PRECONDITION_FAILED, ApiErrorStatus::NotReady).into_response())
156 }
157 Err(e) => Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::from(e)).into_response()),
158 },
159 Ok(None) => Ok((StatusCode::NOT_FOUND, ApiErrorStatus::PeerNotFound).into_response()),
160 Err(_) => Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::PeerNotFound).into_response()),
161 }
162}