Skip to main content

oxide_mesh/
message.rs

1//! Wire protocol types.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// Stable peer identifier (human-chosen or auto-generated UUID).
8pub type PeerId = String;
9
10/// What a peer claims to be able to do. Free-form so federated-learning
11/// extensions can introduce new capability strings without a wire-format
12/// change.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct PeerCapability {
15    /// Capability slug (`"browser"`, `"mirror"`, `"compute"`, …).
16    pub name: String,
17    /// Optional semantic version.
18    #[serde(default)]
19    pub version: Option<String>,
20}
21
22/// One message on the mesh.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "kind", rename_all = "snake_case")]
25pub enum PeerMessage {
26    /// First message after a connection establishes — peers identify
27    /// themselves and advertise capabilities.
28    Hello {
29        /// Sender id.
30        from: PeerId,
31        /// Capability set.
32        capabilities: Vec<PeerCapability>,
33    },
34    /// Fanout publish to every peer subscribed to `topic`.
35    Broadcast {
36        /// Sender id.
37        from: PeerId,
38        /// Free-form topic.
39        topic: String,
40        /// Arbitrary JSON payload.
41        payload: serde_json::Value,
42    },
43    /// Point-to-point delivery.
44    Direct {
45        /// Sender.
46        from: PeerId,
47        /// Intended recipient.
48        to: PeerId,
49        /// Arbitrary JSON payload.
50        payload: serde_json::Value,
51    },
52    /// Distributed task assignment.
53    Task {
54        /// Sender (originator).
55        from: PeerId,
56        /// Task id (caller-chosen or auto).
57        task_id: String,
58        /// Task description.
59        spec: serde_json::Value,
60        /// Wall-clock time the task was published.
61        issued_at: DateTime<Utc>,
62    },
63    /// Result of a previously-issued [`PeerMessage::Task`].
64    Result {
65        /// Responder.
66        from: PeerId,
67        /// Same `task_id` as the originating Task.
68        task_id: String,
69        /// Result payload.
70        result: serde_json::Value,
71        /// Whether the task completed successfully.
72        ok: bool,
73    },
74}
75
76impl PeerMessage {
77    /// Sender id, useful for routing decisions.
78    pub fn sender(&self) -> &PeerId {
79        match self {
80            PeerMessage::Hello { from, .. }
81            | PeerMessage::Broadcast { from, .. }
82            | PeerMessage::Direct { from, .. }
83            | PeerMessage::Task { from, .. }
84            | PeerMessage::Result { from, .. } => from,
85        }
86    }
87
88    /// Convenience: build a broadcast message.
89    pub fn broadcast(
90        from: impl Into<PeerId>,
91        topic: impl Into<String>,
92        payload: serde_json::Value,
93    ) -> Self {
94        Self::Broadcast {
95            from: from.into(),
96            topic: topic.into(),
97            payload,
98        }
99    }
100
101    /// Convenience: build a direct message.
102    pub fn direct(
103        from: impl Into<PeerId>,
104        to: impl Into<PeerId>,
105        payload: serde_json::Value,
106    ) -> Self {
107        Self::Direct {
108            from: from.into(),
109            to: to.into(),
110            payload,
111        }
112    }
113
114    /// Convenience: build a task with an auto-generated id.
115    pub fn task(from: impl Into<PeerId>, spec: serde_json::Value) -> Self {
116        Self::Task {
117            from: from.into(),
118            task_id: Uuid::new_v4().to_string(),
119            spec,
120            issued_at: Utc::now(),
121        }
122    }
123}