firecracker_rs_sdk/events/
mod.rs

1use std::any::TypeId;
2
3use serde::{de::DeserializeOwned, Serialize};
4
5use crate::{Error, Result};
6
7const HTTP_VERSION: &'static str = "HTTP/1.0";
8
9/// Trait for encoding a struct into an HTTP request.
10pub trait RequestTrait {
11    /// The type of the payload to be serialized.
12    type Payload: Serialize + 'static;
13
14    /// Encodes the payload into an HTTP request.
15    fn encode(&self) -> Result<Vec<u8>> {
16        // method uri version
17        // "GET /version HTTP/1.0\r\n\r\n";
18        let mut request = format!("{} {} {}\r\n", self.method(), self.path(), HTTP_VERSION);
19
20        let request = if TypeId::of::<Self::Payload>() == TypeId::of::<Empty>() {
21            request.push_str("\r\n");
22            request.as_bytes().to_vec()
23        } else {
24            let payload = self.payload();
25            let mut payload = serde_json::to_vec(&payload)
26                .map_err(|e| Error::Event(format!("serde_json encode: {e}")))?;
27            // add `Content-Length` header
28            request.push_str(&format!("Content-Length: {}\r\n", payload.len()));
29            // empty line splitting headers and body
30            request.push_str("\r\n");
31            // add body
32            let mut request = request.as_bytes().to_vec();
33            request.append(&mut payload);
34            request
35        };
36
37        Ok(request)
38    }
39
40    /// Returns the HTTP method (e.g., "PATCH", "GET").
41    fn method(&self) -> &'static str;
42
43    /// Returns the endpoint path (e.g., "/balloon").
44    fn path(&self) -> String;
45
46    /// Returns the payload to be serialized.
47    fn payload(&self) -> &Self::Payload;
48}
49
50/// Trait for decoding an HTTP response into a struct.
51pub trait ResponseTrait {
52    /// The type of the payload to be deserialized.
53    type Payload: DeserializeOwned + 'static;
54
55    /// Get the HTTP response status code
56    fn status_code(response: &Vec<u8>) -> Result<u16> {
57        let mut headers = [httparse::EMPTY_HEADER; 64];
58        let mut res = httparse::Response::new(&mut headers);
59        let body_start = res.parse(&response).unwrap();
60        if body_start.is_partial() {
61            return Err(Error::Event("Incomplete response".into()));
62        }
63        res.code
64            .ok_or_else(|| Error::Event("Bad HTTP response".into()))
65    }
66
67    /// Decodes the HTTP response into a payload.
68    fn decode(response: &Vec<u8>) -> Result<Self::Payload> {
69        let mut headers = [httparse::EMPTY_HEADER; 64];
70        let mut res = httparse::Response::new(&mut headers);
71
72        let body_start = res.parse(&response).unwrap();
73        if body_start.is_partial() {
74            return Err(Error::Event("Incomplete response".into()));
75        }
76        let body_start = body_start.unwrap(); // unwrap safe
77
78        let content_length = res
79            .headers
80            .iter()
81            .find(|h| h.name.to_lowercase() == "content-length")
82            .and_then(|h| {
83                Some(
84                    std::str::from_utf8(h.value)
85                        .unwrap()
86                        .parse::<usize>()
87                        .unwrap(),
88                )
89            });
90
91        match content_length {
92            Some(content_length) => {
93                let body = &response[body_start..(body_start + content_length)];
94                let payload: Self::Payload = serde_json::from_slice(body)
95                    .map_err(|e| Error::Event(format!("serde_json decode: {e}")))?;
96                Ok(payload)
97            }
98            None if TypeId::of::<Self::Payload>() == TypeId::of::<Empty>() => {
99                // just no payload, fine
100                // FIXME: ugly to use "null", could there be prettier solution?
101                let payload: Self::Payload = serde_json::from_str("null")
102                    .map_err(|e| Error::Event(format!("serde_json decode: {e}")))?;
103                Ok(payload)
104            }
105            _ => Err(Error::Event("Bad HTTP response".into())),
106        }
107    }
108}
109
110pub trait EventTrait: RequestTrait + ResponseTrait {}
111
112macro_rules! impl_event_traits {
113    // Other conditions
114    ($struct_name:ident, $method:expr, $path:expr, $req_payload:ty, $res_payload:ty) => {
115        pub struct $struct_name<'a>(pub &'a $req_payload);
116
117        impl<'a> RequestTrait for $struct_name<'a> {
118            type Payload = $req_payload;
119
120            fn method(&self) -> &'static str {
121                $method
122            }
123
124            fn path(&self) -> String {
125                $path.into()
126            }
127
128            fn payload(&self) -> &Self::Payload {
129                &self.0
130            }
131        }
132
133        impl<'a> ResponseTrait for $struct_name<'a> {
134            type Payload = $res_payload;
135        }
136
137        impl<'a> EventTrait for $struct_name<'a> {}
138
139        paste::paste! {
140            pub struct [<$struct_name Owned>](
141                pub $req_payload
142                // field!($req_payload)
143            );
144
145            impl RequestTrait for [<$struct_name Owned>] {
146                type Payload = $req_payload;
147
148                fn method(&self) -> &'static str {
149                    $method
150                }
151
152                fn path(&self) -> String {
153                    $path.into()
154                }
155
156                fn payload(&self) -> &Self::Payload {
157                    &self.0
158                }
159            }
160
161            impl ResponseTrait for [<$struct_name Owned>] {
162                type Payload = $res_payload;
163            }
164
165            impl EventTrait for [<$struct_name Owned>] {}
166        }
167    };
168
169    ($struct_name:ident, $method:expr, $path:expr, $id:ident, $req_payload:ty, $res_payload:ty) => {
170        pub struct $struct_name<'a>(pub &'a $req_payload);
171
172        impl<'a> RequestTrait for $struct_name<'a> {
173            type Payload = $req_payload;
174
175            fn method(&self) -> &'static str {
176                $method
177            }
178
179            fn path(&self) -> String {
180                format!("{}/{}", $path, &self.0.$id)
181            }
182
183            fn payload(&self) -> &Self::Payload {
184                &self.0
185            }
186        }
187
188        impl<'a> ResponseTrait for $struct_name<'a> {
189            type Payload = $res_payload;
190        }
191
192        impl<'a> EventTrait for $struct_name<'a> {}
193
194        paste::paste! {
195            pub struct [<$struct_name Owned>](pub $req_payload);
196
197            impl RequestTrait for [<$struct_name Owned>] {
198                type Payload = $req_payload;
199
200                fn method(&self) -> &'static str {
201                    $method
202                }
203
204                fn path(&self) -> String {
205                    format!("{}/{}", $path, &self.0.$id)
206                }
207
208                fn payload(&self) -> &Self::Payload {
209                    &self.0
210                }
211            }
212
213            impl ResponseTrait for [<$struct_name Owned>] {
214                type Payload = $res_payload;
215            }
216
217            impl EventTrait for [<$struct_name Owned>] {}
218        }
219    };
220}
221
222use crate::models::*;
223const GET: &'static str = "GET";
224const PUT: &'static str = "PUT";
225const PATCH: &'static str = "PATCH";
226
227impl_event_traits!(DescribeInstance, GET, "/", Empty, InstanceInfo);
228impl_event_traits!(CreateSyncAction, PUT, "/actions", InstanceActionInfo, Empty);
229impl_event_traits!(DescribeBalloonConfig, GET, "/balloon", Empty, Balloon);
230impl_event_traits!(PutBalloon, PUT, "/balloon", Balloon, Empty);
231impl_event_traits!(PatchBalloon, PATCH, "/balloon", BalloonUpdate, Empty);
232impl_event_traits!(
233    DescribeBalloonStats,
234    GET,
235    "/balloon/statistics",
236    Empty,
237    BalloonStats
238);
239impl_event_traits!(
240    PatchBalloonStatsInterval,
241    PATCH,
242    "/balloon/statistics",
243    BalloonStatsUpdate,
244    Empty
245);
246impl_event_traits!(PutGuestBootSource, PUT, "/boot-source", BootSource, Empty);
247impl_event_traits!(PutCpuConfiguration, PUT, "/cpu-config", CPUConfig, Empty);
248impl_event_traits!(PutGuestDriveByID, PUT, "/drives", drive_id, Drive, Empty);
249impl_event_traits!(
250    PatchGuestDriveByID,
251    PATCH,
252    "/drives",
253    drive_id,
254    PartialDrive,
255    Empty
256);
257impl_event_traits!(PutLogger, PUT, "/logger", Logger, Empty);
258impl_event_traits!(
259    GetMachineConfiguration,
260    GET,
261    "/machine-config",
262    Empty,
263    MachineConfiguration
264);
265impl_event_traits!(
266    PutMachineConfiguration,
267    PUT,
268    "/machine-config",
269    MachineConfiguration,
270    Empty
271);
272impl_event_traits!(
273    PatchMachineConfiguration,
274    PATCH,
275    "/machine-config",
276    MachineConfiguration,
277    Empty
278);
279impl_event_traits!(PutMetrics, PUT, "/metrics", Metrics, Empty);
280impl_event_traits!(PutMmds, PUT, "/mmds", MmdsContentsObject, Empty);
281impl_event_traits!(PatchMmds, PATCH, "/mmds", MmdsContentsObject, Empty);
282impl_event_traits!(GetMmds, GET, "/mmds", Empty, MmdsContentsObject);
283impl_event_traits!(PutMmdsConfig, PUT, "/mmds/config", MmdsConfig, Empty);
284impl_event_traits!(PutEntropyDevice, PUT, "/entropy", EntropyDevice, Empty);
285impl_event_traits!(
286    PutGuestNetworkInterfaceByID,
287    PUT,
288    "/network-interfaces",
289    iface_id,
290    NetworkInterface,
291    Empty
292);
293impl_event_traits!(
294    PatchGuestNetworkInterfaceByID,
295    PATCH,
296    "/network-interfaces",
297    iface_id,
298    PartialNetworkInterface,
299    Empty
300);
301impl_event_traits!(
302    CreateSnapshot,
303    PUT,
304    "/snapshot/create",
305    SnapshotCreateParams,
306    Empty
307);
308impl_event_traits!(
309    LoadSnapshot,
310    PUT,
311    "/snapshot/load",
312    SnapshotLoadParams,
313    Empty
314);
315impl_event_traits!(
316    GetFirecrackerVersion,
317    GET,
318    "/version",
319    Empty,
320    FirecrackerVersion
321);
322impl_event_traits!(PatchVm, PATCH, "/vm", Vm, Empty);
323impl_event_traits!(
324    GetExportVmConfig,
325    GET,
326    "/vm/config",
327    Empty,
328    FullVmConfiguration
329);
330impl_event_traits!(PutGuestVsock, PUT, "/vsock", Vsock, Empty);