omaha_client/protocol/request.rs
1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use crate::protocol::Cohort;
10use serde::{Serialize, Serializer};
11use serde_repr::Serialize_repr;
12use std::collections::HashMap;
13
14#[cfg(test)]
15mod tests;
16
17/// This is the key for the http request header that identifies the 'updater' that is sending a
18/// request.
19pub const HEADER_UPDATER_NAME: &str = "X-Goog-Update-Updater";
20
21/// This is the key for the http request header that identifies whether this is an interactive
22/// or a background update (see InstallSource).
23pub const HEADER_INTERACTIVITY: &str = "X-Goog-Update-Interactivity";
24
25/// This is the key for the http request header that identifies the app id(s) that are included in
26/// this request.
27pub const HEADER_APP_ID: &str = "X-Goog-Update-AppId";
28
29/// An Omaha protocol request.
30///
31/// This holds the data for constructing a request to the Omaha service.
32///
33/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#request
34#[derive(Debug, Default, Serialize)]
35pub struct Request {
36 /// The current Omaha protocol version (which this is meant to be used with, is 3.0. This
37 /// should always be set to "3.0".
38 ///
39 /// This is the 'protocol' attribute of the request object.
40 #[serde(rename = "protocol")]
41 pub protocol_version: String,
42
43 /// This is the string identifying the updater software itself (this client). e.g. "fuchsia"
44 pub updater: String,
45
46 /// The version of the updater itself (e.g. "Fuchsia/Rust-0.0.0.1"). This is the version of the
47 /// updater implemented using this Crate.
48 ///
49 /// This is the 'updaterversion' attribute of the request object.
50 #[serde(rename = "updaterversion")]
51 pub updater_version: String,
52
53 /// The install source trigger for this request.
54 #[serde(rename = "installsource")]
55 pub install_source: InstallSource,
56
57 /// The system update is always done by "the machine" aka system-level or administrator
58 /// privileges.
59 ///
60 /// This is the 'ismachine' attribute of the request object.
61 #[serde(rename = "ismachine")]
62 pub is_machine: bool,
63
64 /// The randomly generated GUID for a single Omaha request.
65 ///
66 /// This is the 'requestid' attribute of the request object.
67 #[serde(rename = "requestid")]
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub request_id: Option<GUID>,
70
71 /// The randomly generated GUID for all Omaha requests in an update session.
72 ///
73 /// This is the 'sessionid' attribute of the request object.
74 #[serde(rename = "sessionid")]
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub session_id: Option<GUID>,
77
78 /// Information about the device operating system.
79 ///
80 /// This is the 'os' child object of the request object.
81 pub os: OS,
82
83 /// The applications to update.
84 ///
85 /// These are the 'app' children objects of the request object
86 #[serde(rename = "app")]
87 pub apps: Vec<App>,
88}
89
90/// RequestWrapper is a serialization wrapper for a Request.
91///
92/// A Request object serializes into a value for an object,
93/// not an object that is '{"request": {....} }'.
94/// This wrapper provides the request wrapping that Omaha expects to see.
95#[derive(Debug, Default, Serialize)]
96pub struct RequestWrapper {
97 pub request: Request,
98}
99
100/// Enum of the possible reasons that this update request was initiated.
101#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
102#[serde(rename_all = "lowercase")]
103pub enum InstallSource {
104 /// This update check was triggered "on demand", by a user.
105 OnDemand,
106
107 /// This update check was triggered as part of a background task, unattended by a user.
108 #[default]
109 ScheduledTask,
110}
111
112/// Information about the platform / operating system.
113///
114/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#os
115#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
116pub struct OS {
117 /// The device platform (e.g. 'Fuchsia')
118 pub platform: String,
119
120 /// The version of the platform
121 pub version: String,
122
123 /// The patch level of the platform (e.g. "12345_arm64")
124 #[serde(rename = "sp")]
125 pub service_pack: String,
126
127 /// The platform architecture (e.g. "x86-64")
128 pub arch: String,
129}
130
131/// Information about an individual app that an update check is being performed for.
132///
133/// While unlikely, it's possible for a single request to have an update check, a ping, and for it
134/// to be reporting an event.
135///
136/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#app-request
137#[derive(Debug, Default, Clone, Serialize)]
138pub struct App {
139 /// This is the GUID or product ID that uniquely identifies the product to Omaha.
140 ///
141 /// This is the 'appid' attribute of the app object.
142 #[serde(rename = "appid")]
143 pub id: String,
144
145 /// The version of the product that's currently installed. This is in 'A.B.C.D' format.
146 ///
147 /// This is the version attribute of the app object.
148 pub version: String,
149
150 /// The fingerprint for the application.
151 ///
152 /// This is the fp attribute of the app object.
153 #[serde(rename = "fp")]
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub fingerprint: Option<String>,
156
157 /// This is the cohort id, as previously assigned by the Omaha service. This is a machine-
158 /// readable string, not meant for user display.
159 ///
160 /// This holds the following fields of the app object:
161 /// cohort
162 /// cohorthint
163 /// cohortname
164 #[serde(flatten)]
165 pub cohort: Option<Cohort>,
166
167 /// If present, this request is an update check.
168 #[serde(rename = "updatecheck")]
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub update_check: Option<UpdateCheck>,
171
172 /// These are events to report to Omaha.
173 #[serde(rename = "event")]
174 #[serde(skip_serializing_if = "Vec::is_empty")]
175 pub events: Vec<Event>,
176
177 /// An optional status ping.
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub ping: Option<Ping>,
180
181 /// Extra fields to include (App-specific fields used to extend the protocol).
182 ///
183 /// # NOTE: Can break the omaha protocol if improperly used.
184 ///
185 /// This is listed last in the struct, and should remain so, due to how Serde behaves when
186 /// flattening fields into the parent. If this map contains a field whose name matches that of
187 /// another field in the struct (such as `id`), it will overwrite that field. If that field is
188 /// optionally serialized (such as `update_check`), it will still overwrite that field
189 /// (regardless of the presence or not of the field it's overwriting).
190 #[serde(flatten)]
191 pub extra_fields: HashMap<String, String>,
192}
193
194/// This is an update check for the parent App object.
195///
196/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#updatecheck-request
197#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
198pub struct UpdateCheck {
199 /// If the update is disabled, the client will not honor an 'update' response. The default
200 /// value of false indicates that the client will attempt an update if instructed that one is
201 /// available.
202 #[serde(skip_serializing_if = "std::ops::Not::not")]
203 #[serde(rename = "updatedisabled")]
204 pub disabled: bool,
205
206 /// If true, Omaha will offer an update even if the client is already running the same version.
207 #[serde(skip_serializing_if = "std::ops::Not::not")]
208 #[serde(rename = "sameversionupdate")]
209 pub offer_update_if_same_version: bool,
210}
211
212impl UpdateCheck {
213 /// Public constructor for an update check request on an app that will not honor an 'update'
214 /// response and will not perform an update if one is available.
215 pub fn disabled() -> Self {
216 UpdateCheck {
217 disabled: true,
218 offer_update_if_same_version: false,
219 }
220 }
221}
222
223/// This is a status ping to the Omaha service.
224///
225/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#ping-request
226///
227/// These pings only support the Client-Regulated Counting method (Date-based). For more info, see
228/// https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#client-regulated-Counting-days-based
229#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)]
230pub struct Ping {
231 /// This is the January 1, 2007 epoch-based value for the date that was previously sent to the
232 /// client by the service, as the elapsed_days value of the daystart object, if the application
233 /// is active.
234 ///
235 /// This is the 'ad' attribute of the ping object.
236 #[serde(rename = "ad")]
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub date_last_active: Option<u32>,
239
240 /// This is the January 1, 2007 epoch-based value for the date that was previously sent to the
241 /// client by the service, as the elapsed_days value of the daystart object, if the application
242 /// is active or not.
243 ///
244 /// This is the 'rd' attribute of the ping object.
245 #[serde(rename = "rd")]
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub date_last_roll_call: Option<u32>,
248}
249
250/// An event that is being reported to the Omaha service.
251///
252/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#event-request
253#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)]
254pub struct Event {
255 /// This is the event type for the event (see the enum for more information).
256 ///
257 /// This is the eventtype attribute of the event object.
258 #[serde(rename = "eventtype")]
259 pub event_type: EventType,
260
261 /// This is the result code for the event. All event types share a namespace for result codes.
262 ///
263 /// This is the eventresult attribute of the event object.
264 #[serde(rename = "eventresult")]
265 pub event_result: EventResult,
266
267 /// This is an opaque error value that may be provided. It's meaning is application specific.
268 ///
269 /// This is the errorcode attribute of the event object.
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub errorcode: Option<EventErrorCode>,
272
273 /// The version of the app that was present on the machine at the time of the update-check of
274 /// this update flow, regardless of the success or failure of the update operation.
275 #[serde(skip_serializing_if = "Option::is_none")]
276 #[serde(rename = "previousversion")]
277 pub previous_version: Option<String>,
278
279 /// The version of the app that the update flow to which this event belongs attempted to
280 /// reach, regardless of success or failure of the update operation.
281 #[serde(skip_serializing_if = "Option::is_none")]
282 #[serde(rename = "nextversion")]
283 pub next_version: Option<String>,
284
285 /// For events representing a download, the time elapsed between the start of the download and
286 /// the end of the download, in milliseconds. For events representing an entire update flow,
287 /// the sum of all such download times over the course of the update flow.
288 /// Sent in <event>s that have an eventtype of "1", "2", "3", and "14" only.
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub download_time_ms: Option<u64>,
291}
292
293impl Event {
294 /// Creates a new successful event for the given event type.
295 pub fn success(event_type: EventType) -> Self {
296 Self {
297 event_type,
298 event_result: EventResult::Success,
299 ..Self::default()
300 }
301 }
302
303 /// Creates a new error event for the given event error code.
304 pub fn error(errorcode: EventErrorCode) -> Self {
305 Self {
306 event_type: EventType::UpdateComplete,
307 event_result: EventResult::Error,
308 errorcode: Some(errorcode),
309 ..Self::default()
310 }
311 }
312}
313
314/// The type of event that is being reported. These are specified by the Omaha protocol.
315///
316/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#event-request
317#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize_repr)]
318#[repr(u8)]
319pub enum EventType {
320 #[default]
321 Unknown = 0,
322
323 /// The initial download of the application is complete.
324 DownloadComplete = 1,
325
326 /// The initial installation of the application is complete.
327 InstallComplete = 2,
328
329 /// The application update is complete.
330 UpdateComplete = 3,
331
332 /// The download of the update for the application has started.
333 UpdateDownloadStarted = 13,
334
335 /// The download of the update for the application is complete.
336 UpdateDownloadFinished = 14,
337
338 /// The application is now using the updated software. This is sent after a successful boot
339 /// into the update software.
340 RebootedAfterUpdate = 54,
341}
342
343/// The result of event that is being reported. These are specified by the Omaha protocol.
344///
345/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#event-request
346#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize_repr)]
347#[repr(u8)]
348pub enum EventResult {
349 #[default]
350 Error = 0,
351 Success = 1,
352 SuccessAndRestartRequired = 2,
353 SuccessAndAppRestartRequired = 3,
354 Cancelled = 4,
355 ErrorInSystemInstaller = 8,
356
357 /// The client acknowledges that it received the 'update' response, but it will not be acting
358 /// on the update at this time (deferred by Policy).
359 UpdateDeferred = 9,
360}
361
362/// The error code of the event. These are application specific.
363#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize_repr)]
364#[repr(i32)]
365pub enum EventErrorCode {
366 /// Error when parsing Omaha response.
367 #[default]
368 ParseResponse = 0,
369 /// Error when constructing install plan.
370 ConstructInstallPlan = 1,
371 /// Error when installing the update.
372 Installation = 2,
373 /// The update is denied by policy.
374 DeniedByPolicy = 3,
375}
376
377/// The GUID used in Omaha protocol for sessionid and requestid.
378///
379/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#guids
380#[derive(Debug, Default, Clone, Eq, PartialEq)]
381pub struct GUID {
382 uuid: uuid::Uuid,
383}
384
385impl GUID {
386 /// Creates a new random GUID.
387 #[cfg(not(test))]
388 pub fn new() -> Self {
389 Self {
390 uuid: uuid::Uuid::new_v4(),
391 }
392 }
393
394 // For unit tests, creates GUID using a thread local counter, so that for every test case,
395 // the first GUID will be {00000000-0000-0000-0000-000000000000},
396 // and the second will be {00000000-0000-0000-0000-000000000001}, and so on.
397 #[cfg(test)]
398 pub fn new() -> Self {
399 thread_local! {
400 static COUNTER: std::cell::RefCell<u128> =
401 const { std::cell::RefCell::new(0) };
402 }
403 COUNTER.with(|counter| {
404 let mut counter = counter.borrow_mut();
405 let guid = Self::from_u128(*counter);
406 *counter += 1;
407 guid
408 })
409 }
410
411 #[cfg(test)]
412 pub fn from_u128(n: u128) -> Self {
413 Self {
414 uuid: uuid::Uuid::from_u128(n),
415 }
416 }
417}
418
419// Wrap the uuid in {}.
420impl Serialize for GUID {
421 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
422 where
423 S: Serializer,
424 {
425 self.uuid.as_braced().serialize(serializer)
426 }
427}