1use chrono::{Duration, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::{Value, json};
8use uuid::Uuid;
9
10use crate::constants::{ACP_VERSION, DEFAULT_CRYPTO_SUITE};
11use crate::errors::{AcpError, AcpResult};
12use crate::json_support::{self, JsonMap};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
16pub enum MessageClass {
17 Send,
18 Ack,
19 Fail,
20 Capabilities,
21 Compensate,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
26pub enum DeliveryState {
27 Pending,
28 Delivered,
29 Acknowledged,
30 Failed,
31 Declined,
32 Expired,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
37pub enum DeliveryMode {
38 Auto,
39 Direct,
40 Relay,
41 Amqp,
42 Mqtt,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct WrappedContentKey {
47 pub recipient: String,
48 #[serde(rename = "ephemeral_public_key")]
49 pub ephemeral_public_key: String,
50 pub nonce: String,
51 pub ciphertext: String,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct Envelope {
56 #[serde(rename = "acp_version")]
57 pub acp_version: String,
58 #[serde(rename = "message_class")]
59 pub message_class: MessageClass,
60 #[serde(rename = "message_id")]
61 pub message_id: String,
62 #[serde(rename = "operation_id")]
63 pub operation_id: String,
64 pub timestamp: String,
65 #[serde(rename = "expires_at")]
66 pub expires_at: String,
67 pub sender: String,
68 pub recipients: Vec<String>,
69 #[serde(rename = "context_id")]
70 pub context_id: String,
71 #[serde(rename = "crypto_suite")]
72 pub crypto_suite: String,
73 #[serde(
74 rename = "correlation_id",
75 default,
76 skip_serializing_if = "Option::is_none"
77 )]
78 pub correlation_id: Option<String>,
79 #[serde(
80 rename = "in_reply_to",
81 default,
82 skip_serializing_if = "Option::is_none"
83 )]
84 pub in_reply_to: Option<String>,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct ProtectedPayload {
89 pub nonce: String,
90 pub ciphertext: String,
91 #[serde(rename = "wrapped_content_keys")]
92 pub wrapped_content_keys: Vec<WrappedContentKey>,
93 #[serde(rename = "payload_hash")]
94 pub payload_hash: String,
95 #[serde(rename = "signature_kid")]
96 pub signature_kid: String,
97 pub signature: String,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct AcpMessage {
102 pub envelope: Envelope,
103 #[serde(rename = "protected")]
104 pub protected_payload: ProtectedPayload,
105 #[serde(
106 rename = "sender_identity_document",
107 default,
108 skip_serializing_if = "Option::is_none"
109 )]
110 pub sender_identity_document: Option<JsonMap>,
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub struct DeliveryOutcome {
115 pub recipient: String,
116 pub state: DeliveryState,
117 #[serde(
118 rename = "status_code",
119 default,
120 skip_serializing_if = "Option::is_none"
121 )]
122 pub status_code: Option<u16>,
123 #[serde(
124 rename = "response_class",
125 default,
126 skip_serializing_if = "Option::is_none"
127 )]
128 pub response_class: Option<MessageClass>,
129 #[serde(
130 rename = "reason_code",
131 default,
132 skip_serializing_if = "Option::is_none"
133 )]
134 pub reason_code: Option<String>,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub detail: Option<String>,
137 #[serde(
138 rename = "response_message",
139 default,
140 skip_serializing_if = "Option::is_none"
141 )]
142 pub response_message: Option<JsonMap>,
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub struct SendResult {
147 #[serde(rename = "operation_id")]
148 pub operation_id: String,
149 #[serde(rename = "message_id")]
150 pub message_id: String,
151 #[serde(rename = "message_ids", default, skip_serializing_if = "Vec::is_empty")]
152 pub message_ids: Vec<String>,
153 #[serde(default)]
154 pub outcomes: Vec<DeliveryOutcome>,
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub struct CompensateInstruction {
159 #[serde(rename = "operation_id")]
160 pub operation_id: String,
161 pub reason: String,
162 #[serde(default)]
163 pub actions: Vec<JsonMap>,
164}
165
166impl Envelope {
167 #[allow(clippy::too_many_arguments)]
168 pub fn build(
169 sender: impl Into<String>,
170 recipients: Vec<String>,
171 message_class: MessageClass,
172 context_id: impl Into<String>,
173 expires_in_seconds: i64,
174 operation_id: Option<String>,
175 correlation_id: Option<String>,
176 in_reply_to: Option<String>,
177 crypto_suite: Option<String>,
178 ) -> AcpResult<Self> {
179 let now = Utc::now();
180 let expires_at = now + Duration::seconds(expires_in_seconds.max(1));
181 let env = Self {
182 acp_version: ACP_VERSION.to_string(),
183 message_class,
184 message_id: Uuid::new_v4().to_string(),
185 operation_id: operation_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
186 timestamp: now.to_rfc3339(),
187 expires_at: expires_at.to_rfc3339(),
188 sender: sender.into(),
189 recipients,
190 context_id: context_id.into(),
191 crypto_suite: crypto_suite.unwrap_or_else(|| DEFAULT_CRYPTO_SUITE.to_string()),
192 correlation_id,
193 in_reply_to,
194 };
195 env.validate()?;
196 Ok(env)
197 }
198
199 pub fn validate(&self) -> AcpResult<()> {
200 if self.sender.trim().is_empty() {
201 return Err(AcpError::Validation(
202 "Envelope sender is required".to_string(),
203 ));
204 }
205 if self.recipients.is_empty() {
206 return Err(AcpError::Validation(
207 "Envelope recipients must not be empty".to_string(),
208 ));
209 }
210 let ts = chrono::DateTime::parse_from_rfc3339(&self.timestamp)
211 .map_err(|e| AcpError::Validation(format!("Invalid timestamp: {e}")))?;
212 let exp = chrono::DateTime::parse_from_rfc3339(&self.expires_at)
213 .map_err(|e| AcpError::Validation(format!("Invalid expires_at: {e}")))?;
214 if exp <= ts {
215 return Err(AcpError::Validation(
216 "Envelope expires_at must be after timestamp".to_string(),
217 ));
218 }
219 Ok(())
220 }
221
222 pub fn is_expired(&self) -> bool {
223 chrono::DateTime::parse_from_rfc3339(&self.expires_at)
224 .map(|exp| exp <= Utc::now())
225 .unwrap_or(true)
226 }
227
228 pub fn to_map(&self) -> AcpResult<JsonMap> {
229 json_support::to_map(self)
230 }
231}
232
233impl ProtectedPayload {
234 pub fn to_signable_value(&self) -> Value {
235 let mut keys = self.wrapped_content_keys.clone();
236 keys.sort_by(|a, b| a.recipient.cmp(&b.recipient));
237 json!({
238 "nonce": self.nonce,
239 "ciphertext": self.ciphertext,
240 "wrapped_content_keys": keys,
241 "payload_hash": self.payload_hash,
242 "signature_kid": self.signature_kid,
243 })
244 }
245}
246
247impl AcpMessage {
248 pub fn to_map(&self) -> AcpResult<JsonMap> {
249 json_support::to_map(self)
250 }
251
252 pub fn from_map(value: &JsonMap) -> AcpResult<Self> {
253 serde_json::from_value(Value::Object(value.clone())).map_err(AcpError::from)
254 }
255
256 pub fn to_json(&self) -> AcpResult<String> {
257 let value = serde_json::to_value(self)?;
258 json_support::canonical_json_string(&value)
259 }
260}
261
262impl SendResult {
263 pub fn to_map(&self) -> AcpResult<JsonMap> {
264 json_support::to_map(self)
265 }
266}
267
268pub fn build_ack_payload(
269 received_message_id: impl Into<String>,
270 status: impl Into<String>,
271) -> JsonMap {
272 let mut payload = JsonMap::new();
273 payload.insert("status".to_string(), Value::String(status.into()));
274 payload.insert(
275 "received_message_id".to_string(),
276 Value::String(received_message_id.into()),
277 );
278 payload
279}
280
281pub fn build_fail_payload(
282 reason_code: impl Into<String>,
283 detail: impl Into<String>,
284 retriable: bool,
285) -> JsonMap {
286 let mut payload = JsonMap::new();
287 payload.insert("reason_code".to_string(), Value::String(reason_code.into()));
288 payload.insert("detail".to_string(), Value::String(detail.into()));
289 payload.insert("retriable".to_string(), Value::Bool(retriable));
290 payload
291}