iroh_services/
protocol.rs1use 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, Serialize, Deserialize)]
99pub struct Auth {
100 pub caps: Rcan<Caps>,
101}
102
103#[derive(Debug, Serialize, Deserialize)]
105pub struct PutMetrics {
106 pub session_id: Uuid,
107 pub update: iroh_metrics::encoding::Update,
108}
109
110#[derive(Debug, Serialize, Deserialize)]
112pub struct Ping {
113 pub req_id: [u8; 16],
114}
115
116#[derive(Debug, Serialize, Deserialize)]
118pub struct Pong {
119 pub req_id: [u8; 16],
120}
121
122#[derive(Debug, Serialize, Deserialize)]
124pub struct PutNetworkDiagnostics {
125 pub report: crate::net_diagnostics::DiagnosticsReport,
126}
127
128#[derive(Debug, Serialize, Deserialize)]
132pub struct RunNetworkDiagnostics;
133
134#[derive(Debug, Serialize, Deserialize)]
137pub struct GrantCap {
138 pub cap: Rcan<Caps>,
139}
140
141#[derive(Debug, Serialize, Deserialize)]
143pub struct NameEndpoint {
144 pub name: String,
145}
146
147#[derive(Debug, Serialize, Deserialize)]
149pub struct SetGroup {
150 pub group: String,
151}
152
153#[derive(Debug, Serialize, Deserialize)]
155pub struct SetAttributes {
156 pub attributes: BTreeMap<String, String>,
157}
158
159#[cfg(test)]
160mod tests {
161 use std::collections::BTreeMap;
162
163 use super::{RemoteError, SetAttributes, SetGroup};
164 use crate::client::CLIENT_ATTRIBUTE_VALUE_MAX_LENGTH;
165
166 #[test]
167 fn test_remote_error_wire_compat() {
168 let idx = |e: &RemoteError| postcard::to_stdvec(e).expect("encode")[0];
172 assert_eq!(idx(&RemoteError::AuthError(String::new())), 1);
173 assert_eq!(idx(&RemoteError::InternalServerError), 2);
174 assert_eq!(idx(&RemoteError::InvalidInput(String::new())), 3);
176 assert_eq!(idx(&RemoteError::RateLimited), 4);
177 }
178
179 #[test]
184 fn test_set_group_serde_roundtrip() {
185 for group in ["staging", "my-group 👋"] {
187 let msg = SetGroup {
188 group: group.to_string(),
189 };
190 let bytes = postcard::to_stdvec(&msg).expect("postcard serialize");
191 let decoded: SetGroup = postcard::from_bytes(&bytes).expect("postcard deserialize");
192 assert_eq!(decoded.group, msg.group);
193 }
194 }
195
196 #[test]
197 fn test_set_attributes_serde_roundtrip() {
198 let empty = SetAttributes {
200 attributes: BTreeMap::new(),
201 };
202 let bytes = postcard::to_stdvec(&empty).expect("postcard serialize");
203 let decoded: SetAttributes = postcard::from_bytes(&bytes).expect("postcard deserialize");
204 assert!(decoded.attributes.is_empty());
205 assert_eq!(decoded.attributes, empty.attributes);
206
207 let mut attributes = BTreeMap::new();
209 attributes.insert("région 🌍".to_string(), "us-wëst 🚀".to_string());
210 let max_value = "x".repeat(CLIENT_ATTRIBUTE_VALUE_MAX_LENGTH);
211 assert_eq!(max_value.len(), CLIENT_ATTRIBUTE_VALUE_MAX_LENGTH);
212 attributes.insert("max".to_string(), max_value);
213
214 let msg = SetAttributes { attributes };
215 let bytes = postcard::to_stdvec(&msg).expect("postcard serialize");
216 let decoded: SetAttributes = postcard::from_bytes(&bytes).expect("postcard deserialize");
217 assert_eq!(decoded.attributes, msg.attributes);
218 }
219}