1use serde::{Deserialize, Serialize, Serializer};
2use std::collections::{HashMap, HashSet};
3
4pub type Name = crate::common::name::Name;
6pub type Container = crate::common::container::Container;
8pub type ExitCode = i32;
10pub type Manifest = crate::npk::manifest::Manifest;
12pub type NonNulString = crate::common::non_nul_string::NonNulString;
14pub type Pid = u32;
16pub type RepositoryId = String;
18pub type Signal = u32;
20pub type Version = crate::common::version::Version;
22pub type ContainerStats = HashMap<String, serde_json::Value>;
24
25#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
27#[allow(missing_docs)]
28#[serde(untagged)]
29pub enum Message {
30 Connect { connect: Connect },
31 ConnectAck { connect_ack: ConnectAck },
32 ConnectNack { connect_nack: ConnectNack },
33 Request { request: Request },
34 Response { response: Response },
35 Notification { notification: Notification },
36}
37
38#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
40#[serde(rename_all = "snake_case")]
41#[allow(missing_docs)]
42pub enum Notification {
43 CGroup(Container, CgroupNotification),
44 Exit(Container, ExitStatus),
45 Install(Container),
46 Shutdown,
47 Started(Container),
48 Uninstall(Container),
49}
50
51#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case")]
54#[allow(missing_docs)]
55pub enum CgroupNotification {
56 Memory(MemoryNotification),
57}
58
59#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62#[allow(missing_docs)]
63pub struct MemoryNotification {
64 pub low: Option<u64>,
65 pub high: Option<u64>,
66 pub max: Option<u64>,
67 pub oom: Option<u64>,
68 pub oom_kill: Option<u64>,
69}
70
71#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
73#[serde(rename_all = "snake_case")]
74#[allow(missing_docs)]
75pub struct Connect {
76 pub version: Version,
78 pub subscribe_notifications: bool,
80}
81
82#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
84#[serde(rename_all = "snake_case")]
85#[allow(missing_docs)]
86pub struct ConnectAck;
87
88#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91#[allow(missing_docs)]
92pub enum ConnectNack {
93 InvalidProtocolVersion { version: Version },
94 PermissionDenied,
95}
96
97#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100#[allow(missing_docs)]
101pub enum Request {
102 Inspect {
103 container: Container,
104 },
105 Ident,
106 Install {
107 repository: RepositoryId,
108 size: u64,
109 },
110 Kill {
111 container: Container,
112 signal: i32,
113 },
114 List,
115 Mount {
116 containers: Vec<Container>,
117 },
118 Repositories,
119 Shutdown,
120 Start {
121 container: Container,
122 init: Option<NonNulString>,
123 arguments: Vec<NonNulString>,
124 environment: HashMap<NonNulString, NonNulString>,
125 },
126 TokenCreate {
127 target: Name,
128 #[serde(with = "base64")]
129 shared: Vec<u8>,
130 },
131 TokenVerify {
132 token: Token,
133 user: Name,
134 #[serde(with = "base64")]
135 shared: Vec<u8>,
136 },
137 Umount {
138 containers: Vec<Container>,
139 },
140 Uninstall {
141 container: Container,
142 wipe: bool,
143 },
144}
145
146#[derive(Clone, Eq, PartialEq, Debug)]
148pub struct Token(Vec<u8>);
149
150impl AsRef<[u8]> for Token {
151 fn as_ref(&self) -> &[u8] {
152 &self.0
153 }
154}
155
156impl From<Vec<u8>> for Token {
157 fn from(value: Vec<u8>) -> Self {
158 Token(value)
159 }
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
164pub enum VerificationResult {
165 Ok,
167 Invalid,
169 Expired,
171 Future,
173}
174
175#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
177#[serde(rename_all = "snake_case")]
178pub struct ContainerData {
179 pub manifest: Manifest,
181 pub repository: RepositoryId,
183 pub mounted: bool,
185 pub process: Option<Process>,
187}
188
189#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
191#[serde(rename_all = "snake_case")]
192pub struct Process {
193 pub pid: Pid,
195 pub uptime: u64,
197 pub statistics: ContainerStats,
199}
200
201#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204#[allow(missing_docs)]
205pub enum MountResult {
206 Ok { container: Container },
207 Error { container: Container, error: Error },
208}
209
210#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
212#[serde(rename_all = "snake_case")]
213#[allow(missing_docs)]
214pub enum UmountResult {
215 Ok { container: Container },
216 Error { container: Container, error: Error },
217}
218
219#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
221#[serde(rename_all = "snake_case")]
222#[allow(missing_docs)]
223pub enum StartResult {
224 Ok { container: Container },
225 Error { container: Container, error: Error },
226}
227
228#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231#[allow(missing_docs)]
232pub enum KillResult {
233 Ok { container: Container },
234 Error { container: Container, error: Error },
235}
236
237#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
239#[serde(rename_all = "snake_case")]
240#[allow(missing_docs)]
241pub enum InstallResult {
242 Ok { container: Container },
243 Error { error: Error },
244}
245
246#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
248#[serde(rename_all = "snake_case")]
249#[allow(missing_docs)]
250pub enum UninstallResult {
251 Ok { container: Container },
252 Error { container: Container, error: Error },
253}
254
255#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
257#[serde(rename_all = "snake_case")]
258#[allow(missing_docs)]
259pub enum InspectResult {
260 Ok {
261 container: Container,
262 data: Box<ContainerData>,
263 },
264 Error {
265 container: Container,
266 error: Error,
267 },
268}
269
270#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
272#[serde(rename_all = "snake_case")]
273#[allow(missing_docs)]
274pub enum Response {
275 Ident(Container),
276 Inspect(InspectResult),
277 Install(InstallResult),
278 Kill(KillResult),
279 List(Vec<Container>),
280 Mount(Vec<MountResult>),
281 PermissionDenied(Request),
282 Repositories(HashSet<RepositoryId>),
283 Shutdown,
284 Start(StartResult),
285 Token(Token),
286 TokenVerification(VerificationResult),
287 Umount(Vec<UmountResult>),
288 Uninstall(UninstallResult),
289}
290
291#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
293#[serde(rename_all = "snake_case")]
294pub enum ExitStatus {
295 Exit {
297 code: ExitCode,
299 },
300 Signalled {
302 signal: Signal,
304 },
305}
306
307impl ExitStatus {
308 pub const SUCCESS: ExitCode = 0;
310
311 pub fn success(&self) -> bool {
314 matches!(self, ExitStatus::Exit { code } if *code == Self::SUCCESS)
315 }
316
317 pub fn code(&self) -> Option<ExitCode> {
319 match self {
320 ExitStatus::Exit { code } => Some(*code),
321 ExitStatus::Signalled { .. } => None,
322 }
323 }
324}
325
326impl std::fmt::Display for ExitStatus {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 match self {
329 ExitStatus::Exit { code } => write!(f, "Exit({code})"),
330 ExitStatus::Signalled { signal } => write!(f, "Signalled({signal})"),
331 }
332 }
333}
334
335#[derive(Clone, Eq, thiserror::Error, PartialEq, Debug, Serialize, Deserialize)]
337#[serde(rename_all = "snake_case")]
338#[allow(missing_docs)]
339pub enum Error {
340 Configuration {
341 context: String,
342 },
343 DuplicateContainer {
344 container: Container,
345 },
346 InvalidContainer {
347 container: Container,
348 },
349 InvalidArguments {
350 cause: String,
351 },
352 MountBusy {
353 container: Container,
354 },
355 UmountBusy {
356 container: Container,
357 },
358 StartContainerStarted {
359 container: Container,
360 },
361 StartContainerResource {
362 container: Container,
363 },
364 StartContainerMissingResource {
365 container: Container,
366 resource: Name,
367 version: String,
368 },
369 StartContainerFailed {
370 container: Container,
371 error: String,
372 },
373 StopContainerNotStarted {
374 container: Container,
375 },
376 InvalidRepository {
377 repository: RepositoryId,
378 },
379 InstallDuplicate {
380 container: Container,
381 },
382 CriticalContainer {
383 container: Container,
384 status: ExitStatus,
385 },
386 Unexpected {
387 error: String,
388 },
389}
390
391impl std::fmt::Display for Error {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 write!(f, "{:?}", self)
394 }
395}
396
397impl Serialize for Token {
398 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
399 if self.0.len() == 40 {
400 base64::serialize(&self.0, serializer)
401 } else {
402 Err(serde::ser::Error::custom("invalid length"))
403 }
404 }
405}
406
407impl<'de> Deserialize<'de> for Token {
408 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
409 let token = base64::deserialize(deserializer)?;
410 if token.len() == 40 {
411 Ok(Token(token))
412 } else {
413 Err(serde::de::Error::custom("invalid length"))
414 }
415 }
416}
417
418mod base64 {
419 use base64::{engine::general_purpose::STANDARD as Base64, Engine as _};
420 use serde::{Deserialize, Deserializer, Serialize, Serializer};
421
422 pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
423 let base64 = Base64.encode(v);
424 String::serialize(&base64, s)
425 }
426
427 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
428 let base64 = String::deserialize(d)?;
429 Base64
430 .decode(base64.as_bytes())
431 .map_err(serde::de::Error::custom)
432 }
433}