1use std::collections::BTreeMap;
2
3use anyhow::Result;
4use irpc::{channel::oneshot, rpc_requests};
5use rcan::Rcan;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use crate::{caps::Caps, net_diagnostics::DiagnosticsReport};
10
11pub const ALPN: &[u8] = b"/iroh/n0des/1";
32
33pub type IrohServicesClient = irpc::Client<IrohServicesProtocol>;
34
35#[rpc_requests(message = ServicesMessage)]
38#[derive(Debug, Serialize, Deserialize)]
39#[allow(clippy::large_enum_variant)]
40#[non_exhaustive]
41pub enum IrohServicesProtocol {
42 #[rpc(tx=oneshot::Sender<()>)]
43 Auth(Auth),
44 #[rpc(tx=oneshot::Sender<RemoteResult<()>>)]
45 PutMetrics(PutMetrics),
46 #[rpc(tx=oneshot::Sender<Pong>)]
47 Ping(Ping),
48
49 #[rpc(tx=oneshot::Sender<RemoteResult<()>>)]
50 PutNetworkDiagnostics(PutNetworkDiagnostics),
51
52 #[rpc(tx=oneshot::Sender<RemoteResult<()>>)]
53 GrantCap(GrantCap),
54
55 #[rpc(tx=oneshot::Sender<RemoteResult<()>>)]
56 NameEndpoint(NameEndpoint),
57
58 #[rpc(tx=oneshot::Sender<RemoteResult<()>>)]
59 SetGroup(SetGroup),
60
61 #[rpc(tx=oneshot::Sender<RemoteResult<()>>)]
62 SetAttributes(SetAttributes),
63}
64
65#[rpc_requests(message = NetDiagnosticsMessage)]
67#[derive(Debug, Serialize, Deserialize)]
68#[allow(clippy::large_enum_variant)]
69pub enum ClientHostProtocol {
70 #[rpc(tx=oneshot::Sender<()>)]
71 Auth(Auth),
72 #[rpc(tx=oneshot::Sender<RemoteResult<DiagnosticsReport>>)]
73 RunNetworkDiagnostics(RunNetworkDiagnostics),
74}
75
76pub type RemoteResult<T> = Result<T, RemoteError>;
77
78#[derive(Clone, Serialize, Deserialize, thiserror::Error, Debug)]
79#[non_exhaustive]
80pub enum RemoteError {
81 #[error("Missing capability: {}", _0.to_strings().join(", "))]
86 MissingCapability(Caps),
87 #[error("Unauthorized: {}", _0)]
88 AuthError(String),
89 #[error("Internal server error")]
90 InternalServerError,
91 #[error("Invalid input: {}", _0)]
92 InvalidInput(String),
93 #[error("Rate limit exceeded")]
94 RateLimited,
95}
96
97#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
99#[display("Auth")]
100pub struct Auth {
101 pub caps: Rcan<Caps>,
102}
103
104#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
106#[display("PutMetrics")]
107pub struct PutMetrics {
108 pub session_id: Uuid,
109 pub update: iroh_metrics::encoding::Update,
110}
111
112#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
114#[display("Ping")]
115pub struct Ping {
116 pub req_id: [u8; 16],
117}
118
119#[derive(Debug, Serialize, Deserialize)]
121pub struct Pong {
122 pub req_id: [u8; 16],
123}
124
125#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
127#[display("PutNetworkDiagnostics")]
128pub struct PutNetworkDiagnostics {
129 pub report: crate::net_diagnostics::DiagnosticsReport,
130}
131
132#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
136#[display("RunNetworkDiagnostics")]
137pub struct RunNetworkDiagnostics;
138
139#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
142#[display("GrantCap")]
143pub struct GrantCap {
144 pub cap: Rcan<Caps>,
145}
146
147#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
149#[display("NameEndpoint")]
150pub struct NameEndpoint {
151 pub name: String,
152}
153
154#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
156#[display("SetGroup")]
157pub struct SetGroup {
158 pub group: String,
159}
160
161#[derive(Debug, derive_more::Display, Serialize, Deserialize)]
163#[display("SetAttributes")]
164pub struct SetAttributes {
165 pub attributes: BTreeMap<String, String>,
166}
167
168#[cfg(test)]
169mod tests {
170 use std::collections::BTreeMap;
171
172 use super::{RemoteError, SetAttributes, SetGroup};
173 use crate::client::CLIENT_ATTRIBUTE_VALUE_MAX_LENGTH;
174
175 #[test]
176 fn test_remote_error_wire_compat() {
177 let idx = |e: &RemoteError| postcard::to_stdvec(e).expect("encode")[0];
181 assert_eq!(idx(&RemoteError::AuthError(String::new())), 1);
182 assert_eq!(idx(&RemoteError::InternalServerError), 2);
183 assert_eq!(idx(&RemoteError::InvalidInput(String::new())), 3);
185 assert_eq!(idx(&RemoteError::RateLimited), 4);
186 }
187
188 #[test]
193 fn test_set_group_serde_roundtrip() {
194 for group in ["staging", "my-group 👋"] {
196 let msg = SetGroup {
197 group: group.to_string(),
198 };
199 let bytes = postcard::to_stdvec(&msg).expect("postcard serialize");
200 let decoded: SetGroup = postcard::from_bytes(&bytes).expect("postcard deserialize");
201 assert_eq!(decoded.group, msg.group);
202 }
203 }
204
205 #[test]
206 fn test_set_attributes_serde_roundtrip() {
207 let empty = SetAttributes {
209 attributes: BTreeMap::new(),
210 };
211 let bytes = postcard::to_stdvec(&empty).expect("postcard serialize");
212 let decoded: SetAttributes = postcard::from_bytes(&bytes).expect("postcard deserialize");
213 assert!(decoded.attributes.is_empty());
214 assert_eq!(decoded.attributes, empty.attributes);
215
216 let mut attributes = BTreeMap::new();
218 attributes.insert("région 🌍".to_string(), "us-wëst 🚀".to_string());
219 let max_value = "x".repeat(CLIENT_ATTRIBUTE_VALUE_MAX_LENGTH);
220 assert_eq!(max_value.len(), CLIENT_ATTRIBUTE_VALUE_MAX_LENGTH);
221 attributes.insert("max".to_string(), max_value);
222
223 let msg = SetAttributes { attributes };
224 let bytes = postcard::to_stdvec(&msg).expect("postcard serialize");
225 let decoded: SetAttributes = postcard::from_bytes(&bytes).expect("postcard deserialize");
226 assert_eq!(decoded.attributes, msg.attributes);
227 }
228}