Skip to main content

aion_server/worker/
deployed_binary.rs

1//! The identity a worker-deployment record captures for the binary it names.
2//!
3//! A `builtin` deployment names THIS server's executable, and the record has to
4//! say which executable that was when the record was written — the content, not
5//! the path, because a path is a label recording where a file used to be. The
6//! supervisor captures the identity again at every spawn, and the two
7//! disagreeing is how a restart across an upgrade becomes a visible fact
8//! instead of a silent swap.
9//!
10//! This lives in the worker domain rather than on a transport because both
11//! transports and auto-provision mint records, and three copies of one capture
12//! is three chances for them to disagree about what a deployment's binary is.
13
14use aion_store::DeployedBinaryIdentity;
15
16use crate::build_identity::BuildIdentity;
17use sha2::{Digest, Sha256};
18
19/// Typed deploy-time executable identity capture failures.
20#[derive(Debug, thiserror::Error)]
21pub enum BinaryIdentityCaptureError {
22    /// The operating system could not identify this executable.
23    #[error("could not resolve the running server executable: {source}")]
24    CurrentExecutable {
25        /// The operating system's own diagnosis.
26        #[source]
27        source: std::io::Error,
28    },
29    /// The executable bytes could not be read.
30    #[error("could not read running server executable `{path}`: {source}")]
31    ReadExecutable {
32        /// The executable whose bytes could not be read.
33        path: std::path::PathBuf,
34        /// The operating system's own diagnosis.
35        #[source]
36        source: std::io::Error,
37    },
38}
39
40/// Capture the running server executable's identity for a deployment record.
41///
42/// # Errors
43///
44/// Returns [`BinaryIdentityCaptureError`] when the operating system cannot name
45/// this executable or its bytes cannot be read. Neither is defaulted: a record
46/// carrying an invented hash would report every future upgrade as a match.
47pub fn capture_binary_identity() -> Result<DeployedBinaryIdentity, BinaryIdentityCaptureError> {
48    let path = std::env::current_exe()
49        .map_err(|source| BinaryIdentityCaptureError::CurrentExecutable { source })?;
50    let bytes =
51        std::fs::read(&path).map_err(|source| BinaryIdentityCaptureError::ReadExecutable {
52            path: path.clone(),
53            source,
54        })?;
55    let identity = BuildIdentity::current();
56    let content_hash = lowercase_hex(&Sha256::digest(bytes));
57    Ok(DeployedBinaryIdentity {
58        version: identity.version.to_owned(),
59        commit: identity.commit.to_owned(),
60        dirty: identity.dirty.to_owned(),
61        content_hash,
62    })
63}
64
65/// Lowercase hexadecimal rendering of a digest.
66///
67/// Written out rather than pulled from a formatting helper so the wire form of
68/// a content hash is decided in one place and cannot acquire a `0x`, an
69/// uppercase run, or a separator from a dependency's default.
70#[must_use]
71pub fn lowercase_hex(bytes: &[u8]) -> String {
72    const DIGITS: &[u8; 16] = b"0123456789abcdef";
73    let mut encoded = String::with_capacity(bytes.len().saturating_mul(2));
74    for byte in bytes {
75        encoded.push(char::from(DIGITS[usize::from(byte >> 4)]));
76        encoded.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
77    }
78    encoded
79}