1use std::collections::BTreeMap;
30use std::fmt;
31use std::sync::Arc;
32
33use serde_json::Value;
34
35use crate::contract::{validate_method_id, CallContext, Emit, Handler, MethodPolicy, MethodTable};
36use crate::contract::{TableError, Transport};
37use crate::error::ServiceError;
38use crate::server::ServiceConfig;
39use crate::suite::valid_service_name;
40use crate::Allowlist;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[non_exhaustive]
45pub enum TransportProfile {
46 Local,
49 LocalAndRemote,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ServiceIdentity {
56 service: String,
57 role: String,
58 rank: u8,
59 version: String,
60}
61
62fn plain_text(text: &str) -> bool {
63 !text.is_empty() && text.len() <= 64 && text.chars().all(|c| !c.is_control())
64}
65
66impl ServiceIdentity {
67 pub fn new(
70 service: &str,
71 role: &str,
72 rank: u8,
73 version: &str,
74 ) -> Result<Self, RegistrationError> {
75 if !valid_service_name(service) {
76 return Err(RegistrationError::BadIdentity("service name"));
77 }
78 if !plain_text(role) {
79 return Err(RegistrationError::BadIdentity("role"));
80 }
81 if !plain_text(version) {
82 return Err(RegistrationError::BadIdentity("version"));
83 }
84 Ok(Self {
85 service: service.to_string(),
86 role: role.to_string(),
87 rank,
88 version: version.to_string(),
89 })
90 }
91
92 pub fn service(&self) -> &str {
93 &self.service
94 }
95
96 pub fn role(&self) -> &str {
97 &self.role
98 }
99
100 pub fn rank(&self) -> u8 {
101 self.rank
102 }
103
104 pub fn version(&self) -> &str {
105 &self.version
106 }
107
108 pub fn to_config(&self) -> ServiceConfig {
111 ServiceConfig::new(
112 &self.service,
113 &self.role,
114 self.rank,
115 &self.version,
116 Allowlist::new(),
117 )
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct MethodDescriptor {
124 id: String,
125 policy: MethodPolicy,
126}
127
128impl MethodDescriptor {
129 pub fn new(id: impl Into<String>, policy: MethodPolicy) -> Self {
130 Self {
131 id: id.into(),
132 policy,
133 }
134 }
135
136 pub fn local<I, S>(id: impl Into<String>, apps: I) -> Self
138 where
139 I: IntoIterator<Item = S>,
140 S: Into<String>,
141 {
142 Self::new(id, MethodPolicy::local(apps))
143 }
144
145 pub fn id(&self) -> &str {
146 &self.id
147 }
148
149 pub fn policy(&self) -> &MethodPolicy {
150 &self.policy
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum RegistrationError {
158 BadIdentity(&'static str),
160 Table(TableError),
162 Reserved(String),
164 Duplicate(String),
166 RemoteMethodInLocalProfile(String),
168 RemoteNotDeclared,
170 NoMethods,
172}
173
174impl fmt::Display for RegistrationError {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 RegistrationError::BadIdentity(part) => {
178 write!(f, "service registration: the {part} is not acceptable")
179 }
180 RegistrationError::Table(e) => write!(f, "service registration: {e}"),
181 RegistrationError::Reserved(id) => {
182 write!(f, "service registration: {id:?} is answered by the host")
183 }
184 RegistrationError::Duplicate(id) => {
185 write!(f, "service registration: method {id:?} is registered twice")
186 }
187 RegistrationError::RemoteMethodInLocalProfile(id) => write!(
188 f,
189 "service registration: method {id:?} is reachable by remote devices but the profile is local"
190 ),
191 RegistrationError::RemoteNotDeclared => f.write_str(
192 "service registration: a remote daemon was configured for a local-only registration",
193 ),
194 RegistrationError::NoMethods => {
195 f.write_str("service registration: no method was registered")
196 }
197 }
198 }
199}
200
201impl std::error::Error for RegistrationError {}
202
203impl From<TableError> for RegistrationError {
204 fn from(e: TableError) -> Self {
205 RegistrationError::Table(e)
206 }
207}
208
209type MethodFn =
210 Box<dyn Fn(&CallContext, &Value, &Emit) -> Result<Value, ServiceError> + Send + Sync>;
211
212type Parts = (
214 ServiceIdentity,
215 TransportProfile,
216 MethodTable,
217 Arc<dyn Handler>,
218);
219
220pub struct Registration {
223 identity: ServiceIdentity,
224 transport: TransportProfile,
225 table: MethodTable,
226 handlers: BTreeMap<String, MethodFn>,
227 on_handoff: Option<Box<dyn Fn() + Send + Sync>>,
228 on_connection_closed: Option<Box<dyn Fn(u64) + Send + Sync>>,
229}
230
231impl Registration {
232 pub fn new(identity: ServiceIdentity) -> Self {
234 Self {
235 identity,
236 transport: TransportProfile::Local,
237 table: MethodTable::new(),
238 handlers: BTreeMap::new(),
239 on_handoff: None,
240 on_connection_closed: None,
241 }
242 }
243
244 pub fn transport(mut self, transport: TransportProfile) -> Self {
246 self.transport = transport;
247 self
248 }
249
250 pub fn method(
252 self,
253 descriptor: MethodDescriptor,
254 handler: impl Fn(&CallContext, &Value, &Emit) -> Result<Value, ServiceError>
255 + Send
256 + Sync
257 + 'static,
258 ) -> Result<Self, RegistrationError> {
259 self.method_boxed(descriptor, Box::new(handler))
260 }
261
262 fn method_boxed(
263 mut self,
264 descriptor: MethodDescriptor,
265 handler: MethodFn,
266 ) -> Result<Self, RegistrationError> {
267 let MethodDescriptor { id, policy } = descriptor;
268 validate_method_id(&id)?;
269 if id == "ping" {
270 return Err(RegistrationError::Reserved(id));
271 }
272 if self.handlers.contains_key(&id) {
273 return Err(RegistrationError::Duplicate(id));
274 }
275 if self.transport == TransportProfile::Local && policy.transport() != Transport::Local {
276 return Err(RegistrationError::RemoteMethodInLocalProfile(id));
277 }
278 self.table = self.table.clone().try_method(id.clone(), policy)?;
280 self.handlers.insert(id, handler);
281 Ok(self)
282 }
283
284 pub fn on_handoff(mut self, hook: impl Fn() + Send + Sync + 'static) -> Self {
286 self.on_handoff = Some(Box::new(hook));
287 self
288 }
289
290 pub fn on_connection_closed(mut self, hook: impl Fn(u64) + Send + Sync + 'static) -> Self {
292 self.on_connection_closed = Some(Box::new(hook));
293 self
294 }
295
296 pub fn identity(&self) -> &ServiceIdentity {
297 &self.identity
298 }
299
300 pub fn profile(&self) -> TransportProfile {
301 self.transport
302 }
303
304 pub fn methods(&self) -> &MethodTable {
305 &self.table
306 }
307
308 pub fn into_parts(self) -> Result<Parts, RegistrationError> {
311 if self.handlers.is_empty() {
312 return Err(RegistrationError::NoMethods);
313 }
314 let handler: Arc<dyn Handler> = Arc::new(Registered {
315 handlers: self.handlers,
316 on_handoff: self.on_handoff,
317 on_connection_closed: self.on_connection_closed,
318 });
319 Ok((self.identity, self.transport, self.table, handler))
320 }
321}
322
323struct Registered {
325 handlers: BTreeMap<String, MethodFn>,
326 on_handoff: Option<Box<dyn Fn() + Send + Sync>>,
327 on_connection_closed: Option<Box<dyn Fn(u64) + Send + Sync>>,
328}
329
330impl Handler for Registered {
331 fn call(
332 &self,
333 cx: &CallContext,
334 method: &str,
335 params: &Value,
336 emit: &Emit,
337 ) -> Result<Value, ServiceError> {
338 match self.handlers.get(method) {
339 Some(handler) => handler(cx, params, emit),
340 None => Err(ServiceError::new(
343 "unknown_method",
344 format!("{method} is not registered"),
345 )),
346 }
347 }
348
349 fn on_handoff(&self) {
350 if let Some(hook) = &self.on_handoff {
351 hook();
352 }
353 }
354
355 fn connection_closed(&self, conn: u64) {
356 if let Some(hook) = &self.on_connection_closed {
357 hook(conn);
358 }
359 }
360}