lucet_module/version_info.rs
1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
2use std::cmp::min;
3use std::fmt;
4use std::io;
5
6/// VersionInfo is information about a Lucet module to allow the Lucet runtime to determine if or
7/// how the module can be loaded, if so requested. The information here describes implementation
8/// details in runtime support for `lucetc`-produced modules, and nothing higher level.
9#[repr(C)]
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct VersionInfo {
12 major: u16,
13 minor: u16,
14 patch: u16,
15 reserved: u16,
16 /// `version_hash` is either all nulls or the first eight ascii characters of the git commit
17 /// hash of wherever this Version is coming from. In the case of a compiled lucet module, this
18 /// hash will come from the git commit that the lucetc producing it came from. In a runtime
19 /// context, it will be the git commit of lucet-runtime built into the embedder.
20 ///
21 /// The version hash will typically populated only in release builds, but may blank even in
22 /// that case: if building from a packaged crate, or in a build environment that does not have
23 /// "git" installed, `lucetc` and `lucet-runtime` will fall back to an empty hash.
24 version_hash: [u8; 8],
25}
26
27impl fmt::Display for VersionInfo {
28 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
29 write!(fmt, "{}.{}.{}", self.major, self.minor, self.patch)?;
30 if u64::from_ne_bytes(self.version_hash) != 0 {
31 write!(
32 fmt,
33 "-{}",
34 std::str::from_utf8(&self.version_hash).unwrap_or("INVALID")
35 )?;
36 }
37 Ok(())
38 }
39}
40
41impl VersionInfo {
42 pub fn new(major: u16, minor: u16, patch: u16, version_hash: [u8; 8]) -> VersionInfo {
43 VersionInfo {
44 major,
45 minor,
46 patch,
47 reserved: 0x8000,
48 version_hash,
49 }
50 }
51
52 /// A more permissive version check than for version equality. This check will allow an `other`
53 /// version that is more specific than `self`, but matches for data that is available.
54 pub fn compatible_with(&self, other: &VersionInfo) -> bool {
55 if !(self.valid() || other.valid()) {
56 return false;
57 }
58
59 if self.major == other.major && self.minor == other.minor && self.patch == other.patch {
60 if self.version_hash == [0u8; 8] {
61 // we aren't bound to a specific git commit, so anything goes.
62 true
63 } else {
64 self.version_hash == other.version_hash
65 }
66 } else {
67 false
68 }
69 }
70
71 pub fn write_to<W: WriteBytesExt>(&self, w: &mut W) -> io::Result<()> {
72 w.write_u16::<LittleEndian>(self.major)?;
73 w.write_u16::<LittleEndian>(self.minor)?;
74 w.write_u16::<LittleEndian>(self.patch)?;
75 w.write_u16::<LittleEndian>(self.reserved)?;
76 w.write(&self.version_hash).and_then(|written| {
77 if written != self.version_hash.len() {
78 Err(io::Error::new(
79 io::ErrorKind::Other,
80 "unable to write full version hash",
81 ))
82 } else {
83 Ok(())
84 }
85 })
86 }
87
88 pub fn read_from<R: ReadBytesExt>(r: &mut R) -> io::Result<Self> {
89 let mut version_hash = [0u8; 8];
90 Ok(VersionInfo {
91 major: r.read_u16::<LittleEndian>()?,
92 minor: r.read_u16::<LittleEndian>()?,
93 patch: r.read_u16::<LittleEndian>()?,
94 reserved: r.read_u16::<LittleEndian>()?,
95 version_hash: {
96 r.read_exact(&mut version_hash)?;
97 version_hash
98 },
99 })
100 }
101
102 pub fn valid(&self) -> bool {
103 self.reserved == 0x8000
104 }
105
106 pub fn current(current_hash: &'static [u8]) -> Self {
107 let mut version_hash = [0u8; 8];
108
109 for i in 0..min(version_hash.len(), current_hash.len()) {
110 version_hash[i] = current_hash[i];
111 }
112
113 // The reasoning for this is as follows:
114 // `SerializedModule`, in version before version information was introduced, began with a
115 // pointer - `module_data_ptr`. This pointer would be relocated to somewhere in user space
116 // for the embedder of `lucet-runtime`. On x86_64, hopefully, that's userland code in some
117 // OS, meaning the pointer will be a pointer to user memory, and will be below
118 // 0x8000_0000_0000_0000. By setting `reserved` to `0x8000`, we set what would be the
119 // highest bit in `module_data_ptr` in an old `lucet-runtime` and guarantee a segmentation
120 // fault when loading these newer modules with version information.
121 VersionInfo::new(
122 env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
123 env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
124 env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
125 version_hash,
126 )
127 }
128}