-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dgw): add agent version field to heartbeat API (#1122)
- Loading branch information
1 parent
8585030
commit 83fbddb
Showing
17 changed files
with
302 additions
and
186 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
mod reversed_hex_uuid; | ||
|
||
pub mod registry; | ||
|
||
use uuid::{uuid, Uuid}; | ||
|
||
pub use reversed_hex_uuid::InvalidReversedHexUuid; | ||
|
||
/// MSI upgrade code for the Devolutions Gateway. | ||
/// | ||
/// MSI update code is same for all versions of the product, while product code is different for | ||
/// each version. We are using the update code to find installed product version or its product | ||
/// code in the Windows registry. | ||
pub const GATEWAY_UPDATE_CODE: Uuid = uuid!("{db3903d6-c451-4393-bd80-eb9f45b90214}"); | ||
/// MSI upgrade code for the Devolutions Agent. | ||
/// | ||
/// See [`GATEWAY_UPDATE_CODE`] for more information on update codes. | ||
pub const AGENT_UPDATE_CODE: Uuid = uuid!("{82318d3c-811f-4d5d-9a82-b7c31b076755}"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
use uuid::Uuid; | ||
|
||
use crate::windows::reversed_hex_uuid::{reversed_hex_to_uuid, uuid_to_reversed_hex, InvalidReversedHexUuid}; | ||
use crate::DateVersion; | ||
|
||
const REG_CURRENT_VERSION: &str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion"; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
pub enum RegistryError { | ||
#[error("failed to open registry key `{key}`")] | ||
OpenKey { key: String, source: windows_result::Error }, | ||
#[error("failed to enumerate registry key values from `{key}`")] | ||
EnumKeyValues { key: String, source: windows_result::Error }, | ||
#[error("failed to read registry value `{value}` from key `{key}`")] | ||
ReadValue { | ||
value: String, | ||
key: String, | ||
source: windows_result::Error, | ||
}, | ||
#[error(transparent)] | ||
InvalidReversedHexUuid(#[from] InvalidReversedHexUuid), | ||
} | ||
|
||
/// Get the product code of an installed MSI using its upgrade code. | ||
pub fn get_product_code(update_code: Uuid) -> Result<Option<Uuid>, RegistryError> { | ||
let reversed_hex_uuid = uuid_to_reversed_hex(update_code); | ||
|
||
let key_path = format!("{REG_CURRENT_VERSION}\\Installer\\UpgradeCodes\\{reversed_hex_uuid}"); | ||
|
||
let update_code_key = windows_registry::LOCAL_MACHINE.open(&key_path); | ||
|
||
// Product not installed if no key found. | ||
let update_code_key = match update_code_key { | ||
Ok(key) => key, | ||
Err(_) => return Ok(None), | ||
}; | ||
|
||
// Product code is the name of the only value in the registry key. | ||
let (product_code, _) = match update_code_key | ||
.values() | ||
.map_err(|source| RegistryError::EnumKeyValues { key: key_path, source })? | ||
.next() | ||
{ | ||
Some(value) => value, | ||
None => return Ok(None), | ||
}; | ||
|
||
Ok(Some(reversed_hex_to_uuid(&product_code)?)) | ||
} | ||
|
||
/// Get the installed version of a product using Windows registry. Returns `None` if the product | ||
/// is not installed. | ||
pub fn get_installed_product_version(update_code: Uuid) -> Result<Option<DateVersion>, RegistryError> { | ||
let product_code_uuid = match get_product_code(update_code)? { | ||
Some(uuid) => uuid, | ||
None => return Ok(None), | ||
}; | ||
|
||
let key_path = format!("{REG_CURRENT_VERSION}\\Uninstall\\{product_code_uuid}"); | ||
|
||
const VERSION_VALUE_NAME: &str = "Version"; | ||
|
||
// Now we know the product code of installed MSI, we could read its version. | ||
let product_tree = windows_registry::LOCAL_MACHINE | ||
.open(&key_path) | ||
.map_err(|source| RegistryError::OpenKey { | ||
key: key_path.clone(), | ||
source, | ||
})?; | ||
|
||
let product_version: u32 = product_tree | ||
.get_value(VERSION_VALUE_NAME) | ||
.and_then(TryInto::try_into) | ||
.map_err(|source| RegistryError::ReadValue { | ||
value: VERSION_VALUE_NAME.to_owned(), | ||
key: key_path.clone(), | ||
source, | ||
})?; | ||
|
||
// Convert encoded MSI version number to human-readable date. | ||
let short_year = (product_version >> 24) + 2000; | ||
let month = (product_version >> 16) & 0xFF; | ||
let day = product_version & 0xFFFF; | ||
|
||
Ok(Some(DateVersion { | ||
year: short_year, | ||
month, | ||
day, | ||
// NOTE: Windows apps could only have 3 version numbers (major, minor, patch). | ||
revision: 0, | ||
})) | ||
} |
90 changes: 90 additions & 0 deletions
90
crates/devolutions-agent-shared/src/windows/reversed_hex_uuid.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
//! Windows-specific UUID format conversion functions. | ||
use std::str::FromStr; | ||
use uuid::Uuid; | ||
|
||
const REVERSED_UUID_STR_LENGTH: usize = uuid::fmt::Simple::LENGTH; | ||
const UUID_REVERSING_PATTERN: &[usize] = &[8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2]; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
#[error("invalid UUID representation {uuid}")] | ||
pub struct InvalidReversedHexUuid { | ||
uuid: String, | ||
} | ||
|
||
/// Converts standard UUID to its reversed hex representation used in Windows Registry | ||
/// for upgrade code table. | ||
/// | ||
/// e.g.: `{82318d3c-811f-4d5d-9a82-b7c31b076755}` => `C3D81328F118D5D4A9287B3CB1707655` | ||
pub(crate) fn uuid_to_reversed_hex(uuid: Uuid) -> String { | ||
let mut simple_uuid_buffer = [0u8; REVERSED_UUID_STR_LENGTH]; | ||
let mut hex_chars_slice: &str = uuid.as_simple().encode_upper(&mut simple_uuid_buffer); | ||
|
||
let mut reversed_hex = String::with_capacity(REVERSED_UUID_STR_LENGTH); | ||
|
||
for block_len in UUID_REVERSING_PATTERN.iter() { | ||
let (block, rest) = hex_chars_slice.split_at(*block_len); | ||
reversed_hex.extend(block.chars().rev()); | ||
hex_chars_slice = rest; | ||
} | ||
|
||
assert!( | ||
reversed_hex.len() == REVERSED_UUID_STR_LENGTH, | ||
"UUID_REVERSING_PATTERN should ensure output length" | ||
); | ||
|
||
reversed_hex | ||
} | ||
|
||
/// Converts reversed hex UUID back to standard Windows Registry format (upper case letters). | ||
/// | ||
/// e.g.: `C3D81328F118D5D4A9287B3CB1707655` => `{82318d3c-811f-4d5d-9a82-b7c31b076755}` | ||
pub(crate) fn reversed_hex_to_uuid(mut hex: &str) -> Result<Uuid, InvalidReversedHexUuid> { | ||
if hex.len() != REVERSED_UUID_STR_LENGTH { | ||
return Err(InvalidReversedHexUuid { uuid: hex.to_owned() }); | ||
} | ||
|
||
let mut uuid_chars = String::with_capacity(uuid::fmt::Simple::LENGTH); | ||
|
||
for pattern in UUID_REVERSING_PATTERN.iter() { | ||
let (part, rest) = hex.split_at(*pattern); | ||
uuid_chars.extend(part.chars().rev()); | ||
hex = rest; | ||
} | ||
|
||
assert!( | ||
uuid_chars.len() == REVERSED_UUID_STR_LENGTH, | ||
"UUID_REVERSING_PATTERN should ensure output length" | ||
); | ||
|
||
let uuid = Uuid::from_str(&uuid_chars).map_err(|_| InvalidReversedHexUuid { uuid: hex.to_owned() })?; | ||
|
||
Ok(uuid) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn convert_uuid_to_reversed_hex() { | ||
assert_eq!( | ||
uuid_to_reversed_hex(uuid::uuid!("{82318d3c-811f-4d5d-9a82-b7c31b076755}")), | ||
"C3D81328F118D5D4A9287B3CB1707655" | ||
); | ||
} | ||
|
||
#[test] | ||
fn convert_reversed_hex_to_uuid() { | ||
assert_eq!( | ||
reversed_hex_to_uuid("C3D81328F118D5D4A9287B3CB1707655").unwrap(), | ||
uuid::uuid!("{82318D3C-811F-4D5D-9A82-B7C31B076755}") | ||
); | ||
} | ||
|
||
#[test] | ||
fn reversed_hex_to_uuid_failure() { | ||
assert!(reversed_hex_to_uuid("XXX81328F118D5D4A9287B3CB1707655").is_err()); | ||
assert!(reversed_hex_to_uuid("ABCD").is_err()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.