ados_sdk/context/network.rs
1//! `ctx.cloud`, `ctx.node` and `ctx.mdns`: the facades that reach past the
2//! plugin's sandbox to the cloud relay, the node facts and the local network.
3
4use std::collections::BTreeMap;
5use std::sync::Arc;
6
7use rmpv::Value;
8
9use crate::client::{ClientError, PluginIpcClient};
10
11/// `ctx.cloud` — this plugin's own cloud stream and cloud records.
12///
13/// Both go through the agent's cloud relay under the plugin's verified id, so a
14/// plugin only ever publishes into its own namespace. The relay being down is
15/// the resting state on an unpaired or offline node: both calls then answer a
16/// `not_available` map rather than an error.
17#[derive(Clone)]
18pub struct CloudClient {
19 pub(super) ipc: Arc<PluginIpcClient>,
20}
21
22impl CloudClient {
23 /// Publish one lossy message on the plugin's cloud stream `stream`. Needs
24 /// `cloud.publish`. See [`PluginIpcClient::cloud_publish`].
25 pub async fn publish(&self, stream: &str, payload: &[u8]) -> Result<Value, ClientError> {
26 self.ipc.cloud_publish(stream, payload).await
27 }
28
29 /// Upsert the record `key` in the plugin's cloud collection `collection`,
30 /// about `device_id` when given (else this node). Needs `cloud.records`.
31 /// See [`PluginIpcClient::cloud_records_put`].
32 pub async fn put_record(
33 &self,
34 collection: &str,
35 key: &str,
36 data: Value,
37 device_id: Option<&str>,
38 ) -> Result<Value, ClientError> {
39 self.ipc
40 .cloud_records_put(collection, key, data, device_id)
41 .await
42 }
43}
44
45/// `ctx.node` — facts about the node the plugin runs on.
46///
47/// The plugin's sandbox hides the agent's sidecars and config, so this is the
48/// sanctioned read of what the agent itself decides on: the profile, the board
49/// and its accelerators, the ground-station role, whether the camera pipeline
50/// is up and the main stream's geometry.
51#[derive(Clone)]
52pub struct NodeClient {
53 pub(super) ipc: Arc<PluginIpcClient>,
54}
55
56impl NodeClient {
57 /// Read the node facts. Needs `node.info.read`. Each fact whose source is
58 /// absent is `None`. See [`PluginIpcClient::node_info`].
59 pub async fn info(&self) -> Result<ados_protocol::node_info::NodeInfo, ClientError> {
60 self.ipc.node_info().await
61 }
62}
63
64/// `ctx.mdns` — find and offer services on the local network.
65///
66/// The plugin's sandbox cannot bind the mDNS port, so the host runs the
67/// responder: `advertise` publishes one of the plugin's declared listen ports
68/// under the node's hostname for as long as this connection lives, and
69/// `browse` collects the instances of one service type.
70#[derive(Clone)]
71pub struct MdnsClient {
72 pub(super) ipc: Arc<PluginIpcClient>,
73}
74
75impl MdnsClient {
76 /// Publish `port` as `service_type` with `txt`. Needs `network.listen`,
77 /// and the port must be a declared listen port of a service that runs on
78 /// this node. See [`PluginIpcClient::mdns_advertise`].
79 pub async fn advertise(
80 &self,
81 service_type: &str,
82 port: u16,
83 txt: &BTreeMap<String, String>,
84 ) -> Result<ados_protocol::plugin_mdns::Advertised, ClientError> {
85 self.ipc.mdns_advertise(service_type, port, txt).await
86 }
87
88 /// Every instance of `service_type` answering within `window`. Needs
89 /// `network.outbound`. See [`PluginIpcClient::mdns_browse`].
90 pub async fn browse(
91 &self,
92 service_type: &str,
93 window: std::time::Duration,
94 ) -> Result<Vec<ados_protocol::plugin_mdns::DiscoveredService>, ClientError> {
95 self.ipc.mdns_browse(service_type, window).await
96 }
97}