Skip to main content

core_api/
external_rpc.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::{
5    AuthRequest, AuthenticatedSession, ServiceCoreEffect, ServiceCoreInput, ServiceCoreOutput,
6};
7use std::collections::HashMap;
8
9pub const EXTERNAL_CORE_PROTOCOL_VERSION: u32 = 8;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ExternalCoreRequest {
13    pub id: String,
14    pub method: ExternalCoreMethod,
15    pub payload: Value,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ExternalCoreResponse {
20    pub id: String,
21    pub ok: bool,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub result: Option<Value>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub error: Option<ExternalCoreError>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ExternalCoreError {
30    pub code: String,
31    pub message: String,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub enum ExternalCoreMethod {
36    HandleCoreInput,
37    RegisterLocalAppClient,
38    RegisterNodeConnection,
39    CleanupWsState,
40    UpdateCallbackBase,
41    DeviceIdentity,
42    CommissionFingerprint,
43    CommissionPublicKeyBase64,
44    ListMdnsRecords,
45    PollEvents,
46    CompleteEvents,
47    CommissionChallengePayload,
48    PairingStartPayload,
49    SetupStartPayload,
50    GeneralWebhookPayload,
51    PlaygroundWebhookPayload,
52    InvokeWasmPayload,
53    Health,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct HandleCoreInputRequest {
58    pub input: ServiceCoreInput,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct HandleCoreInputResponse {
63    pub output: ServiceCoreOutput,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct RegisterLocalAppClientRequest {
68    pub ws_id: String,
69    pub auth_request: AuthRequest,
70    pub session: AuthenticatedSession,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct RegisterNodeConnectionRequest {
75    pub ws_id: String,
76    pub session_id: String,
77    pub node_type: String,
78    pub instance_id: String,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub host_name: Option<String>,
81    pub tenant_id: String,
82    pub scope_id: String,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct CleanupWsStateRequest {
87    pub ws_id: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct UpdateCallbackBaseRequest {
92    pub callback_base: String,
93    pub host: String,
94    pub port: u16,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct ExternalCoreDeviceIdentity {
100    pub device_id: String,
101    pub device_name: String,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct ListMdnsRecordsRequest {
107    pub port: u16,
108    pub addresses: Vec<String>,
109    pub host_type: String,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct ExternalCoreMdnsRecord {
115    pub service_type: String,
116    pub instance_name: String,
117    pub port: u16,
118    pub properties: HashMap<String, String>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct PollExternalCoreEventsRequest {
124    pub consumer_id: String,
125    pub max_events: u16,
126    pub timeout_ms: u64,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct PollExternalCoreEventsResponse {
132    pub events: Vec<ExternalCoreEvent>,
133    pub timed_out: bool,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct CompleteExternalCoreEventsRequest {
139    pub consumer_id: String,
140    pub completions: Vec<ExternalCoreEventCompletion>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct CompleteExternalCoreEventsResponse {
146    pub completed_event_ids: Vec<String>,
147    pub missing_event_ids: Vec<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct ExternalCoreEvent {
153    pub event_id: String,
154    pub kind: ExternalCoreEventKind,
155    pub payload: Value,
156    pub expects_response: bool,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
160#[serde(rename_all = "camelCase")]
161pub enum ExternalCoreEventKind {
162    MdnsRecordsChanged,
163    ServiceEffects,
164    HostRuntimeRequest,
165    ScopeOwnedDataPurgeRequested,
166}
167
168/// A platform capability request emitted by an externally hosted Core.
169///
170/// Core owns the onboarding workflow. The native host owns LAN discovery and
171/// transport to a discovered candidate, so Android can satisfy this contract
172/// without moving application logic into the mobile shell.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct ExternalHostRuntimeRequest {
176    pub method: ExternalHostRuntimeMethod,
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub candidate_id: Option<String>,
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub action: Option<String>,
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub payload: Option<Value>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
186#[serde(rename_all = "camelCase")]
187pub enum ExternalHostRuntimeMethod {
188    Discovery,
189    CandidateRequest,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
193#[serde(rename_all = "camelCase")]
194pub struct ScopeOwnedDataPurgeRequest {
195    pub tenant_id: String,
196    pub scope_id: String,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200#[serde(rename_all = "camelCase")]
201pub struct ExternalCoreServiceEffects {
202    pub effects: Vec<ServiceCoreEffect>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct ExternalCoreEventCompletion {
208    pub event_id: String,
209    pub ok: bool,
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub response: Option<Value>,
212    #[serde(default, skip_serializing_if = "Option::is_none")]
213    pub error: Option<ExternalCoreError>,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct HttpPayloadRequest {
218    pub payload: Value,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct EndpointPayloadRequest {
223    pub endpoint_id: String,
224    pub payload: Value,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct PlaygroundPayloadRequest {
229    pub tenant_id: String,
230    pub scope_id: String,
231    pub device_id: String,
232    pub capability_id: String,
233    pub payload: Value,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct ExternalCoreRuntimeMetadata {
239    pub backend: String,
240    pub instance_id: String,
241    pub pid: u32,
242    pub state: String,
243    pub started_at: String,
244    pub protocol_version: u32,
245    pub binary_version: String,
246    pub socket_path: String,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct ExternalCoreHealth {
252    pub ok: bool,
253    pub ready: bool,
254    pub backend: String,
255    pub binary_version: String,
256    pub agent_version: String,
257    pub protocol_version: u32,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub instance_id: Option<String>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub pid: Option<u32>,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub state: Option<String>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub started_at: Option<String>,
266    #[serde(default)]
267    pub capabilities: Vec<String>,
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn scope_owned_data_purge_event_uses_the_v8_wire_shape() {
276        let event = ExternalCoreEvent {
277            event_id: "event-1".to_string(),
278            kind: ExternalCoreEventKind::ScopeOwnedDataPurgeRequested,
279            payload: serde_json::to_value(ScopeOwnedDataPurgeRequest {
280                tenant_id: "tenant-1".to_string(),
281                scope_id: "scope-1".to_string(),
282            })
283            .unwrap(),
284            expects_response: true,
285        };
286
287        let value = serde_json::to_value(event).unwrap();
288        assert_eq!(EXTERNAL_CORE_PROTOCOL_VERSION, 8);
289        assert_eq!(value["kind"], "scopeOwnedDataPurgeRequested");
290        assert_eq!(value["payload"]["tenantId"], "tenant-1");
291        assert_eq!(value["payload"]["scopeId"], "scope-1");
292        assert_eq!(value["expectsResponse"], true);
293    }
294}