1#[cfg(test)]
10mod tests;
11
12use crate::protocol::Cohort;
13use serde::Deserialize;
14use serde_json::{Map, Value};
15
16#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
22pub struct Response {
23 #[serde(rename = "protocol")]
28 pub protocol_version: String,
29
30 pub server: Option<String>,
32
33 pub daystart: Option<DayStart>,
35
36 #[serde(rename = "app")]
40 pub apps: Vec<App>,
41}
42
43#[derive(Clone, Debug, Deserialize, PartialEq)]
44pub struct DayStart {
45 pub elapsed_days: Option<u32>,
48 pub elapsed_seconds: Option<u32>,
51}
52
53#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
54pub struct App {
55 #[serde(rename = "appid")]
56 pub id: String,
57
58 pub status: OmahaStatus,
60
61 #[serde(flatten)]
66 pub cohort: Cohort,
67
68 pub ping: Option<Ping>,
70
71 #[serde(rename = "updatecheck")]
73 pub update_check: Option<UpdateCheck>,
74
75 #[serde(rename = "event")]
77 pub events: Option<Vec<Event>>,
78
79 #[serde(flatten)]
81 pub extra_attributes: Map<String, Value>,
82}
83
84impl App {
85 pub fn get_manifest_version(&self) -> Option<String> {
86 self.update_check.as_ref().and_then(|update_check| {
87 update_check.manifest.as_ref().map(|manifest| manifest.version.clone())
88 })
89 }
90}
91
92#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
93#[serde(field_identifier, rename_all = "lowercase")]
94pub enum OmahaStatus {
95 #[default]
96 Ok,
97 Restricted,
100 NoUpdate,
102 Error(String),
103}
104
105#[derive(Clone, Debug, Deserialize, PartialEq)]
106pub struct Ping {
107 status: OmahaStatus,
109}
110
111#[derive(Clone, Debug, Deserialize, PartialEq)]
112pub struct Event {
113 pub status: OmahaStatus,
115}
116
117#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
118pub struct UpdateCheck {
119 pub status: OmahaStatus,
121 pub info: Option<String>,
123
124 pub urls: Option<URLs>,
126
127 pub manifest: Option<Manifest>,
129
130 #[serde(flatten)]
132 pub extra_attributes: Map<String, Value>,
133}
134
135impl UpdateCheck {
136 pub fn ok(urls: impl IntoIterator<Item = impl Into<String>>) -> Self {
137 UpdateCheck {
138 urls: Some(URLs::new(urls.into_iter().map(Into::into).collect())),
139 ..UpdateCheck::default()
140 }
141 }
142
143 pub fn no_update() -> Self {
144 UpdateCheck { status: OmahaStatus::NoUpdate, ..UpdateCheck::default() }
145 }
146
147 pub fn get_all_url_codebases(&self) -> impl Iterator<Item = &str> {
149 self.urls.iter().flat_map(|urls| &urls.url).map(|url| url.codebase.as_str())
150 }
151
152 pub fn get_all_packages(&self) -> impl Iterator<Item = &Package> {
154 self.manifest.iter().flat_map(|m| &m.packages.package)
155 }
156
157 pub fn get_all_full_urls(&self) -> impl Iterator<Item = String> + '_ {
159 self.get_all_url_codebases().flat_map(move |codebase| {
160 self.get_all_packages().map(move |package| format!("{}{}", codebase, package.name))
161 })
162 }
163}
164
165#[derive(Clone, Debug, Deserialize, PartialEq)]
167pub struct URLs {
168 pub url: Vec<URL>,
169}
170
171impl URLs {
172 pub fn new(urls: Vec<String>) -> Self {
173 URLs { url: urls.into_iter().map(|url| URL { codebase: url }).collect() }
174 }
175}
176
177#[derive(Clone, Debug, Deserialize, PartialEq)]
178pub struct URL {
179 pub codebase: String,
181}
182
183#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
184pub struct Manifest {
185 pub version: String,
186
187 pub actions: Actions,
188 pub packages: Packages,
189}
190
191#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
193pub struct Actions {
194 pub action: Vec<Action>,
195}
196
197#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
198pub struct Action {
199 pub event: Option<String>,
201
202 pub run: Option<String>,
204
205 #[serde(flatten)]
206 pub extra_attributes: Map<String, Value>,
207}
208
209#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
211pub struct Packages {
212 pub package: Vec<Package>,
213}
214
215impl Packages {
216 pub fn new(package: Vec<Package>) -> Self {
217 Self { package }
218 }
219}
220
221#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
222pub struct Package {
223 pub name: String,
225 pub required: bool,
226 pub size: Option<u64>,
227 pub hash: Option<String>,
229 pub hash_sha256: Option<String>,
231
232 #[serde(rename = "fp")]
234 pub fingerprint: String,
235
236 #[serde(flatten)]
237 pub extra_attributes: Map<String, Value>,
238}
239
240impl Package {
241 pub fn with_name(name: impl Into<String>) -> Self {
242 Self { name: name.into(), ..Self::default() }
243 }
244}
245
246pub fn parse_json_response(json: &[u8]) -> serde_json::Result<Response> {
248 #[derive(Deserialize)]
249 struct ResponseWrapper {
250 response: Response,
251 }
252
253 let wrapper: ResponseWrapper = parse_safe_json(json)?;
254 Ok(wrapper.response)
255}
256
257fn parse_safe_json<'a, T>(raw: &'a [u8]) -> serde_json::Result<T>
264where
265 T: Deserialize<'a>,
266{
267 let safety_prefix = b")]}'\n";
268 if raw.starts_with(safety_prefix) {
271 serde_json::from_slice(&raw[safety_prefix.len()..])
272 } else {
273 serde_json::from_slice(raw)
274 }
275}