pyrinas_cli/
ota.rs

1use chrono::{Duration, Local, Utc};
2
3// Pyrinas
4use pyrinas_shared::ota::v2::{OTAImageData, OTAImageType, OTAPackage, OtaUpdate};
5use pyrinas_shared::{
6    ManagementData, ManagmentDataType, OtaGroupListResponse, OtaImageListResponse,
7};
8
9// Cbor
10use serde_cbor;
11
12// Std lib
13use std::fs::File;
14use std::io::{self, prelude::*};
15use std::net::TcpStream;
16
17// Websocket
18use tungstenite::{protocol::WebSocket, stream::MaybeTlsStream, Message};
19
20// Error handling
21use thiserror::Error;
22
23use crate::{git, OtaLink, OtaSubCommand};
24
25#[derive(Debug, Error)]
26pub enum Error {
27    #[error("file error: {source}")]
28    FileError {
29        #[from]
30        source: io::Error,
31    },
32
33    /// Serde CBOR error
34    #[error("serde_cbor error: {source}")]
35    CborError {
36        #[from]
37        source: serde_cbor::Error,
38    },
39
40    /// Websocket error
41    #[error("websocket error: {source}")]
42    WebsocketError {
43        #[from]
44        source: tungstenite::Error,
45    },
46
47    /// Error to indicate repo is dirty
48    #[error("repository is dirty. Run --force to override")]
49    DirtyError,
50
51    /// Error for git related commands
52    #[error("{source}")]
53    GitError {
54        #[from]
55        source: git::GitError,
56    },
57}
58
59/// Functon for processing all incoming OTA commands.
60pub fn process(
61    socket: &mut WebSocket<MaybeTlsStream<TcpStream>>,
62    cmd: &OtaSubCommand,
63) -> Result<(), Error> {
64    match cmd {
65        OtaSubCommand::Add(a) => {
66            let image_id = crate::ota::add_ota(socket, a.force)?;
67
68            println!("{} image successfully uploaded!", &image_id);
69
70            // Do association
71            match &a.device_id {
72                Some(device_id) => {
73                    let a = OtaLink {
74                        device_id: Some(device_id.clone()),
75                        group_id: Some(device_id.to_string()),
76                        image_id: Some(image_id),
77                        ota_version: a.ota_version,
78                    };
79
80                    crate::ota::link(socket, &a)?;
81
82                    println!("OTA Linked! {:?}", &a);
83                }
84                None => (),
85            };
86        }
87        OtaSubCommand::Remove(r) => {
88            crate::ota::remove_ota(socket, &r.image_id)?;
89
90            println!("{} successfully removed!", &r.image_id);
91        }
92        OtaSubCommand::Unlink(a) => {
93            crate::ota::unlink(socket, a)?;
94
95            println!("OTA Unlinked! {:?}", a);
96        }
97        OtaSubCommand::Link(a) => {
98            crate::ota::link(socket, a)?;
99
100            println!("OTA Linked! {:?}", &a);
101        }
102        OtaSubCommand::ListGroups => {
103            crate::ota::get_ota_group_list(socket)?;
104
105            let start = Utc::now();
106
107            // Get message
108            loop {
109                if Utc::now() > start + Duration::seconds(10) {
110                    eprintln!("No response from server!");
111                    break;
112                }
113
114                match socket.read_message() {
115                    Ok(msg) => {
116                        let data = match msg {
117                            tungstenite::Message::Binary(b) => b,
118                            _ => {
119                                eprintln!("Unexpected WS message!");
120                                break;
121                            }
122                        };
123
124                        let list: OtaGroupListResponse = match serde_cbor::from_slice(&data) {
125                            Ok(m) => m,
126                            Err(e) => {
127                                eprintln!("Unable to get image list! Error: {}", e);
128                                break;
129                            }
130                        };
131
132                        for name in list.groups.iter() {
133                            // Print out the entry
134                            println!("{}", name);
135                        }
136
137                        break;
138                    }
139                    Err(_) => continue,
140                };
141            }
142        }
143        OtaSubCommand::ListImages => {
144            crate::ota::get_ota_image_list(socket)?;
145
146            let start = Utc::now();
147
148            // Get message
149            loop {
150                if Utc::now() > start + Duration::seconds(10) {
151                    eprintln!("No response from server!");
152                    break;
153                }
154
155                match socket.read_message() {
156                    Ok(msg) => {
157                        let data = match msg {
158                            tungstenite::Message::Binary(b) => b,
159                            _ => {
160                                eprintln!("Unexpected WS message!");
161                                break;
162                            }
163                        };
164
165                        let list: OtaImageListResponse = match serde_cbor::from_slice(&data) {
166                            Ok(m) => m,
167                            Err(e) => {
168                                eprintln!("Unable to get image list! Error: {}", e);
169                                break;
170                            }
171                        };
172
173                        for (name, package) in list.images.iter() {
174                            // Get the date
175                            let date = match package.date_added {
176                                Some(d) => d.with_timezone(&Local).to_string(),
177                                None => "".to_string(),
178                            };
179
180                            // Print out the entry
181                            println!("{} {}", name, date);
182                        }
183
184                        break;
185                    }
186                    Err(_) => continue,
187                };
188            }
189        }
190    };
191
192    Ok(())
193}
194
195/// Adds and OTA image from an included manifest file to the server
196pub fn add_ota(
197    stream: &mut WebSocket<MaybeTlsStream<TcpStream>>,
198    force: bool,
199) -> Result<String, Error> {
200    // Get the current version using 'git describe'
201    let ver = crate::git::get_git_describe()?;
202
203    // Then parse it to get OTAPackageVersion
204    let (package_version, dirty) = crate::git::get_ota_package_version(&ver)?;
205
206    // Force error
207    if dirty && !force {
208        return Err(Error::DirtyError);
209    }
210
211    // Path for ota
212    let path = "./build/zephyr/app_update.bin";
213
214    // Read image in as data
215    let mut buf: Vec<u8> = Vec::new();
216    let mut file = File::open(&path)?;
217    let size = file.read_to_end(&mut buf)?;
218
219    println!("Reading {} bytes from firmware update binary.", size);
220
221    // Data structure (from pyrinas_lib_shared)
222    let new = OtaUpdate {
223        uid: None,
224        package: Some(OTAPackage {
225            version: package_version.clone(),
226            files: Vec::new(),
227            date_added: Some(Utc::now()),
228        }),
229        images: Some(
230            [OTAImageData {
231                data: buf,
232                image_type: OTAImageType::Primary,
233            }]
234            .to_vec(),
235        ),
236    };
237
238    // Serialize to cbor
239    let data = serde_cbor::to_vec(&new)?;
240
241    // Then configure the outer data
242    let msg = ManagementData {
243        cmd: ManagmentDataType::AddOta,
244        target: None,
245        msg: data,
246    };
247
248    // If second encode looks good send it off
249    let data = serde_cbor::to_vec(&msg)?;
250
251    // Send over socket
252    stream.write_message(Message::binary(data))?;
253
254    Ok(package_version.to_string())
255}
256
257pub fn unlink(
258    stream: &mut WebSocket<MaybeTlsStream<TcpStream>>,
259    link: &OtaLink,
260) -> Result<(), Error> {
261    // Then configure the outer data
262    let msg = ManagementData {
263        cmd: ManagmentDataType::UnlinkOta,
264        target: None,
265        msg: serde_cbor::to_vec(link)?,
266    };
267
268    // If second encode looks good send it off
269    let data = serde_cbor::to_vec(&msg)?;
270
271    // Send over socket
272    stream.write_message(Message::binary(data))?;
273
274    Ok(())
275}
276
277pub fn link(
278    stream: &mut WebSocket<MaybeTlsStream<TcpStream>>,
279    link: &OtaLink,
280) -> Result<(), Error> {
281    // Then configure the outer data
282    let msg = ManagementData {
283        cmd: ManagmentDataType::LinkOta,
284        target: None,
285        msg: serde_cbor::to_vec(link)?,
286    };
287
288    // If second encode looks good send it off
289    let data = serde_cbor::to_vec(&msg)?;
290
291    // Send over socket
292    stream.write_message(Message::binary(data))?;
293
294    Ok(())
295}
296
297/// Adds and OTA image from an included manifest file to the server
298pub fn remove_ota(
299    stream: &mut WebSocket<MaybeTlsStream<TcpStream>>,
300    image_id: &str,
301) -> Result<(), Error> {
302    // Then configure the outer data
303    let msg = ManagementData {
304        cmd: ManagmentDataType::RemoveOta,
305        target: None,
306        msg: image_id.as_bytes().to_vec(),
307    };
308
309    // If second encode looks good send it off
310    let data = serde_cbor::to_vec(&msg)?;
311
312    // Send over socket
313    stream.write_message(Message::binary(data))?;
314
315    Ok(())
316}
317
318pub fn get_ota_group_list(stream: &mut WebSocket<MaybeTlsStream<TcpStream>>) -> Result<(), Error> {
319    // Then configure the outer data
320    let msg = ManagementData {
321        cmd: ManagmentDataType::GetGroupList,
322        target: None,
323        msg: [].to_vec(),
324    };
325
326    // If second encode looks good send it off
327    let data = serde_cbor::to_vec(&msg)?;
328
329    // Send over socket
330    stream.write_message(Message::binary(data))?;
331
332    Ok(())
333}
334
335pub fn get_ota_image_list(stream: &mut WebSocket<MaybeTlsStream<TcpStream>>) -> Result<(), Error> {
336    // Then configure the outer data
337    let msg = ManagementData {
338        cmd: ManagmentDataType::GetImageList,
339        target: None,
340        msg: [].to_vec(),
341    };
342
343    // If second encode looks good send it off
344    let data = serde_cbor::to_vec(&msg)?;
345
346    // Send over socket
347    stream.write_message(Message::binary(data))?;
348
349    Ok(())
350}