1pub mod certs;
2pub mod config;
3pub mod device;
4pub mod git;
5pub mod ota;
6
7use clap::Parser;
8use pyrinas_shared::{ota::OTAPackageVersion, OtaLink};
9use serde::{Deserialize, Serialize};
10use std::{net::TcpStream, num};
11
12use thiserror::Error;
14
15use tungstenite::{http, http::Request, protocol::WebSocket, stream::MaybeTlsStream};
17
18#[derive(Debug, Error)]
19pub enum Error {
20 #[error("{source}")]
21 Error {
22 #[from]
23 source: config::Error,
24 },
25
26 #[error("http error: {source}")]
27 HttpError {
28 #[from]
29 source: http::Error,
30 },
31
32 #[error("websocket handshake error {source}")]
33 WebsocketError {
34 #[from]
35 source: tungstenite::Error,
36 },
37
38 #[error("semver error: {source}")]
39 SemVerError {
40 #[from]
41 source: semver::Error,
42 },
43
44 #[error("parse error: {source}")]
45 ParseError {
46 #[from]
47 source: num::ParseIntError,
48 },
49
50 #[error("err: {0}")]
51 CustomError(String),
52
53 #[error("ota error: {source}")]
54 OtaError {
55 #[from]
56 source: ota::Error,
57 },
58
59 #[error("{source}")]
60 CertsError {
61 #[from]
62 source: certs::Error,
63 },
64}
65
66#[derive(Parser, Debug)]
68#[clap(version)]
69pub struct OtaCmd {
70 #[clap(subcommand)]
71 pub subcmd: OtaSubCommand,
72}
73
74#[derive(Parser, Debug)]
76#[clap(version)]
77pub struct CertCmd {
78 #[clap(subcommand)]
79 pub subcmd: CertSubcommand,
80}
81
82#[derive(Parser, Debug)]
83#[clap(version)]
84pub enum CertSubcommand {
85 Ca,
87 Server,
89 Device(CertDevice),
91}
92
93#[derive(Parser, Debug)]
95#[clap(version)]
96pub struct CertDevice {
97 id: Option<String>,
99 #[clap(long, short)]
101 provision: bool,
102 #[clap(long, default_value = certs::DEFAULT_MAC_PORT )]
104 port: String,
105 #[clap(long, short)]
107 tag: Option<u32>,
108}
109
110#[derive(Parser, Debug)]
111#[clap(version)]
112pub enum OtaSubCommand {
113 Add(OtaAdd),
115 Link(OtaLink),
117 Unlink(OtaLink),
119 Remove(OtaRemove),
121 ListGroups,
123 ListImages,
125}
126
127#[derive(Parser, Debug)]
129#[clap(version)]
130pub struct OtaAdd {
131 #[clap(long, short)]
133 pub force: bool,
134 #[clap(long, short)]
137 pub device_id: Option<String>,
138 #[clap(long, default_value = pyrinas_shared::DEFAULT_OTA_VERSION)]
140 pub ota_version: u8,
141}
142
143#[derive(Parser, Debug)]
145#[clap(version)]
146pub struct OtaRemove {
147 pub image_id: String,
149}
150
151#[derive(Debug, Serialize, Deserialize, Default)]
152pub struct CertConfig {
153 pub domain: String,
155 pub organization: String,
157 pub country: String,
159 pub pfx_pass: String,
161}
162
163#[derive(Debug, Serialize, Deserialize, Default)]
164pub struct CertEntry {
165 pub tag: u32,
167 pub ca_cert: Option<String>,
169 pub private_key: Option<String>,
171 pub pub_key: Option<String>,
173}
174
175#[derive(Debug, Serialize, Deserialize, Default)]
177pub struct Config {
178 pub url: String,
181 pub secure: bool,
183 pub authkey: String,
186 pub cert: CertConfig,
188 pub alts: Option<Vec<CertEntry>>,
190}
191
192#[derive(Parser, Debug, Serialize, Deserialize)]
194#[clap(version)]
195pub struct ConfigCmd {
196 #[clap(subcommand)]
197 pub subcmd: ConfigSubCommand,
198}
199
200#[derive(Parser, Debug, Serialize, Deserialize)]
201#[clap(version)]
202pub enum ConfigSubCommand {
203 Show(Show),
204 Init,
205}
206
207#[derive(Parser, Debug, Serialize, Deserialize)]
209#[clap(version)]
210pub struct Show {}
211
212#[derive(Debug, Serialize, Deserialize, Clone)]
214pub struct OTAManifest {
215 pub version: OTAPackageVersion,
216 pub file: String,
217 pub force: bool,
218}
219
220pub fn get_socket(config: &Config) -> Result<WebSocket<MaybeTlsStream<TcpStream>>, Error> {
241 if !config.secure {
242 println!("WARNING! Not using secure web socket connection!");
243 }
244
245 let full_uri = format!(
247 "ws{}://{}/socket",
248 match config.secure {
249 true => "s",
250 false => "",
251 },
252 config.url
253 );
254
255 let req = Request::builder()
257 .uri(full_uri)
258 .header("ApiKey", config.authkey.clone())
259 .body(())?;
260
261 let (socket, _response) = tungstenite::connect(req)?;
264
265 Ok(socket)
267}
268
269#[cfg(test)]
270mod tests {
271
272 use super::*;
273 use std::sync::Once;
274
275 static INIT: Once = Once::new();
276
277 fn setup() {
279 INIT.call_once(|| env_logger::init());
280 }
281
282 #[test]
283 fn get_ota_package_version_success_with_dirty() {
284 setup();
286
287 let ver = "0.2.1-19-g09db6ef-dirty";
288
289 let res = git::get_ota_package_version(ver);
290
291 assert!(res.is_ok());
293
294 let (package_ver, dirty) = res.unwrap();
295
296 assert!(dirty);
298
299 assert_eq!(
301 package_ver,
302 OTAPackageVersion {
303 major: 0,
304 minor: 2,
305 patch: 1,
306 commit: 19,
307 hash: [
308 'g' as u8, '0' as u8, '9' as u8, 'd' as u8, 'b' as u8, '6' as u8, 'e' as u8,
309 'f' as u8
310 ]
311 }
312 )
313 }
314
315 #[test]
316 fn get_ota_package_version_success_clean() {
317 setup();
319
320 let ver = "0.2.1-19-g09db6ef";
321
322 let res = git::get_ota_package_version(ver);
323
324 assert!(res.is_ok());
326
327 let (package_ver, dirty) = res.unwrap();
328
329 assert!(!dirty);
331
332 assert_eq!(
334 package_ver,
335 OTAPackageVersion {
336 major: 0,
337 minor: 2,
338 patch: 1,
339 commit: 19,
340 hash: [
341 'g' as u8, '0' as u8, '9' as u8, 'd' as u8, 'b' as u8, '6' as u8, 'e' as u8,
342 'f' as u8
343 ]
344 }
345 )
346 }
347
348 #[test]
349 fn get_ota_package_version_failure_dirty() {
350 setup();
352
353 let ver = "0.2.1-g09db6ef-dirty";
354
355 let res = git::get_ota_package_version(ver);
356
357 assert!(res.is_err());
359 }
360
361 #[allow(dead_code)]
363 fn get_git_describe_success() {
364 setup();
366
367 let res = git::get_git_describe();
368
369 assert!(res.is_ok());
371
372 log::info!("res: {}", res.unwrap());
373 }
374}