use std::path::Path;
use serde::{Deserialize, Serialize};
pub const CLOUD_LINK_SIDECAR: &str = "/run/ados/cloud-link.json";
pub const CLOUD_LINK_SIDECAR_VERSION: u16 = 1;
pub const CLOUD_LINK_STALE_MS: i64 = 20_000;
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct CloudLink {
#[serde(default)]
pub version: u16,
pub generated_at_ms: Option<i64>,
#[serde(default)]
pub paired: bool,
#[serde(default)]
pub cloud_url_set: bool,
pub broker_connected: Option<bool>,
pub last_heartbeat_ok_ms: Option<i64>,
pub last_heartbeat_status: Option<u16>,
pub last_heartbeat_error: Option<String>,
}
pub fn read_cloud_link_from(path: &Path, now_ms: i64) -> Option<CloudLink> {
let text = std::fs::read_to_string(path).ok()?;
let link: CloudLink = serde_json::from_str(&text).ok()?;
let generated = link.generated_at_ms?;
if generated > now_ms || now_ms - generated > CLOUD_LINK_STALE_MS {
return None;
}
crate::sidecar::check_sidecar_version("cloud-link", link.version, CLOUD_LINK_SIDECAR_VERSION);
Some(link)
}
pub fn read_cloud_link(now_ms: i64) -> Option<CloudLink> {
read_cloud_link_from(Path::new(CLOUD_LINK_SIDECAR), now_ms)
}
pub fn write_cloud_link_to(path: &Path, link: &CloudLink) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let body = serde_json::to_vec(link).map_err(std::io::Error::other)?;
let tmp = path.with_extension("json.tmp");
std::fs::write(&tmp, body)?;
std::fs::rename(&tmp, path)
}
pub fn write_cloud_link(link: &CloudLink) -> std::io::Result<()> {
write_cloud_link_to(Path::new(CLOUD_LINK_SIDECAR), link)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_link_reads_back_and_a_stale_or_future_one_does_not() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("cloud-link.json");
let now = 1_700_000_000_000i64;
let link = CloudLink {
version: CLOUD_LINK_SIDECAR_VERSION,
generated_at_ms: Some(now),
paired: true,
cloud_url_set: true,
broker_connected: Some(true),
last_heartbeat_ok_ms: Some(now - 2_000),
last_heartbeat_status: Some(200),
last_heartbeat_error: None,
};
write_cloud_link_to(&path, &link).unwrap();
assert_eq!(read_cloud_link_from(&path, now + 1_000), Some(link));
assert!(read_cloud_link_from(&path, now + CLOUD_LINK_STALE_MS + 1).is_none());
assert!(read_cloud_link_from(&path, now - 1).is_none());
}
}