Skip to main content

arminius_lib_federation_output_service_messages/
command.rs

1// SPDX-FileCopyrightText: 2026 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Commands the federation output service handles.
6use std::collections::BTreeMap;
7
8use arminius_lib_message_bus::Command;
9use arminius_lib_user_keys::{identity_keys::RawUserKeys, one_time_keys::FederatedOneTimeKeys};
10use ruma::{
11	OneTimeKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId,
12	RoomVersionId,
13	api::federation::{
14		discovery::ServerSigningKeys,
15		membership::{RawStrippedState, create_join_event::v2::RoomState},
16	},
17};
18use serde::{Deserialize, Serialize};
19use serde_json::value::RawValue;
20
21use crate::error::MiscRequestError;
22
23/// The JetStream stream filter every command in this crate resolves to.
24///
25/// Asserted against each command's [`Command::SUBJECT_FILTER`] in the crate's
26/// tests, so a subject that would land on a different (and possibly
27/// overlapping) stream fails the build rather than the deployment.
28pub const COMMAND_SUBJECT_FILTER: &str = "federation-output-service.commands.>";
29
30/// Fetch a remote homeserver's signing keys.
31#[derive(Debug, Clone, Serialize, Deserialize, Command)]
32#[subject(name = "federation-output-service.commands.discover_server_keys", version = "1")]
33#[command(reply = Result<ServerSigningKeys, MiscRequestError>)]
34pub struct DiscoverServerKeysCommand {
35	/// The homeserver to query.
36	pub destination: OwnedServerName,
37}
38
39/// A remote homeserver's answer to a `make_join` request.
40///
41/// Ruma's own `prepare_join_event::v1::Response` is not `Serialize`, so the two
42/// fields the caller needs are carried in their own type.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct MakeJoinReply {
45	/// The room version the remote advertised.
46	pub room_version: Option<RoomVersionId>,
47	/// The join event template.
48	pub event: Box<RawValue>,
49}
50
51/// Ask a remote homeserver for a join event template.
52#[derive(Debug, Clone, Serialize, Deserialize, Command)]
53#[subject(name = "federation-output-service.commands.make_join", version = "1")]
54#[command(reply = Result<MakeJoinReply, MiscRequestError>)]
55pub struct MakeJoinCommand {
56	/// The homeserver to query.
57	pub destination: OwnedServerName,
58	/// The room being joined.
59	pub room_id: OwnedRoomId,
60	/// The user doing the joining.
61	pub user_id: OwnedUserId,
62}
63
64/// Submit a signed join event to a remote homeserver.
65#[derive(Debug, Clone, Serialize, Deserialize, Command)]
66#[subject(name = "federation-output-service.commands.send_join", version = "1")]
67#[command(reply = Result<RoomState, MiscRequestError>)]
68pub struct SendJoinCommand {
69	/// The homeserver to send to.
70	pub destination: OwnedServerName,
71	/// The room being joined.
72	pub room_id: OwnedRoomId,
73	/// The ID of the join event.
74	pub event_id: OwnedEventId,
75	/// The join PDU.
76	pub pdu: Box<RawValue>,
77}
78
79/// Invite a remote user into a local room.
80///
81/// Replies with the PDU as countersigned by the remote homeserver. It stays a
82/// [`RawValue`] because it is canonical JSON, and re-encoding it as ordinary
83/// JSON would invalidate the signature.
84#[derive(Debug, Clone, Serialize, Deserialize, Command)]
85#[subject(name = "federation-output-service.commands.send_invite", version = "1")]
86#[command(reply = Result<Box<RawValue>, MiscRequestError>)]
87pub struct SendInviteCommand {
88	/// The homeserver to send to.
89	pub destination: OwnedServerName,
90	/// The room the invite is for.
91	pub room_id: OwnedRoomId,
92	/// The ID of the invite event.
93	pub event_id: OwnedEventId,
94	/// The version of the room the invite is for.
95	pub room_version: RoomVersionId,
96	/// The invite PDU.
97	pub pdu: Box<RawValue>,
98	/// Stripped state giving the invitee context about the room.
99	pub invite_room_state: Vec<RawStrippedState>,
100}
101
102/// Fetch a user's profile from their homeserver.
103#[derive(Debug, Clone, Serialize, Deserialize, Command)]
104#[subject(name = "federation-output-service.commands.get_profile", version = "1")]
105#[command(reply = Result<serde_json::Value, MiscRequestError>)]
106pub struct GetProfileCommand {
107	/// The homeserver to query.
108	pub destination: OwnedServerName,
109	/// The user whose profile to fetch.
110	pub user_id: OwnedUserId,
111}
112
113/// Claim one-time keys from a remote homeserver.
114///
115/// This is the one command in this crate that is *not* idempotent: every
116/// attempt that reaches the remote consumes a key there, so it must never be
117/// retried blindly. Hence
118/// [`Delivery::AtMostOnce`](arminius_lib_message_bus::Delivery::AtMostOnce):
119/// the handler acknowledges it before asking the remote, and the broker will
120/// not hand it out a second time. A caller left unanswered by a handler that
121/// died mid-request retries at its own discretion; a key consumed twice cannot
122/// be given back.
123#[derive(Debug, Clone, Serialize, Deserialize, Command)]
124#[subject(name = "federation-output-service.commands.claim_one_time_keys", version = "1")]
125#[command(reply = Result<FederatedOneTimeKeys, MiscRequestError>, delivery = at_most_once)]
126pub struct ClaimOneTimeKeysCommand {
127	/// The homeserver to query.
128	pub destination: OwnedServerName,
129	/// The key algorithm wanted, per user and device.
130	pub one_time_keys: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
131}
132
133/// Fetch identity keys from a remote homeserver.
134#[derive(Debug, Clone, Serialize, Deserialize, Command)]
135#[subject(name = "federation-output-service.commands.fetch_user_keys", version = "1")]
136#[command(reply = Result<RawUserKeys, MiscRequestError>)]
137pub struct FetchUserKeysCommand {
138	/// The homeserver to query.
139	pub destination: OwnedServerName,
140	/// The devices whose keys to fetch, per user.
141	pub device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
142}
143
144/// Retrieve a single event from a remote homeserver.
145#[derive(Debug, Clone, Serialize, Deserialize, Command)]
146#[subject(name = "federation-output-service.commands.retrieve_event", version = "1")]
147#[command(reply = Result<serde_json::Value, MiscRequestError>)]
148pub struct RetrieveEventCommand {
149	/// The homeserver to query.
150	pub destination: OwnedServerName,
151	/// The event to retrieve.
152	pub event_id: OwnedEventId,
153}